线程之间通信+wait/notify+CountDownLatch
线程通信概念:线程是操作系统中独立的个体,但是这些个体如果不经过特殊的处理就不能成为一个整体,线程间的通信就成为整体的必用方式之一。当线程存在通信指挥,系统间的交互性会更加强大,在提高CPU利用率的同时还会使开发人员对线程任务在处理的过程中进行有效的把控与监督。
使用wait/notify方法实现线程间的通信。(注意这两个方法都是Object的类的方法,换句话说java中所有的对象都提供了这两个方法)
1 wait和notify必须配合synchronized关键字使用
2 wait方法释放锁,notify方法不释放锁
下面的例子使用volatile使变量在线程间可见。在主线程中开启了2个子线程,子线程t1向List集合中循环添加10个元素。在t2线程中while(true)轮询List集合中元素的个数,当元素个数为5时,抛出RuntimeException()异常,终止t2子线程。
import java.util.ArrayList;
import java.util.List;
public class ListAdd1 {
private volatile static List list = new ArrayList();
public void add(){
list.add("abc");
}
public int size(){
return list.size();
}
public static void main(String[] args) {
final ListAdd1 list1 = new ListAdd1();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
try {
for(int i = 0; i <10; i++){
list1.add();
System.out.println("当前线程:" + Thread.currentThread().getName() + "添加了一个元素..");
Thread.sleep(500);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "t1");
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
while(true){
if(list1.size() == 5){
System.out.println("当前线程收到通知:" + Thread.currentThread().getName() + " list size = 5 线程停止..");
throw new RuntimeException();
}
}
}
}, "t2");
t1.start();
t2.start();
}
}
在Eclipse的console输出
对上面的例子使用wait/notify进行改造。t2线程先start,因为其List的size!=5,所以执行lock.wait()释放对象锁,这样在t1线程就可以获得这把lock对象锁。t1线程向List中添加元素,当List的size==5时,执行lock.notify(),发出唤醒通知,此时t1线程并不释放lock对象锁,所以这时t2虽然收到唤醒的通知,但是由于t1此时并未释放lock对象锁,所以t2只能一直等待,直到t1执行完毕释放lock对象锁,t2才能获取到lock对象锁,执行lock.wait();后面的代码。
import java.util.ArrayList;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.LinkedBlockingQueue;
/**
* wait notfiy 方法,wait释放锁,notfiy不释放锁
*
*/
public class ListAdd2 {
private volatile static List list = new ArrayList();
public void add(){
list.add("abc");
}
public int size(){
return list.size();
}
public static void main(String[] args) {
final ListAdd2 list2 = new ListAdd2();
//当使用wait 和 notify 的时候 , 一定要配合着synchronized关键字去使用
final Object lock = new Object();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
try {
synchronized (lock) {
for(int i = 0; i <10; i++){
list2.add();
System.out.println("当前线程:" + Thread.currentThread().getName() + "添加了一个元素..");
Thread.sleep(500);
if(list2.size() == 5){
System.out.println("已经发出通知..");
lock.notify();//不释放对象锁
}
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "t1");
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (lock) {
if(list2.size() != 5){
try {
lock.wait();//释放对象锁
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("当前线程:" + Thread.currentThread().getName() + "收到通知线程停止..");
throw new RuntimeException();
}
}
}, "t2");
t2.start();//t2先启动
t1.start();
}
}
在Eclipse的控制台输出
wait/notify的方式弊端:实时性不好,比如查询100万条数据中的一条数据在一个子线程A中实现,在另外一个子线程B中使用wait等待接受notify通知。先启动子线程B,执行到wait()释放锁并等待唤醒通知。而子线程A执行查询,在查询到第5条数据的时候已找到,那么此时虽然能执行notify,发出唤醒通知,但是由于子线程A并未释放对象锁,只有等待子线程A全部执行完毕之后,子线程B才能继续执行wait()后面的代码。因此,使用wait/notify的方式,实时性不好。
下面使用java.util.concurrent包下的类CountDownLatch对上面wait/notify方式的代码进行改造。CountDownLatch机制不是用来保护共享资源或临界区,而是用来同步一个或多个执行任务的线程。
import java.util.ArrayList;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.LinkedBlockingQueue;
/**
* wait notfiy 方法,wait释放锁,notfiy不释放锁
*
*/
public class ListAdd2 {
private volatile static List list = new ArrayList();
public void add(){
list.add("abc");
}
public int size(){
return list.size();
}
public static void main(String[] args) {
final ListAdd2 list2 = new ListAdd2();
// 当使用wait 和 notify 的时候 , 一定要配合着synchronized关键字去使用
//final Object lock = new Object();
final CountDownLatch countDownLatch = new CountDownLatch(1);
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
try {
//synchronized (lock) {
for(int i = 0; i <10; i++){
list2.add();
System.out.println("当前线程:" + Thread.currentThread().getName() + "添加了一个元素..");
Thread.sleep(500);
if(list2.size() == 5){
System.out.println("已经发出通知..");
countDownLatch.countDown();
//lock.notify();//不释放对象锁
}
}
//}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "t1");
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
//synchronized (lock) {
if(list2.size() != 5){
try {
//System.out.println("t2进入...");
//lock.wait();//释放对象锁
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("当前线程:" + Thread.currentThread().getName() + "收到通知线程停止..");
throw new RuntimeException();
//}
}
}, "t2");
t2.start();//t2先启动
t1.start();
}
}
t2先启动,阻塞在countDownLatch.await();。t1启动后再运行过程中,当List的size==5时,执行countDownLatch.countDown();发出唤醒通知,此时,t2接收到通知后,由于没有使用synchronized关键字涉及不到获取锁的问题,因此t2收到通知立即开始执行countDownLatch.await();后面的代码。
在Eclipse中console输出内容如下: