Java 多线程等待/通知机制
Java多线程 等待/通知机制
文章目录
1、什么是等待通知机制
(1)提出原因
如果某一线程通过while轮询机制来检测某一条件,轮询时间间隔很小,会更浪费CPU资源;如果轮询时间间隔很大,可能会取不到想要的数据,所以就需要一种机制来减少CPU的资源浪费,而且还可以在实现多个线程之间的通信,这就是wait/notify机制的由来。
(2) 什么是等待/通知机制
通俗的说就是:线程A要等待线程B发出通知才执行,这个时候线程A可以执行wait方法,等待线程B执行notify方法唤醒线程A
(3)几个重要实现方法
1、wait()方法:当前执行代码的线程进行等待;将当前线程置入"预执行队列中";并且wait所在的代码行出停止执行,直到接到通知或者被中断为止。(在调用wait()之前,线程必须获得该对象的对象级别锁,即只能在同步方法或者同步代码块中调用该方法)
2、notify()方法:该方法用来通知那些正在等待的线程,如果有多个线程在等待,则线程规划器会随便挑出一个wait状态的线程,对其发出通知notify,执行notify后,当前线程不会立马释放锁,而是等待执行notify()方法的线程将程序执行完,才会释放锁,wait的线程才能获得锁,没有得到锁的线程会继续阻塞在wait状态。
(只能在同步方法或者同步代码块中调用该方法)
3、notifyAll()方法:是所有处于wait状态的线程都别唤起,进入可运行状态。此时,优先级最高的线程最先执行,但也可能是随机执行,这取决于JVM的实现。
2、底层实现原理
理解原理之前先来了解一下java对象模型:
2.1 对象模型
(1)每一个Java类,在被JVM加载的时候,JVM会给这个类创建一个instanceKlass(类信息),保存在方法区,用来在JVM层表示该Java类。
使用new创建一个对象的时候,JVM会在对内存中创建一个instanceOopDesc对象,这个对象中包含了两部分信息,对象头以及实例数据。
(2)对象头中包括两部分:
Mark Word 一些运行时数据,其中就包括和多线程相关的锁的信息。用于存储对象自身的运行时数据,如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程 ID、偏向时间戳等等
Klass Point 元数据其实维护的是指针,指向的是对象所属的类的instanceKlass。
2.2 Moniter(对象监视者)
(1)为了解决线程安全的问题,Java提供了同步机制、互斥锁机制,这个机制保证了在同一时刻只有一个线程能访问共享资源。这个机制的保障来源于监视锁Monitor。
(2)每一个Object对象中内置了一个Monitor对象。(对象头的MarkWord中的LockWord指向monitor的起始地址)
Monitor相当于一个许可证,线程拿到许可证即可以进行操作,没有拿到则需要阻塞等待。
ObjectMonitor中有几个关键属性:
_owner:指向持有ObjectMonitor对象的线程
_WaitSet:存放处于wait状态的线程队列
_EntryList:存放处于等待锁block状态的线程队列
_recursions:锁的重入次数
_count:用来记录该线程获取锁的次数
线程T等待对象锁:_EntryList中加入T
线程T获取对象锁:_EntryList移除T,_owner置为T,计数器_count+1
持有对象锁的线程调用wait():_owner置为T,计数器_count-1,_WaitSet中加入T,等待被唤醒。
2.3 原理
(1)多个线程需要满足同一个对象锁的条件,也就是说被用一个Monitor(监视者)监视者;
(2)持有锁的线程可以从 EntryList队列中 进入到执行状态;
(3)正在执行的线程调用了 wait() 方法后,进入到WaitSet 等待队列中,等待被唤醒;并释放带掉对象锁;
(4)正在执行的线程调用了 notify() 或者 notifyAll() 方法后,通知位于WaitSet队列中的线程,它将释放锁,(继续执行完后代码才真正释放锁)。
(5)被唤醒的线程继续执行。
3、实例:生产者消费者问题
代码演示多个生产者,多个消费者问题
食物类:
public class Value {
public static String value = “”;
}
生产者类:
package ReadAndWriter;
public class Producers implements Runnable{
private Object lock;
public Producers(Object lock){
this.lock = lock;
}
public void setValue() {
try {
synchronized (lock) {
while (!Value.value.equals("")) {
System.out.println("生产者 "
+ Thread.currentThread().getName() + " 等待消费者消费食物");
lock.wait();
}
System.out.println("生产者 " + Thread.currentThread().getName()
+ " 生产食物中");
String value = System.currentTimeMillis() + "_"
+ System.nanoTime();
Value.value = value;
lock.notify();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
public void run() {
while (true){
setValue();
}
}
}
消费者类:
package ReadAndWriter;
public class Customers implements Runnable{
private Object lock;
public Customers(Object lock){
this.lock = lock;
}
public void getValue() {
try {
synchronized (lock) {
while (Value.value.equals("")) {
System.out.println("消费者 "
+ Thread.currentThread().getName() + "没有食物,等待中");
lock.wait();
}
System.out.println("消费者 " + Thread.currentThread().getName()
+ "获得食物");
Value.value = "";
lock.notify();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
public void run() {
while (true){
getValue();
}
}
}
测试类:
package ReadAndWriter;
public class Main {
public static void main(String args[]){
String lock = "lock";
Producers p1 = new Producers(lock);
Producers p2 = new Producers(lock);
Customers c1 = new Customers(lock);
Customers c2 = new Customers(lock);
new Thread(p1).start();
new Thread(p2).start();
new Thread(c1).start();
new Thread(c2).start();
}
}
结果:
从结果可以看出:该线程出现了假死的状态。
3.1 线程假死状态
那么为什么会出现假死的状态呢?
如果这个时候创建多个生产者,多个消费者,如果连续唤醒的是同类线程,那么会出现假死状态,就是线程都处于waiting状态,因为notify随机唤醒一个线程,如果唤醒的同类的,那么就浪费了一次唤醒,如果这个时候无法再唤醒异类线程,那么就会假死。
如图:
4、总结
本片主要就是介绍了什么是java 多线程的等待/通知机制,并简单的描述了实现的底层原理。
(1)等待/通知的实现必须是建立在同一个对象锁基础之上的;
(2)使用时需要注意避免引起假死锁;
上一篇: Java多线程中断机制
下一篇: Java多线程机制1