1、wait/notify方法
package sync; import java.util.ArrayList; import java.util.List; public class WaitAndNotify { private volatile static List list= new ArrayList(); private void add() { list.add("wang"); } private int size() { return list.size(); } public static void main(String[] args) { final WaitAndNotify list1 = new WaitAndNotify(); /* * 1. 实例化一个Object对象当作锁,下面t1和t2竞争这把锁实现同步 * 2. Object下面会有wait、notify方法,(面试经常问:Object下面有哪些方法,) * 3. 说明每个对象都有这两个方法,都可以当作锁 */ 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++) { list1.add(); System.out.println("当前线程:" + Thread.currentThread().getName() + "添加了一个元素"); Thread.sleep(500); if(list1.size() == 5) { System.out.println("已经发出通知.."); //notify不释放锁,for循环还会继续执行 lock.notify(); } } } } catch(InterruptedException e) { e.printStackTrace(); } } }, "t1"); Thread t2 = new Thread(new Runnable() { @Override public void run() { synchronized(lock) { /* * 如果先执行t2线程,如果size不是5,就wait * wait会释放锁 */ if(list1.size() != 5) { try { System.out.println("t2进入.."); Thread.sleep(3000); lock.wait(); }catch(InterruptedException e) { e.printStackTrace(); } } // System.out.println("当前线程收到通知:" + Thread.currentThread().getName() + "list size = 5,线程停止"); throw new RuntimeException(); } } }, "t2"); t2.start(); t1.start(); } }
输出结果:
t2进入.. 当前线程:t1添加了一个元素 当前线程:t1添加了一个元素 当前线程:t1添加了一个元素 当前线程:t1添加了一个元素 当前线程:t1添加了一个元素 已经发出通知.. 当前线程:t1添加了一个元素 当前线程:t1添加了一个元素 当前线程:t1添加了一个元素 当前线程:t1添加了一个元素 当前线程:t1添加了一个元素 当前线程收到通知:t2list size = 5,线程停止 Exception in thread "t2" java.lang.RuntimeException at sync.WaitAndNotify$2.run(WaitAndNotify.java:73) at java.lang.Thread.run(Unknown Source)
代码解读:
第一步:执行t2线程,进入run方法,list.size不等于5,就lock.wait释放锁,t2进程等待,转而执行t1
第二步:执行t1进程,得到锁,执行for循环,当list.size等于5时,发出通知..唤醒t2进程,但是会继续执行完for循环,因为notify不释放锁
第三步:t2进程被唤醒,因此list.size已经等于10,不等于5,直接输出最后两行代码
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();后面的代码。
2、CountDownLatch方法
下面使用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输出内容如下: