美文网首页知识点多线程
Object的wait和notify/notifyAll

Object的wait和notify/notifyAll

作者: virtual灬zzZ | 来源:发表于2022-01-22 14:28 被阅读0次

    线程之间的交流,可以使用Object超类自带的wait和notify/notifyAll 来实现。它们调用的前提是 获得锁,使用它们的代码/方法需要使用synchronize修饰。

    wait()

    wait()会使当前线程阻塞,然后交出锁给别的线程,自己暂停在wait()处,等重新被唤醒,获得锁之后,再往下执行。如果没有获得锁就直接使用wait()方法,会抛出一个runtimeException,具体是illegalMonitorStateException,非法监视器状态异常,监视器即是锁。wait()的使用是由try..catch..包裹的,catch的是interruptException,证明它是可以被打断的,除了正常的notify(),使用thread.interrput()也能将它唤醒立马跳到catch块中,发生异常就已经是释放锁了。

    notify/notifyAll

    同样的,这两方法也是需要获得锁才能操作,不然还是抛出illegalMonitorStateException。该方法主要是唤醒waiting状态的线程,让出锁,但注意不是立马就交出锁,而是执行完临界区余下的代码才交出锁的。notifyAll则是唤醒所有waitting的线程,让他们都有机会继续争锁。notify()是随机唤醒一个线程,具体哪个还是取决于操作系统多线程的管理,在经典的多生产者-多消费者案例下,notify()唤醒的有可能是自身相同类型的线程,所以这时候必须使用notifyAll()。

    需要注意的是,wait()和notify()/notifyAll(),使用的必须是同一个监视器,不同的监视器是会报错的。

    notify()在注释文档中写着是 随意的,但具体取决于JVM的实现。

    https://www.jianshu.com/p/99f73827c616

    wait/notify消息通知潜在的一些问题

    notify通知过早

    notify 通知的遗漏很容易理解,即 threadA 还没开始 wait 的时候,threadB 已经 notify 了,这样,threadB 通知是没有任何响应的,当 threadB 退出 synchronized 代码块后,threadA 再开始 wait,便会一直阻塞等待,直到被别的线程打断。下面用代码进行模拟:

    public class EarlyNotify {
        private static Object lock = new Object();
    
        public static void main(String[] args) {
            WaitThread waitThread = new WaitThread("waitThread", lock);
            NotifyThread notifyThread = new NotifyThread("notifyThread", lock);
            notifyThread.start();
            try {
                Thread.sleep(20);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            waitThread.start();
        }
    
        static class WaitThread extends Thread {
            private Object lock;
    
            public WaitThread(String name, Object lock) {
                super(name);
                this.lock = lock;
            }
    
            @Override
            public void run() {
                synchronized (lock) {
                    try {
                        System.out.println(Thread.currentThread().getName() + " 获得锁");
                        System.out.println(Thread.currentThread().getName() + " 开始wait");
                        lock.wait();
                        System.out.println(Thread.currentThread().getName() + " 结束wait");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    
        static class NotifyThread extends Thread {
            private Object lock;
    
            public NotifyThread(String name, Object lock) {
                super(name);
                this.lock = lock;
            }
    
            @Override
            public void run() {
                synchronized (lock) {
                    System.out.println(Thread.currentThread().getName() + " 获得锁");
                    System.out.println(Thread.currentThread().getName() + " 开始notify");
                    lock.notify();
                    System.out.println(Thread.currentThread().getName() + " 结束notify");
                }
            }
        }
    }
    
    运行结果:(waitThread 一直在阻塞,程序运行不完)
    notifyThread 获得锁
    notifyThread 开始notify
    notifyThread 结束notify
    waitThread 获得锁
    waitThread 开始wait
    

    上面示例代码中启动了两个线程,一个是WaitThread,另一个是NotifyThread。NotifyThread首先启动,先调用notify方法。然后WaitThread线程才启动,调用wait方法,但是由于通知过了,wait方法就无法再获取到相应的通知,因此WaitThread会一直在wait方法出阻塞,这种现象就是通知过早的现象。

    针对这种现象,解决方法,一般是添加一个状态标志,让waitThread调用wait方法前先判断状态是否已经改变了没,如果通知早已发出的话,WaitThread就不再去wait。对上面的代码进行更正:

    public class EarlyNotify {
        private static Object lock = new Object();
        //sout已经刷新主存
        private static Boolean isWaiting = true;
    
        public static void main(String[] args) {
            WaitThread waitThread = new WaitThread("waitThread", lock);
            NotifyThread notifyThread = new NotifyThread("notifyThread", lock);
            notifyThread.start();
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            waitThread.start();
        }
    
        static class WaitThread extends Thread {
            private Object lock;
    
            public WaitThread(String name, Object lock) {
                super(name);
                this.lock = lock;
            }
    
            @Override
            public void run() {
                synchronized (lock) {
                    while (isWaiting) {
                        try {
                            System.out.println(Thread.currentThread().getName() + " 获得锁");
                            System.out.println(Thread.currentThread().getName() + " 开始wait");
                            lock.wait();
                            System.out.println(Thread.currentThread().getName() + " 结束wait");
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }
    
        static class NotifyThread extends Thread {
            private Object lock;
    
            public NotifyThread(String name, Object lock) {
                super(name);
                this.lock = lock;
            }
    
            @Override
            public void run() {
                synchronized (lock) {
                    System.out.println(Thread.currentThread().getName() + " 获得锁");
                    System.out.println(Thread.currentThread().getName() + " 开始notify");
                    lock.notify();
                    isWaiting = false;
                    System.out.println(Thread.currentThread().getName() + " 结束notify");
                }
            }
        }
    }
    

    这段代码只是增加了一个 isWait 状态变量,NotifyThread调用notify方法后会对状态变量进行更新,在WaitThread中调用wait方法之前会先对状态变量进行判断,在该示例中,调用notify后将状态变量 isWait 改变为false,因此,在WaitThread中while对isWait判断后就不会执行wait方法,从而避免了Notify过早通知造成遗漏的情况。

    在使用线程的等待/通知机制时,一般都要配合一个 boolean 变量值(或者其他能够判断真假的条件),在 notify 之前改变该 boolean 变量的值,让 wait 返回后能够退出 while 循环(一般都要在 wait 方法外围加一层 while 循环,以防止早期通知),或在通知被遗漏后,不会被阻塞在 wait 方法处。这样便保证了程序的正确性。

    在使用线程的等待/通知机制时,一般还都要在 while 循环中调用 wait()方法,因此需要配合使用一个 boolean 变量(或其他能判断真假的条件),满足 while 循环的条件时,进入 while 循环,执行 wait()方法,不满足 while 循环的条件时,跳出循环,执行后面的代码。

    总结

    在Object提供的消息通知机制应该遵循如下这些条件:

    1. 永远在while循环中对条件进行判断而不是if语句中进行wait条件的判断;
    2. 使用NotifyAll而不是使用notify。

    基本的使用范式如下:

    // The standard idiom for calling the wait method in Java
    synchronized (sharedObject) {
       while (condition) {
         sharedObject.wait();
        // (Releases lock, and reacquires on wakeup)
       }
       // do action based upon condition e.g. take or put into queue
    }
    

    相关文章

      网友评论

        本文标题:Object的wait和notify/notifyAll

        本文链接:https://www.haomeiwen.com/subject/qgoshrtx.html