在虚拟机规范中存在一个wait set(wait set又被称为线程休息室)的概念,至于该wait set是怎样的数据结构,JDK官方并没有给出明确的定义,不同厂家的JDK有着不同的实现方式,甚至相同的JDK厂家不同的版本也存在着差异,但是不管怎样,线程调用了某个对象的wait方法之后都会被加入与该对象monitor关联的wait set中,并且释放monitor的所有权。
若干个线程调用了wait方法之后被加入与monitor关联的wait set中,待另外一个线程调用该monitor的notify方法之后,其中一个线程会从wait set中弹出,至于是随机弹出还是以先进先出的方式弹出,虚拟机规范同样也没有给出强制的要求;而执行notifyAll则不需要考虑哪个线程会被弹出,因为wait set中的所有wait线程都将被弹出
java 官方的一个描述(wait方法)
This method causes the current thread (call it T) to place itself in the wait set for this object and then to relinquish any and all synchronization claims on this object.
1 wait set几点说明
- 所有对象都会由有个wait set,用来存放该对象wait方法之后的进入block状态的线程
- 线程被notify之后,不一定立即执行,需要抢锁
- 线程从wait set被唤醒的顺序不一定是FIFO
- 线程被唤醒后,必须重新获取锁,但是并不是从获取锁的地方执行,而是从wait的地方开始执行,也就说jvm会记录每个线程wait的位置
2 几个测试
2.1 测试:线程从wait set被唤醒的顺序不一定是FIFO
package study.wyy.concurrency.thread.waitset;
import lombok.extern.slf4j.Slf4j;
import java.util.stream.IntStream;
/**
* @author :wyaoyao
* @date : 2020-04-11 10:10
*/
@Slf4j
public class Test1 {
// 定义一个锁
private static final Object LOCK = new Object();
public static void main(String[] args) throws InterruptedException {
// 模拟9个线程,进行wait
IntStream.rangeClosed(1, 9).forEach(i ->
new Thread(String.valueOf(i)) {
@Override
public void run() {
synchronized (LOCK){
try {
log.info("{} will place to wait set",Thread.currentThread().getName());
LOCK.wait();
log.info("{} will leave off wait set",Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start());
// 休眠一会,进行唤醒
Thread.sleep(3000);
IntStream.rangeClosed(1,10).forEach(i->{
synchronized (LOCK){
LOCK.notify();
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
}
输出结果:会发现不是先进先出的顺序
[1] INFO study.wyy.concurrency.thread.waitset.Test1 - 1 will place to wait set
[7] INFO study.wyy.concurrency.thread.waitset.Test1 - 7 will place to wait set
[6] INFO study.wyy.concurrency.thread.waitset.Test1 - 6 will place to wait set
[5] INFO study.wyy.concurrency.thread.waitset.Test1 - 5 will place to wait set
[4] INFO study.wyy.concurrency.thread.waitset.Test1 - 4 will place to wait set
[3] INFO study.wyy.concurrency.thread.waitset.Test1 - 3 will place to wait set
[2] INFO study.wyy.concurrency.thread.waitset.Test1 - 2 will place to wait set
[9] INFO study.wyy.concurrency.thread.waitset.Test1 - 9 will place to wait set
[8] INFO study.wyy.concurrency.thread.waitset.Test1 - 8 will place to wait set
[1] INFO study.wyy.concurrency.thread.waitset.Test1 - 1 will leave off wait set
[6] INFO study.wyy.concurrency.thread.waitset.Test1 - 6 will leave off wait set
[7] INFO study.wyy.concurrency.thread.waitset.Test1 - 7 will leave off wait set
[8] INFO study.wyy.concurrency.thread.waitset.Test1 - 8 will leave off wait set
[9] INFO study.wyy.concurrency.thread.waitset.Test1 - 9 will leave off wait set
[2] INFO study.wyy.concurrency.thread.waitset.Test1 - 2 will leave off wait set
[3] INFO study.wyy.concurrency.thread.waitset.Test1 - 3 will leave off wait set
[4] INFO study.wyy.concurrency.thread.waitset.Test1 - 4 will leave off wait set
[5] INFO study.wyy.concurrency.thread.waitset.Test1 - 5 will leave off wait set
2.2 测试:线程wait唤醒之后,会去抢锁执行,但是执行的时候,还是会从wait的地方开始执行
package study.wyy.concurrency.thread.waitset;
import lombok.extern.slf4j.Slf4j;
/**
* @author :wyaoyao
* @date : 2020-04-11 10:21
*/
@Slf4j
public class Test2 {
// 定义一个锁
private static final Object LOCK = new Object();
private static void work(){
synchronized (LOCK){
log.info("begining ......");
try {
log.info("Thread will coming");
LOCK.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.info("Thread will out");
}
public static void main(String[] args) throws InterruptedException {
new Thread(()->{
work();
}).start();
Thread.sleep(1000);
synchronized (LOCK){
LOCK.notify();
}
}
}
[Thread-0] INFO study.wyy.concurrency.thread.waitset.Test2 - begining ......
[Thread-0] INFO study.wyy.concurrency.thread.waitset.Test2 - Thread will coming
[Thread-0] INFO study.wyy.concurrency.thread.waitset.Test2 - Thread will out
可见线程wait唤醒之后,虽然会去抢锁执行,但是执行的时候,还是会从wait的地方开始执行,没有再次打印
begining …