一直搞不懂Java线程通信,这次终于明白了( 三 )

  • 等待池:假设一个线程A调用了某个对象的wait()方法,线程A就会释放该对象的锁,之后进入到了该对象的等待池中

  • 一直搞不懂Java线程通信,这次终于明白了

    文章插图
    对象锁:任何一个对象都可以被当做锁,所以称为对象锁,比如下方代码lock1和lock2就是两把对象锁,都有自己独立的锁池和等待池
    • 调用 lock1.wait() 就是该线程进入到lock1对象锁的等待池中
    • lock1.notify()就是唤醒lock1对象锁的等待池中的随机一个等待线程,lock1.notifyAll(); 就是唤醒该等待池中所有等待线程
    • lock1的锁池和等待池与lock2是独立的,互不影响,并不会唤醒彼此等待池中的线程
    // 锁1private Object lock1 = new Object();// 锁2private Object lock2 = new Object();public void cook() {// 使用lock1对象锁synchronized (lock1) {lock1.wait();}lock1.notify();}调用wait、notify、notifyAll之后线程变化:
    • 如果线程调用了对象的wait()方法,那么线程便会处于该对象的等待池中,等待池中的线程不会去竞争该对象的锁 。
    • 当有线程调用了对象的notifyAll()方法【唤醒所有该对象等待池中的 wait 线程】或 notify()方法【只随机唤醒一个该对象等待池中的 wait 线程】,被唤醒的的线程便会进入该对象的锁池中,锁池中的线程会去竞争该对象锁 。也就是说,调用了notify后只要一个线程会由等待池进入锁池,而notifyAll会将该对象等待池内的所有线程移动到锁池中,等待锁竞争
    • 优先级高的线程竞争到对象锁的概率大,假若某线程没有竞争到该对象锁,它还会留在锁池中,唯有线程再次调用wait()方法,它才会重新回到等待池中 。而竞争到对象锁的线程则继续往下执行,直到执行完了 synchronized 代码块,它会释放掉该对象锁,这时锁池中的线程会继续竞争该对象锁 。
    为什么会死锁呢?
    KitChenRoom中有 cook 和 eat 两个方法都是有同步代码块,并且进入while之后就会调用lock对象锁的wait方法,所以多个调用过cook和eat方法的线程就会进入等待池处于阻塞状态,等待一个正在运行的线程来唤醒它们 。下面分别分析一下使用notify和notifyAll方法唤醒线程的不同之处:
    • 使用notify:notify方法只能唤醒一个线程,其它等待的线程仍然处于wait状态,假设调用cook方法的线程执行完后,所有的线程都处于等待状态,此时又执行了notify方法,这时如果唤醒的仍然是一个调用cook方法的线程【比如爸爸线程 将 妈妈线程唤醒】,那么while循环等于true,则此唤醒的线程【妈妈线程】就会调用wait方法,也会处于等待状态,而且没有唤醒其他线程,那就芭比Q了,此时所有的线程都处于等待状态,就发生了死锁 。
    • 使用notifyAll:可以唤醒所有正在等待该锁的线程,那么所有的线程都会处于运行前的准备状态(就是cook方法执行完后,唤醒了所有等待该锁的线程),那么此时,即使再次唤醒一个调用cook方法的线程,while循环等于true,唤醒的线程再次处于等待状态,那么还会有其它的线程可以获得锁,进入运行状态 。
    解决wait死锁的两种方案:
    • 通过调用notifyAll唤醒所有等待线程
    • 调用 wait(long timeout) 重载方法,设置等待超时时长,在指定时间内还没被唤醒则自动醒来
    下边仍然是调用 notify 唤醒等待池中的一个线程,但是调用wait(long timeout) 超时等待方法,让线程进入等待状态
    public class KitChenRoom {private boolean hasFood = false;private Object lock = new Object();public void cook() {synchronized (lock) {while (hasFood) {try {// 超时等待 2 秒lock.wait(2000);} catch (InterruptedException e) {throw new RuntimeException(e);}}System.out.println(Thread.currentThread().getName() + "没吃的了,给娃做饭!");hasFood = true;lock.notify();}}public void eat() {synchronized (lock) {while (!hasFood) {try {// 超时等待 2 秒lock.wait(2000);} catch (InterruptedException e) {throw new RuntimeException(e);}}System.out.println(Thread.currentThread().getName() + "感谢老妈,恰饭,恰饭");hasFood = false;lock.notify();}}}运行结果:运行三次发现,第一次程序陷入了两次等待2秒之后程序继续执行,这就是超时自动唤醒,避免了死锁


    推荐阅读