java并发学习--第六章 线程之间的通信
一、等待通知机制wait()与notify()
在线程中除了线程同步机制外,还有一个最重要的机制就是线程之间的协调任务。比如说最常见的生产者与消费者模式,很明显如果要实现这个模式,我们需要创建两个线程,一个生产者,一个消费者;有两个线程还不够,如果当生产者生产商品完成后,消费者如何知道要去消费生产的商品?为此JDK给我们可提供了wait()和notify()方法来进行线程之间的通信,从而解决了生产者完成后通知消费者进行消费的问题。
wait()和notify()是等待/唤醒机制,调用wait()方法可以让当前线程阻塞,只有当使用notify()时,才能让阻塞的线程继续执行。这样的机制就能够达道两个线程之间通信的功能。
我们来看看这两个方法:
1.wait()、notify()和notifyAll()方法是都是Objec类中自带的方法,因此所有的类都能够使用;
2.wait()、notify()和notifyAll()是native方法,无法被重写;
3.调用某个对象的wait()方法能让当前线程阻塞,并且当前线程必须拥有此对象的锁;
4.notify()方法能够唤醒一个正在等待的线程,如果有多个线程都在等待被唤醒,调用notify()方法只能随机唤醒一个方法;
5.调用notifyAll()方法能够唤醒所有正在等待的线程;
6.使用wait()、notify()和notifyAll()方法必须给线程加锁。
我们用一个例子来介绍生成者与消费者模式:
生产者代码:
/** * 生产者,生成了10个商品后通知消费者消费 */ public class Productor implements Runnable { @Override public void run() { //注意,这里生成者和消费者必须拿同一把锁,如果锁不相同,则这个机制无法成功使用 synchronized (Job.value) { int i = 0; try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } while (i < 10) { //生产任务 Job.value[i] = "商品:" + i; System.out.println("开始生产" + Job.value[i]); i++; } //生产完成后,唤醒所有线程,让消费者消费 System.out.println("生产完成,开始消费"); Job.value.notifyAll(); } } }
消费者代码:
/** * 消费者,等待生产者生成完商品后,才开始消费 */ public class Consume implements Runnable { @Override public void run() { //注意,这里生产者和消费者必须拿同一把锁,如果锁不相同,则这个机制无法成功使用 synchronized (Job.value) { //先判断有没商品生产,如果没有则开始等到生产者生成 if (Job.value[0] == null) { try { //等待生产 System.out.println("当前没有任务,等到生产"); Job.value.wait(0); } catch (InterruptedException e) { e.printStackTrace(); } } //生产完成后,唤醒消费者后面的线程任务,消费商品 for (String string : Job.value) { System.out.println("消费"+string); } } } }
测试类:
public class Job { public static String value[]=new String[10] ; public static void main(String[] args) { Productor productor=new Productor(); Consume consume=new Consume(); new Thread(consume).start(); new Thread(productor).start(); } }
运行的结果:
二、ReentrantLock中的等待唤醒机制Condition类
前面我们已经介绍了JDK中object自带的wait()与notify()方法,但是notify()在多线程环境下只能随机唤醒一个线程,这样的功能实在是有些鸡肋,为此在AQS(AbstractQueuedSynchronizer)中,为我们提供了一个Condition类实现了等待唤醒机制,并且Condition中唤醒的机制更加灵活。
Condition是在java 1.5中才出现的,它用来替代传统的Object的wait()、notify()实现线程间的协作,相比使用Object的wait()、notify(),使用Condition1的await()、signal()这种方式实现线程间协作更加安全和高效。因此通常来说比较推荐使用Condition。
Condition中常用的是await()和signal()方法,与wait()与notify一样,必须获取同一把锁才能实现等待唤醒机制,接下来我们就使用Condition中await()和signal()方法对我们的生产者和消费者模式进行改造:再增加一个消费者2,如果商品队列为空,两个消费者都进行等待,当生产者生产完成后唤醒消费者1,消费者1消费完成后唤醒消费者2,这样就能够指定唤醒一个线程
改造后的生成者:
/** * 生产者,生成了10个商品后唤醒所有线程 */ public class Productor implements Runnable { //获取的锁 Lock lock; //生成者和消费者1使用的等待/通知对象,用于绑定生成者和消费者1的等待/通知关系 Condition condition; public Productor(Lock lock, Condition condition) { this.lock = lock; this.condition = condition; } @Override public void run() { //注意,这里生成者和消费者必须拿同一把锁,如果锁不相同,则这个机制无法成功使用 lock.lock(); int i = 0; try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } while (i < 5) { //生产任务 Job.value[i] = "商品:" + i; System.out.println("开始生产" + Job.value[i]); i++; } //生产完成后,让消费者1消费 System.out.println("生产完成,开始消费"); condition.signal(); //必须要释放锁 lock.unlock(); } }
改造后的消费者1:
/** * 消费者1,等待生产者生成完商品后,才开始消费 */ public class Consume implements Runnable { //获取的锁 Lock lock; //生成者和消费者1使用的等待/通知对象,用于绑定生成者和消费者1的等待/通知关系 Condition condition; //消费者1和消费者2使用的等待/通知对象,用于绑定消费者1和消费者2的等待/通知关系 Condition consumeCondition; public Consume(Lock lock, Condition condition, Condition consumeCondition) { this.lock = lock; this.condition = condition; this.consumeCondition = consumeCondition; } @Override public void run() { //注意,这里生产者和消费者必须拿同一把锁,如果锁不相同,则这个机制无法成功使用 lock.lock(); //先判断有没商品生产,如果没有则开始等到生产者生成 if (Job.value[0] == null) { try { //等待生产 System.out.println("我是消费者1当前没有任务,等待生产"); condition.await(); } catch (InterruptedException e) { e.printStackTrace(); } } //生产完成后,唤醒消费者后面的线程任务,消费商品 for (String string : Job.value) { System.out.println("消费者1清理"+string); } System.out.println("消费者1清理完毕,唤醒消费者2进行消费"); //当所有都消费完成后,通知消费者2进行消费 consumeCondition.signal(); //必须要释放锁 lock.unlock(); } }
新增消费者2:
/** * 消费者2,等待消费者1清理完成商品后,才开始消费 */ public class Consume2 implements Runnable { //获取的锁 Lock lock; //消费者1和消费者2使用的等待/通知对象,用于绑定消费者1和消费者2的等待/通知关系 Condition consumeCondition; public Consume2(Lock lock, Condition consumeCondition) { this.lock = lock; this.consumeCondition = consumeCondition; } @Override public void run() { //注意,这里生产者和消费者必须拿同一把锁,如果锁不相同,则这个机制无法成功使用 lock.lock(); //先判断有没商品生产,如果没有则开始等到生产者生成 if (Job.value[0] == null) { try { //等待生产 System.out.println("我是消费者2,当前没有任务,等待生产"); consumeCondition.await(); } catch (InterruptedException e) { e.printStackTrace(); } } //生产完成后,唤醒消费者后面的线程任务,消费商品 for (String string : Job.value) { System.out.println("消费者2消费"+string); } //必须要释放锁 lock.unlock(); } }
测试类:
public class Job { //创建一把锁 private static Lock lock = new ReentrantLock(); //生成者和消费者1使用的等待/通知对象,用于绑定生成者和消费者1的等待/通知关系 private static Condition condition = lock.newCondition(); //消费者1和消费者2使用的等待/通知对象,用于绑定消费者1和消费者2的等待/通知关系 private static Condition consumeCondition = lock.newCondition(); public static String value[] = new String[5]; public static void main(String[] args) { Productor productor = new Productor(lock,condition); Consume consume = new Consume(lock,condition,consumeCondition); Consume2 consume2=new Consume2(lock,consumeCondition); new Thread(consume2).start(); new Thread(consume).start(); new Thread(productor).start(); } }
运行的结果: