一、等待通知机制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(); }
}
运行的结果: