等待·唤醒机制 与生产消费者模型
等待唤醒机制:线程之间的通信
通过保证在临界区上由多个线程的相互排斥从,线程同步完全可以避免资源冲突的发生,但是有时还需要线程之间的相互协作。使用条件便于线程之间的通信。
线程间通信三大问题:
1,分工
2,互斥
3,同步
重点: 有效利用资源 :对其状态的判断
1.机制
**同步队列:**所有尝试获取锁对象失败的线程保存在这个队列中,排队等待获取锁;
**等待队列:**已经拿到锁的线程,在等待其他资源时,主动释放锁,置入该等待队列中等待被唤醒。
队列先进先出:引出“公平锁”
2. synchronized锁 等待唤醒 实现机制
2.1 机制
没有资源–>消费者线程唤醒生产者线程–>消费者线程等待–>生产者线程生产–>资源生成–>修改资源状态为“有”;
有资源–>生产者线程唤醒消费者线程–>生产者线程等待–>消费者线程消费–>消费资源–>修改资源状态为“没有”;
Object下的方法:
2.2 wait():
1.wait()方法的作用是使当前执行代码的线程进行等待,wait()方法是Object类的方法,该方法用来将当前线程置入“等待队列”中,并且在wait()所在的代码处停止执行,直到被唤醒或被中断。
2.wait()方法只能在同步块或同步代码中使用。如果调用wait()方法时必须持有锁,会抛出异常。
3.重载
public final native void wait(Long timeout):
等待一段时间,若没有唤醒,自行唤醒,并将自己置于同步队列中
4.注意!!!:
一旦线程调用待条件的await()
请解释sleep()与wait()的区别:
1.sleep()是Thread类中定义的方法,到了一定的时间后该线程自动唤醒,不会释放对象锁;
2.wait()是Object类中定义的方法,要想唤醒必须使用notify()、notifyAll()方法才能唤醒锁,会释放对象锁。
2.3 notify():
会在等待队列中任意唤醒一个线程,将其置入同步队列尾部,排队获取锁。
唤醒对象获取了锁,只有其执行完毕后才会释放锁。
2.4 notifyAll():
会把等待队列中的所有线程都唤醒,都置于同步队列中。
2.5 注意:
1.wait()与notify()必须由同一个锁调用
2.wait()与notify()都属于Object类的方法;
3.wait()方法与notify()必须在同步代码块或同步方法中使用,首先必须获取锁。
3. Lock锁 等待唤醒 实现机制
3.1 Condition 简介
线程间的通信通常用到Object类中的 wait(),notify()这两个方法以及他们的重载。这两个方法是与对象监视器配合完成线程间的等待/通知机制,而Condition 与Lock配合完成等待唤醒机制,前者是java底层级别的,后者是语言级别的,具有更高的可控制性和扩展性。
两者的功能特性差异
1,Condition能够支持不响应中断,而通过使用Object方式不支持
2,Condition能够支持多个等待队列(new 多个Condition对象),而Object方式只能支持一个
3,Condition能够支持超时时间的设置,而Object不支持
3.2 Condition实现原理
3.2.1 等待队列
创建一个Condition对象使用lock.newCondition()
,这个方法实际上会呢哇出一个ConditionObject对象,该类是AQS的一个内部类,AQS内部维护了一个同步队列,所有获取锁失败的线程会尾插中这个同步队列中,而,condition内部也是同样的维护了一个等待队列,所有调用condition.await方法的线程会加入到等待队列中,并且线程状态转换为等待状态。
多次使用newCondition()
创建对象,会创建出多个等待队列,即**一个Lock可以持有多个等待队列。**而之前的Object对象监视器只能拥有一个同步队列和一个等待队列
3.3 方法
3.3.1 await()
释放Lock锁,将线程置于等待队列阻塞
一旦线程调用带条件的await(),线程就进入等待状态等待恢复信号。如果忘记对状态调用signal()或者signalAll
(),那么线程就会永远等待下去。
重载:await()
3.3.2 signal()与singalAll
调用signal()或signalAll
可以将等待队列中的等待时间最长的节点移动到同步队列中。
3.4 注意
状态由Lock对象创建。为了调用任意条件(如await()、signal()和signalAll
()),必须首先获取锁。如果没有获取锁就调用这些方法,会抛出IllegalMonitorStateException
异常。
4 .到底使用sunchronized
还是Lock?
1.若无特殊的应用场景,推荐使用synchronized,其使用方便(隐式的加减锁),并且由于synchronized是JVM层面的实现,在之后的JDK还有对其优化的空间。
2.若要使用公平锁、读写锁、超时过去锁等特殊场景、才会考虑使用Lock
5 . 生产-消费者模型
1,资源类:设置资源属性;设置资源状态;
资源状态分为两类
1)true:有资源
2)false:没有资源
2,生产者:生产者类是一个线程类,实现Runnable接口,设置线程任务:生产资源
3,消费者:消费者类 是一个线程类,实现Runnable接口,设置线程任务:消费资源
4,测试类:包含mian方法,生产方法和消费方法。
实例 实现:
package PrCoTest;
/**
* @Name: 生产消费者模型
* @Author:ZYJ
* @Date:2019-06-10-19:46
* @Description: 模拟线程间通信
*/
/**
* 测试类,包含main方法,创建生产消费者线程
*/
public class PrCoDemo {
public static void main(String[] args) throws InterruptedException {
Goods goods =new Goods();
Produce produce = new Produce(goods);
Consumer consumer = new Consumer(goods);
Thread produceThread= new Thread(produce,"生产者线程");
Thread consumerThread = new Thread(consumer,"消费者线程");
produceThread.start();
produceThread.sleep(1000);
consumerThread.start();
}
}
/**
* 资源类
* 设置资源属性 资源状态
* 同步生产方法
* 同步消费方法
*/
class Goods{
private String goodsName;//商品名称
private int count; //商品数量
/**
* 生产方法
* 对资源的状态进行判断 若有资源 调用wait()等待
* 若无资源 生产
* @param goodsName
*/
public synchronized void set(String goodsName) throws InterruptedException {
//有商品
if(count>0){
System.out.println("有"+goodsName+",等待消费者消费....");
wait();
}
//没有商品
this.goodsName=goodsName;
this.count=count+1;
System.out.println("生产"+toString());
//生产完毕,唤醒消费者线程消费
notify();
}
/**
* 消费方法
* 对资源状态进行判断 若有资源,消费 ,消费后将状态置为 没有资源
* 若没有资源 调用wait() 等待
*/
public synchronized void get() throws InterruptedException {
//没有商品
if(count==0){
System.out.println(goodsName+"没有卖光了,等一等...");
wait();
}
//有商品
this.count=this.count-1;
Thread.sleep(1000);
System.out.println("买买买"+toString());
//消费完毕,唤醒生产者线程生产
notify();
}
@Override
public String toString(){
return "Goods[ goodsName="+goodsName+",库存为"+count+"]";
}
}
/**
* 生产者类
* 创建资源对象变量,有一个带参的构造方法为这个资源变量赋值
* 覆写run()方法,设置线程任务
*
*/
class Produce implements Runnable{
private Goods goods;
Produce(Goods goods) {
super();//子类没有无参构造时,显示super().
this.goods = goods;
}
//设置线程任务
@Override
public void run() {
try {
this.goods.set("房子");//调用生产方法
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/**
* 消费者类
* 创建资源对象变量,有一个带参的构造方法为这个资源变量赋值
* 覆写run()方法,设置线程任务
*/
class Consumer implements Runnable{
private Goods goods;
Consumer(Goods goods) {
super();
this.goods = goods;
}
//设置线程任务
@Override
public void run() {
try {
this.goods.get();//调用消费方法
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
实例 多生产与多消费
package PrCoSTest;
/**
* @Name: 生产消费者模型 :多生产与多消费
* @Author:ZYJ
* @Date:2019-06-11-11:37
* @Description: 模拟线程间通信
*/
import java.util.ArrayList;
import java.util.List;
/**
* 测试类
*/
public class PrCosDemo {
public static void main(String[] args) {
Goods goods = new Goods();
List<Thread> threadList = new ArrayList<>();
for (int i = 0; i < 10; i++) {
Producer producer = new Producer(goods);
Thread produceThread = new Thread(producer);
produceThread.setName("生产者线程"+i);
threadList.add(produceThread);
}
for(int i=0;i<6;i++){
Consumer consumer = new Consumer(goods);
Thread consumerThread = new Thread(consumer);
consumerThread.setName("消费者线程"+i);
threadList.add(consumerThread);
}
for(Thread thread : threadList){
thread.start();
}
}
}
/**
* 资源类 :
* 设置资源属性与状态
* 创建生产与消费方法
*/
class Goods{
private String goodsName;
private int count;
/**
* 生产方法
* @param goodsName
*/
public synchronized void set(String goodsName) throws InterruptedException {
//有商品 等待消费
while (this.count>0){
wait();
}
this.goodsName=goodsName;
this.count=count+1;
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName());
System.out.println("生产"+toString());
System.out.println("=========================");
//生产完毕 唤醒消费者线程
notifyAll();
}
/**
* 消费方法
*/
public synchronized void get() throws InterruptedException {
//当没有资源时
while (this.count==0){
//等待
wait();
}
//有资源消费
this.count=this.count-1;
Thread.sleep(1000);//模拟网络延迟
System.out.println(Thread.currentThread().getName());
System.out.println("消费"+toString());
System.out.println("===================");
//消费完毕,唤醒生产者线程生产
notifyAll();
}
//覆写toString()方法
@Override
public String toString(){
return "Goods["+goodsName+",库存为:"+count+"]";
}
}
/**
* 生产者线程
*/
class Producer implements Runnable{
private Goods goods;
Producer(Goods goods) {
super();
this.goods = goods;
}
//设置线程任务
@Override
public void run() {
while (true){
try {
//调用生产方法
this.goods.set("mac一台");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
/**
* 消费者线程
*/
class Consumer implements Runnable{
private Goods goods;
Consumer(Goods goods) {
super();
this.goods = goods;
}
@Override
public void run() {
while (true) {
try {
this.goods.get();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}