一、等待通知机制wait()与notify()
在线程中除了线程同步机制外,还有一个最重要的机制就是线程之间的协调任务。比如说最常见的生产者与消费者模式,我们用一个例子来介绍这种模式:
生产者代码:
/**
* 生产者,生成了10个商品后通知消费者消费
*/
public class Productor implements Runnable {
@Override
public void run() {
//注意,这里生成者和消费者必须拿同一把锁,如果锁不相同,则这个机制无法成功使用
synchronized (Job.value) {
int i = 0;
while (i < 10) {
//生产任务
try {
Thread.sleep(100);
Job.value[i] = "商品:" + i;
System.out.println("开始生产" + Job.value[i]);
i++;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//生产完成后,唤醒所有线程,让消费者消费
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();
}
}
知识兔运行的结果:
二、wait()与notify()的用法
1.wait()、notify()和notifyAll()方法是都是Objec类中自带的方法,因此所有的类都能够使用;
2.wait()、notify()和notifyAll()是native方法,无法被重写;
3.调用某个对象的wait()方法能让当前线程阻塞,并且当前线程必须拥有此对象的锁;
4.notify()方法能够唤醒一个正在等待的线程,如果有多个线程都在等待被唤醒,调用notify()方法只能随机唤醒一个方法;
5.调用notifyAll()方法能够唤醒所有正在等待的线程;
6.使用wait()、notify()和notifyAll()方法必须给线程加锁。
三、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();
}
}
知识兔运行的结果: