Java multithread wait/notify principle

1. Introduce wait/notify

Review: In the previous study, when we created an object, synchronized to lock the object, JVM will associate a Monitor object to the object header, this Monitor consists of three parts.
One is the Owner object, which stores the thread that created the object.
The other
is EntryList, which is the queue of other blocked threads trying to obtain the resources of the object. The third is WaitSet, which stores the thread that gave up the object lock.
Insert picture description here

  • If the lock object in the Owner thread is found to be unsatisfied, call the wait() method to enter the WaitSet state and change to the WAITING state.
  • Threads under EntryList and WaitSet are blocked and do not occupy CPU time slices
  • The thread under EntryList will be awakened when the Owner releases the lock
  • The thread under WaitSet will be awakened when the Owner thread calls notify or notifyAll, but it does not mean that the lock is obtained immediately after being awakened, and it needs to enter the EntryList to compete for the lock again

2. API introduction

obj.wait() 会让obj对象由拥有锁到暂时放弃锁,进入到waitset中
obj.notify() 会唤醒在waitset中的线程,然后需要进入EntryList重新竞争锁
obj.notifyAll() 会唤醒所有在waitset中的线程,然后需要进入EntryList重新竞争锁

case:

public class TestWaitNotify {
    
    
    final static Object obj = new Object();

    public static void main(String[] args) {
    
    

        new Thread(() -> {
    
    
            synchronized (obj) {
    
    
                log.debug("执行....");
                try {
    
    
                    obj.wait(); // 让线程在obj上一直等待下去
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
                log.debug("其它代码....");
            }
        },"t1").start();

        new Thread(() -> {
    
    
            synchronized (obj) {
    
    
                log.debug("执行....");
                try {
    
    
                    obj.wait(); // 让线程在obj上一直等待下去
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
                log.debug("其它代码....");
            }
        },"t2").start();

        // 主线程两秒后执行
        sleep(2);
        log.debug("唤醒 obj 上其它线程");
        synchronized (obj) {
    
    
//            obj.notify(); // 唤醒obj上一个线程
            obj.notifyAll(); // 唤醒obj上所有等待线程
        }
    }

The difference between wait(long n) and wait() is that the former is a time-limited wait, and enters the EntryList after the time is up, and the latter enters a permanent wait if there is no notify() or notifyAll() wakeup.

3. The correct posture of wait notify

Before we start, let’s take a look at the difference between sleep() and wait() methods.

  • sleep() is a static method of Thread, and wait() method is a method under the Object class
  • The sleep() method does not need to be used in conjunction with synchronized, but the wait() method must be used in conjunction with sysnchronized
  • Call the sleep() method, the object will not release the lock, the object will release the lock when the wait() method is called
  • The same point is that after calling the two methods, the state of the thread is TIMED_WAITING state

case one: Observe the following example, is there anything wrong with it?

@Slf4j(topic = "c.TestWaitNotify")
public class TestWaitNotify {
    
    
    final static Object room = new Object();
    static boolean hasCigarette = false;


    public static void main(String[] args) {
    
    

        //小南
        new Thread(() -> {
    
    
            synchronized (room) {
    
    
                log.debug("有烟没?[{}]", hasCigarette);
                if (!hasCigarette) {
    
    
                    log.debug("没烟,先歇会!");
                    sleep(2);
                }
                log.debug("有烟没?[{}]", hasCigarette);
                if (hasCigarette) {
    
    
                    log.debug("可以开始干活了");
                }
            }
        }, "小南").start();

        //其它5个人
        for (int i = 0; i < 5; i++) {
    
    
            new Thread(() -> {
    
    
                synchronized (room) {
    
    
                    log.debug("可以开始干活了");
                }
            }, "其它人").start();
        }

        //送烟人
        sleep(1);
        new Thread(() -> {
    
    
            // 这里能不能加 synchronized (room)?
            hasCigarette = true;
            log.debug("烟到了噢!");
        }, "送烟的").start();
    }
}

Output:
Insert picture description here

Irregularities:

1. 在小南线程睡咩后,由于sleep()方法没有释放锁,导致其余5个人和送烟人都无法执行代码
2. 送烟人已经送到了,小南还在睡
3. 其它干活的线程,都要一直阻塞,效率太低

Solution: use wait()/notify() method

case two: improve case one

public class TestWaitNotify {
    
    
    final static Object room = new Object();
    static boolean hasCigarette = false;


    public static void main(String[] args) {
    
    

        //小南
        new Thread(() -> {
    
    
            synchronized (room) {
    
    
                log.debug("有烟没?[{}]", hasCigarette);
                if (!hasCigarette) {
    
    
                    log.debug("没烟,先歇会!");
                    try {
    
    
                        room.wait(20000);
                    } catch (InterruptedException e) {
    
    
                        e.printStackTrace();
                    }
                }
                log.debug("有烟没?[{}]", hasCigarette);
                if (hasCigarette) {
    
    
                    log.debug("可以开始干活了");
                }
            }
        }, "小南").start();

        //其它5个人
        for (int i = 0; i < 5; i++) {
    
    
            new Thread(() -> {
    
    
                synchronized (room) {
    
    
                    log.debug("可以开始干活了");
                }
            }, "其它人").start();
        }

        //送烟人
        sleep(1);
        new Thread(() -> {
    
    
            // 这里能不能加 synchronized (room)?
            synchronized (room){
    
    
                hasCigarette = true;
                room.notify();
                log.debug("烟到了噢!");
            }

        }, "送烟的").start();
    }
}

Output:
Insert picture description here
Using the wait()/notify() method does solve the problem of case one, but it also has drawbacks. For example, what if there are multiple waiters?

case three: introducing multiple waiters

public class TestWaitNotify {
    
    
    final static 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 {
    
    
                        room.wait();
                    } catch (InterruptedException e) {
    
    
                        e.printStackTrace();
                    }
                }
                log.debug("有烟没?[{}]", hasCigarette);
                if (hasCigarette) {
    
    
                    log.debug("可以开始干活了");
                }
            }
        }, "小南").start();

        //小女等外卖
        new Thread(() -> {
    
    
            synchronized (room) {
    
    
                Thread thread = Thread.currentThread();
                log.debug("外卖送到没?[{}]", hasTakeout);
                if (!hasTakeout) {
    
    
                    log.debug("没外卖,先歇会!");
                    try {
    
    
                        room.wait();
                    } catch (InterruptedException e) {
    
    
                        e.printStackTrace();
                    }
                }
                log.debug("外卖送到没?[{}]", hasTakeout);
                if (hasTakeout) {
    
    
                    log.debug("可以开始干活了");
                } else {
    
    
                    log.debug("没干成活...");
                }
            }
        }, "小女").start();

        //送外卖
        sleep(1);
        new Thread(() -> {
    
    
            synchronized (room) {
    
    
                hasTakeout = true;
                log.debug("外卖到了噢!");
                room.notify();
            }
        }, "送外卖的").start();
    }
}

