【面试题】notify() 和 notifyAll()方法的使用和区别

【面试题】notify() 和 notifyAll()方法的使用和区别

Java中notify和notifyAll的区别

何时在Java中使用notify和notifyAll?

【问】为什么wait()一定要放在循环中?

Java中通知和notifyAll方法的示例

Java中通知和notify方法的示例 


Java中notify和notifyAll的区别

Java提供了两个方法notifynotifyAll来唤醒在某些条件下等待的线程,但是Java中的notify和notifyAll之间存在细微差别

当我们使用 notify 时,只有一个等待线程会被唤醒而且它不能保证哪个线程会被唤醒,这取决于线程调度器。关于线程调度的算法如下:

当我们使用 notifyAll 时,那么会唤醒所有线程,这些被唤醒的线程会去争抢锁,谁先抢到谁就先执行,其它的就要继续等待!如何实现?在循环中调用 wait 即可!

因此,notify和notifyAll之间的关键区别在于 notify只会唤醒一个线程,而notifyAll方法将唤醒所有线程

何时在Java中使用notify和notifyAll?

如果所有线程都在等待相同的条件,并且一次只有一个线程可以从条件变为true,则使用notify 和 notifyAll都可以

在上述的情况下,notify 性能优于 notifyAll,因为 notify 只需要唤醒一个线程,而 notifyAll 会唤醒所有线程,但是最后只会有一个线程继续工作,其它会继续等待,所有可以说被唤醒的大多数线程“无用”的,这种操作会浪费CPU

虽然这看起来很合理,但仍有一个警告,即无意中的接收者吞下了关键通知。通过使用notifyAll,我们确保所有收件人都会收到通知。

【问】为什么wait()一定要放在循环中?

永远在 while循环里,而不是 if语句下使用 wait()。这样,循环会在线程睡眠前后都检查 wait的条件,并在条件并未改变的情况下,处理唤醒通知。

正确写法: 

synchronized (monitor) {
    //  判断条件谓词是否得到满足
    while(!locked) {
        //  等待唤醒
        monitor.wait();
    }
    //  处理其他的业务逻辑
}

用 if 判断的话,唤醒后线程会从 wait 之后的代码开始运行,但是不会重新判断 if条件,直接继续运行 if 代码块之后的代码;

而如果使用 while 的话,也会从 wait 之后的代码运行,但是唤醒后会重新判断循环条件(重点),如果不成立再执行 while 代码块之后的代码块,成立的话继续 wait

Java中通知和notifyAll方法的示例

代码描述 

public class NotificationTest {

    private volatile boolean go = false;

    public static void main(String args[]) throws InterruptedException {
        NotificationTest test = new NotificationTest();

        Runnable waitTask = new Runnable(){
            @Override
            public void run(){
                try {
                    test.shouldGo();
                } catch (InterruptedException ex) {
                    Logger.getLogger(NotificationTest.class.getName()).
                            log(Level.SEVERE, null, ex);
                }
                System.out.println(Thread.currentThread() + " finished Execution");
            }
        };

        Runnable notifyTask = new Runnable(){
            @Override
            public void run(){
                test.go();
                System.out.println(Thread.currentThread() + " finished Execution");
            }
        };

        Thread t1 = new Thread(waitTask, "WT1");
        Thread t2 = new Thread(waitTask, "WT2");
        Thread t3 = new Thread(waitTask, "WT3");

        Thread t4 = new Thread(notifyTask,"NT1");

        t1.start();
        t2.start();
        t3.start();

        Thread.sleep(200);

        t4.start();
    }

    // waitTask 等待
    private synchronized void shouldGo() throws InterruptedException {
        while(go != true){
            System.out.println(Thread.currentThread()
                    + " is going to wait on this object");
            wait();
            System.out.println(Thread.currentThread() + " is woken up");
        }
        go = false;
    }

    // notifyTask 唤醒
    private synchronized void go() {
        while (go == false){
            System.out.println(Thread.currentThread()
                    + " is going to notify all or one thread waiting on this object");
            go = true;
            notifyAll();
        }
    }
}

运行结果 

分析

最初三个线程WT1,WT2,WT3等待,因为变量go为假,而一个线程NT1将变为真,并通过调用 notifyAll方法通知所有线程,所有线程都将被唤醒

先获得锁的那个线程将会执行结束,并修改go的值为false,这样子另外两个线程(1 和 3)将会继续等待

Java中通知和notify方法的示例 

代码描述 

public class NotificationTest {

    private volatile boolean go = false;

    public static void main(String args[]) throws InterruptedException {
        NotificationTest test = new NotificationTest();

        Runnable waitTask = new Runnable(){
            @Override
            public void run(){
                try {
                    test.shouldGo();
                } catch (InterruptedException ex) {
                    Logger.getLogger(NotificationTest.class.getName()).
                            log(Level.SEVERE, null, ex);
                }
                System.out.println(Thread.currentThread() + " finished Execution");
            }
        };

        Runnable notifyTask = new Runnable(){
            @Override
            public void run(){
                test.go();
                System.out.println(Thread.currentThread() + " finished Execution");
            }
        };

        Thread t1 = new Thread(waitTask, "WT1");
        Thread t2 = new Thread(waitTask, "WT2");
        Thread t3 = new Thread(waitTask, "WT3");

        Thread t4 = new Thread(notifyTask,"NT1");

        t1.start();
        t2.start();
        t3.start();

        Thread.sleep(200);

        t4.start();
    }

    private synchronized void shouldGo() throws InterruptedException {
        while(go != true){
            System.out.println(Thread.currentThread()
                    + " is going to wait on this object");
            wait();
            System.out.println(Thread.currentThread() + " is woken up");
        }
        go = false;
    }

    private synchronized void go() {
        while (go == false){
            System.out.println(Thread.currentThread()
                    + " is going to notify or one thread waiting on this object");
            go = true;
            notify();
        }
    }
}

运行结果 

分析

其实和上面分析的差不多,只不过由于使用的是notify,所以只会唤醒一个线程

posted @   金鳞踏雨  阅读(116)  评论(0编辑  收藏  举报  
相关博文:
阅读排行:
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
历史上的今天:
2022-01-13 Java多线程超详解总结
2022-01-13 Java多线程编程(七)——并发工具类
点击右上角即可分享
微信分享提示