文章目录
- 1. 简介
- 2. 相关API
- 3. wait notify的正确姿势
- 4. 总结
1. 简介
回顾Minitor锁的结构:
- Owner线程发现条件不满足,调用wait方法,即可进入WaitSet变为WAITING状态
- BLOCKED和WAITING的线程都处于阻塞状态,不占用CPU时间
- BLOCKED线程会在Owner线程释放锁时唤醒
- WAITING线程会在Owner调用notify或notifyAll时唤醒,但唤醒后并不意味着立刻获得锁,仍需进入EntryList重新竞争
2. 相关API
- Obj.wait():让进入object的Monitor的线程到WaitSet等待
- Obj.wait(long n):有时限的等待,到n毫秒后结束等待,或者在n毫秒内被唤醒
- Obj.notify():在object上正在waitSet等待的线程中挑哟个唤醒
- Obj.notifyAll():让object上正在waitSet等待的线程全部被唤醒
3. wait notify的正确姿势
- sleep(long n)和wait(long n)的区别
- sleep是Thread的静态方法,而wait是Object的方法
- sleep不需要强制和synchronized配合使用,但wait需要
- sleep在睡眠的同时,不会释放对象锁,但wait在等待时会释放对象锁
- step1
思考下面代码:
@Slf4j
public class jvm {
static final Object room=new Object();
static boolean hasCigarette=false;
static boolean hasTakeout=false;
public static void main(String[] args) {
new Thread(()->{
synchronized (room){
log.debug("有烟没?[{}]",hasCigarette);
if(!hasCigarette){
log.debug(("没烟,先歇一会!"));
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
log.debug("有烟没?[{}]",hasCigarette);
if(hasCigarette){
log.debug("开始干活了!");
}
}
},"小南").start();
for (int i = 0; i < 5; i++) {
new Thread(()->{
synchronized (room){
log.debug("可以开始干活了!");
}
},"其它人").start();
}
new Thread(()->{
//synchronized (room){
hasCigarette=true;
log.debug("烟到了哦!");
//}
},"送烟的").start();
}
}
问题是,小南线程在睡眠的时候,其它线程全部在EntryList中阻塞等待,在高并发场景下,这种效率是很低的,所以我们需要改善这种情况。
- Step 2
使用wait-notify解决上面问题
@Slf4j
public class jvm {
static final Object room=new Object();
static boolean hasCigarette=false;
static boolean hasTakeout=false;
public static void main(String[] args) {
new Thread(()->{
synchronized (room){
log.debug("有烟没?[{}]",hasCigarette);
if(!hasCigarette){
try {
log.debug(("没烟,先歇一会!"));
room.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
log.debug("有烟没?[{}]",hasCigarette);
if(hasCigarette){
log.debug("开始干活了!");
}
}
},"小南").start();
for (int i = 0; i < 5; i++) {
new Thread(()->{
synchronized (room){
log.debug("可以开始干活了!");
}
},"其它人").start();
}
new Thread(()->{
synchronized (room){
hasCigarette=true;
log.debug("烟到了哦!");
room.notify();
}
},"送烟的").start();
}
}
上面代码初步解决了前面小南线程在等待烟的时候,所有等待room的线程都会阻塞的问题。但同时也引入了新的问题,假如不止小南线程在等待,还有其它线程在wait,那么送烟线程会错误的唤醒其它线程,而不是指定的小南线程
- Step 3
@Slf4j
public class jvm {
static final Object room=new Object();
static boolean hasCigarette=false;
static boolean hasTakeout=false;
public static void main(String[] args) throws InterruptedException {
new Thread(()->{
synchronized (room){
log.debug("有烟没?[{}]",hasCigarette);
if(!hasCigarette){
try {
log.debug(("没烟,先歇一会!"));
room.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
log.debug("有烟没?[{}]",hasCigarette);
if(hasCigarette){
log.debug("开始干活了!");
}else {
log.debug("这活干不了!");
}
}
},"小南").start();
new Thread(()->{
synchronized (room){
log.debug("外买到了没?[{}]",hasTakeout);
if(!hasTakeout){
try {
log.debug(("没外卖,先歇一会!"));
room.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
log.debug("外买到了没?[{}]",hasTakeout);
if(hasTakeout){
log.debug("开始干活了!");
}else{
log.debug("这活干不了!");
}
}
},"小北").start();
Thread.sleep(1000);
new Thread(()->{
synchronized (room){
hasTakeout=true;
log.debug("外卖到了哦!");
room.notify();
}
},"送外卖的").start();
}
}
从上面结果我们可以看出,外卖的线程唤醒了小南线程,这就出现了虚假唤醒的情况,那么怎么解决这个问题呢,我们使用notifyAll可以解决上面问题。
@Slf4j
public class jvm {
static final Object room=new Object();
static boolean hasCigarette=false;
static boolean hasTakeout=false;
public static void main(String[] args) throws InterruptedException {
new Thread(()->{
synchronized (room){
log.debug("有烟没?[{}]",hasCigarette);
if(!hasCigarette){
try {
log.debug(("没烟,先歇一会!"));
room.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
log.debug("有烟没?[{}]",hasCigarette);
if(hasCigarette){
log.debug("开始干活了!");
}else {
log.debug("这活干不了!");
}
}
},"小南").start();
new Thread(()->{
synchronized (room){
log.debug("外买到了没?[{}]",hasTakeout);
if(!hasTakeout){
try {
log.debug(("没外卖,先歇一会!"));
room.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
log.debug("外买到了没?[{}]",hasTakeout);
if(hasTakeout){
log.debug("开始干活了!");
}else{
log.debug("这活干不了!");
}
}
},"小北").start();
Thread.sleep(1000);
new Thread(()->{
synchronized (room){
hasTakeout=true;
log.debug("外卖到了哦!");
room.notifyAll();
}
},"送外卖的").start();
}
}
虽然我们解决了小北的问题,小南的问题我们还是没有解决,由于小南被外卖线程给唤醒了,但是却没有拿到烟,这是小南线程还是没干活,这就是新出现的问题。
- Step 4
我们之前使用的if判断,如果我们改成while循环就解决了上面问题
@Slf4j
public class jvm {
static final Object room=new Object();
static boolean hasCigarette=false;
static boolean hasTakeout=false;
public static void main(String[] args) throws InterruptedException {
new Thread(()->{
synchronized (room){
log.debug("有烟没?[{}]",hasCigarette);
while(!hasCigarette){
try {
log.debug(("没烟,先歇一会!"));
room.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
log.debug("有烟没?[{}]",hasCigarette);
if(hasCigarette){
log.debug("开始干活了!");
}else {
log.debug("这活干不了!");
}
}
},"小南").start();
new Thread(()->{
synchronized (room){
log.debug("外买到了没?[{}]",hasTakeout);
while(!hasTakeout){
try {
log.debug(("没外卖,先歇一会!"));
room.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
log.debug("外买到了没?[{}]",hasTakeout);
if(hasTakeout){
log.debug("开始干活了!");
}else{
log.debug("这活干不了!");
}
}
},"小北").start();
Thread.sleep(1000);
new Thread(()->{
synchronized (room){
hasTakeout=true;
log.debug("外卖到了哦!");
room.notifyAll();
}
},"送外卖的").start();
}
}
到此为止我们解决了虚假唤醒的问题
4. 总结
我们使用wait-notify的正确姿势应该如下:
synchronized(lock){
while(条件B不成立)
{
lock.wait();
}
//代码逻辑
}
synchronized(lock){
lock.notifyAll();
}