Output: There is
Insert picture description here
a special situation, that is, when I deliver the food room.notify();, it is Xiaonan who is waiting for a cigarette. This is also an abnormal phenomenon. The problem is that the two threads have the room object and entered the waitset. At this time, it is not. Know which one will be awakened.

But if you use the notifyAll() method to wake up, it will wake up two threads Xiao Nan, Xiao Nan. At this time, this is not in line with our expectations. We only need Xiao Nan, no need to wake up Xiao Nan!

The final solution:

public class TestWaitNotify {
    
    
    final static 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);
                while (!hasCigarette) {
    
    
                    log.debug("没烟,先歇会!");
                    try {
    
    
                        room.wait();
                    } catch (InterruptedException e) {
    
    
                        e.printStackTrace();
                    }
                }
                log.debug("有烟没?[{}]", hasCigarette);
                if (hasCigarette) {
    
    
                    log.debug("可以开始干活了");
                }
            }
        }, "小南").start();

        //小女等外卖
        new Thread(() -> {
    
    
            synchronized (room) {
    
    
                Thread thread = Thread.currentThread();
                log.debug("外卖送到没?[{}]", hasTakeout);
                if (!hasTakeout) {
    
    
                    log.debug("没外卖,先歇会!");
                    try {
    
    
                        room.wait();
                    } catch (InterruptedException e) {
    
    
                        e.printStackTrace();
                    }
                }
                log.debug("外卖送到没?[{}]", hasTakeout);
                if (hasTakeout) {
    
    
                    log.debug("可以开始干活了");
                } else {
    
    
                    log.debug("没干成活...");
                }
            }
        }, "小女").start();

        //送外卖
        sleep(1);
        new Thread(() -> {
    
    
            synchronized (room) {
    
    
                hasTakeout = true;
                log.debug("外卖到了噢!");
                room.notifyAll();
            }
        }, "送外卖的").start();
    }
}

Output:
Insert picture description here

The thread that will not be awakened is processed by while() in the outer layer of wait(), but meeting certain conditions indicates that the thread needs to be awakened. At this time, while() can exit, which is in line with our expectations.

synchronized(lock) {
    
    
 while(条件不成立) {
    
    
 lock.wait();
 }
 // 干活
}
//另一个线程
synchronized(lock) {
    
    
 lock.notifyAll();
}

Learning materials: https://www.bilibili.com/video/BV16J411h7Rd?p=96

Guess you like

Origin blog.csdn.net/JAYU_37/article/details/113435102