服务器之家

服务器之家 > 正文

Java中notify和notifyAll的区别及何时使用

时间:2021-12-22 13:22     来源/作者:angerYang

提几个问题,从问题中去了解去学习:

  • 他们之间有啥区别?
  • 如果我使用notify(),将通知哪个线程?
  • 我怎么知道有多少线程在等待,所以我可以使用notifyAll()?
  • 如何调用notify()?
  • 什么是这些线程等待被通知等?

我给点建议:建议使用jdk8里的lock包

  • java.util.concurrent.locks下的Condition 他可以支持唤醒指定的线程。
  • 他只是一个接口 具体实现类是在AbstractQueuedSynchronizer 也就是AQS框架里的 你可以自己继承他 或者使用 ReentrantLock里的newConditon()方法来获取

解决下问题:

Java中notify和notifyAll的区别

Java提供了两个方法notify和notifyAll来唤醒在某些条件下等待的线程,你可以使用它们中的任何一个,但是Java中的notify和notifyAll之间存在细微差别,这使得它成为Java中流行的多线程面试问题之一。当你调用notify时,只有一个等待线程会被唤醒而且它不能保证哪个线程会被唤醒,这取决于线程调度器。虽然如果你调用notifyAll方法,那么等待该锁的所有线程都会被唤醒,但是在执行剩余的代码之前,所有被唤醒的线程都将争夺锁定,这就是为什么在循环上调用wait,因为如果多个线程被唤醒,那么线程是将获得锁定将首先执行,它可能会重置等待条件,这将迫使后续线程等待。因此,notify和notifyAll之间的关键区别在于notify()只会唤醒一个线程,而notifyAll方法将唤醒所有线程。

何时在Java中使用notify和notifyAll

  • 如果所有线程都在等待相同的条件,并且一次只有一个线程可以从条件变为true,则可以使用notify over notifyAll。
  • 在这种情况下,notify是优于notifyAll 因为唤醒所有这些因为我们知道只有一个线程会受益而所有其他线程将再次等待,所以调用notifyAll方法只是浪费CPU。
  • 虽然这看起来很合理,但仍有一个警告,即无意中的接收者吞下了关键通知。通过使用notifyAll,我们确保所有收件人都会收到通知

Java中通知和notifyAll方法的示例(后序demo示例代码 )

  • 我已经汇总了一个示例来说明当我们在Java中调用notifyAll方法时如何通知所有线程,并且当我们在Java中调用notify方法时,只有一个Thread会被唤醒。
  • 在这个例子中,如果boolean变量go为false,则三个线程将等待,记住boolean go是一个volatile变量,以便所有线程都能看到它的更新值。
  • 最初三个线程WT1,WT2,WT3将等待,因为变量go为假,而一个线程NT1将变为真,并通过调用notifyAll方法通知所有线程,或通过调用notify()方法通知一个线程。在notify()调用的情况下,无法保证哪个线程会被唤醒,您可以通过多次运行此Java程序来查看它。
  • 在notifyAll的情况下,所有线程都将被唤醒,但是它们将竞争监视器或锁定,并且将首先获得锁定的线程将完成其执行并且重置为false将迫使其他两个线程仍在等待。在该程序结束时,将有两个线程在等待,两个线程包括通知线程完成。程序不会终止,因为其他两个线程仍在等待,并且它们不是守护程序线程。
  • 实例代码如下:以下是如何在Java中使用notify和notifyAll方法的完整代码示例。在解释了何时使用notify vs notifyAll方法,这个例子将阐明在Java中调用notify和notifyAll方法的效果。go!
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
import java.util.logging.Level;
import java.util.logging.Logger;
 
/**
 * Java程序演示如何在Java和Java中使用notify和notifyAll方法
  *如何通知和notifyAll方法通知线程,哪个线程被唤醒等。
 */
public class NotificationTest {
 
    private volatile boolean go = false;
 
    public static void main(String args[]) throws InterruptedException {
        final 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"); //will wait
        Thread t2 = new Thread(waitTask, "WT2"); //will wait
        Thread t3 = new Thread(waitTask, "WT3"); //will wait
        Thread t4 = new Thread(notifyTask,"NT1"); //will notify
      
        //starting all waiting thread
        t1.start();
        t2.start();
        t3.start();
      
        //pause to ensure all waiting thread started successfully
        Thread.sleep(200);
      
        //starting notifying thread
        t4.start();
      
    }
    /*
     * wait and notify can only be called from synchronized method or bock
     */
    private synchronized void shouldGo() throws InterruptedException {
        while(go != true){
            System.out.println(Thread.currentThread()
                         + " is going to wait on this object");
            wait(); //release lock and reacquires on wakeup
            System.out.println(Thread.currentThread() + " is woken up");
        }
        go = false; //resetting condition
    }
  
    /*
     * both shouldGo() and go() are locked on current object referenced by "this" keyword
     */
    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; //making condition true for waiting thread
            //notify(); // only one out of three waiting thread WT1, WT2,WT3 will woke up
            notifyAll(); // all waiting thread  WT1, WT2,WT3 will woke up
        }
      
    }
}

使用notify时的输出
Thread[WT1,5,main] is going to wait on this object
Thread[WT3,5,main] is going to wait on this object
Thread[WT2,5,main] is going to wait on this object
Thread[NT1,5,main] is going to notify all or one thread waiting on this object
Thread[WT1,5,main] is woken up
Thread[NT1,5,main] finished Execution
Thread[WT1,5,main] finished Execution

使用notifyAll时的输出
Thread[WT1,5,main] is going to wait on this object
Thread[WT3,5,main] is going to wait on this object
Thread[WT2,5,main] is going to wait on this object
Thread[NT1,5,main] is going to notify all or one thread waiting on this object
Thread[WT2,5,main] is woken up
Thread[NT1,5,main] finished Execution
Thread[WT3,5,main] is woken up
Thread[WT3,5,main] is going to wait on this object
Thread[WT2,5,main] finished Execution
Thread[WT1,5,main] is woken up
Thread[WT1,5,main] is going to wait on this object

强烈建议运行这个Java程序并理解它产生的输出并尝试理解它。除了死锁,竞争条件和线程安全之外,线程间通信是Java中并发编程的基础之一。

总结:

1)如果我使用notify(),将通知哪个线程?
无法保证,ThreadScheduler将从等待该监视器上的线程的池中选择一个随机线程。保证只有一个线程会被通知:(随机性)

2) 我怎么知道有多少线程在等待,所以我可以使用notifyAll()?
它取决于程序逻辑,在编码时需要考虑一段代码是否可以由多个线程运行。理解线程间通信的一个很好的例子是在Java中实现生产者 - 消费者模式。

3) 如何调用notify()?
Wait()和notify()方法只能从synchronized方法或块中调用,需要在其他线程正在等待的对象上调用notify方法。

4) 什么是这些线程等待被通知等?
线程等待某些条件,例如在生产者 - 消费者问题中,如果共享队列已满,则生产者线程等待,如果共享队列为空,则生成者线程等待。由于多个线程正在使用共享资源,因此它们使用wait和notify方法相互通信。

这就是Java中的notify和notifyAll方法之间的区别以及何时在Java中使用notify vs notifyAll。现在,应该能够理解并使用notify和notifyAll方法在Java程序中进行线程间通信。

补充下建议里的:lock包下的condition (demo里 是典型的生产者消费者模式》》》 使用的是condition来实现)

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
   final Lock lock = new ReentrantLock();
   //定义2组condition 对应生产者消费者
   final Condition notFull  = lock.newCondition();
  
   final Condition notEmpty = lock.newCondition();
 
   final Object[] items = new Object[100];
   int putptr, takeptr, count;
    //在put的时候 当数组已经满了的情况下 我让线程等待 不在容纳数据 当消费者已经消费了 触发了、、
 
//notfull.signal() 这时候通知生产者 我这变已经消费了 你那边可以试试了哈。
   public void put(Object x) throws InterruptedException {
     lock.lock();
     try {
       while (count == items.length)
         notFull.await();
       items[putptr] = x;
       if (++putptr == items.length) putptr = 0;
       ++count;
       notEmpty.signal();
     } finally {
       lock.unlock();
     }
   }
//同上 相反的理解就是了。。。
   public Object take() throws InterruptedException {
     lock.lock();
     try {
       while (count == 0)
         notEmpty.await();
       Object x = items[takeptr];
       if (++takeptr == items.length) takeptr = 0;
       --count;
       notFull.signal();
       return x;
     } finally {
       lock.unlock();
     }
   }

 Condition因素出Object监视器方法(wait,notify 和notifyAll)为不同的对象,以得到具有多个等待集的每个对象,通过将它们与使用任意的相结合的效果Lock的实施方式。如果Lock替换synchronized方法和语句Condition的使用,则替换Object监视方法的使用。

条件(也称为条件队列或 条件变量)为一个线程提供暂停执行(“等待”)的手段,直到另一个线程通知某个状态条件现在可能为真。由于对此共享状态信息的访问发生在不同的线程中,因此必须对其进行保护,因此某种形式的锁定与该条件相关联。等待条件提供的关键属性是它以原子方式释放关联的锁并挂起当前线程,就像它一样Object.wait。

一个Condition实例本质上绑定到一个锁。要获取Condition特定Lock 实例的实例,请使用其newCondition()方法。

举个例子,假设我们有一个支持put和take方法的有界缓冲区 。如果take在空缓冲区上尝试a ,则线程将阻塞直到某个项可用; 如果put在完整缓冲区上尝试a,则线程将阻塞,直到空间可用。我们希望 在单独的等待集中保持等待put线程和take线程,以便我们可以使用仅在缓冲区中的项或空间可用时通知单个线程的优化。

也就是说 可以创建多个condition 每组condition 对应你的具体的线程操作 当你

notFull.signalAll();的时候 你唤醒的也只是你这组condition里的等待线程 对于不在这组里的notEmpty是没有任何影响的
现在 你是不是可以随心所欲的唤醒你想唤醒的线程了?

补充:关于notify() 和notifyAll() 一个需要注意的地方

notify() 和 notifyAll()都是唤醒其他正在等待同一个对象锁的线程。

下面是我遇到的一个问题,记下来,免得忘了。

直接上代码,有错误的代码:

代码描述:有一个Caculate类,类中又一个成员变量 j,现在有多个线程对这个变量进行操作。一个增加操作、一个减少操作。增加操作:当 j = 0 时,j++ 。减少操作:当 j = 1 时,j-- 。这两个操作分别对应这 add()方法 和sub()方法,都使用synchronized关键字。

可以直接复制拿来运行一下

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
package com.zcd2;
 
public class ThreadTest1
{
    public static void main(String[] args)
    {
        Caculate caculate = new Caculate();
        
        //使用多个线程对实例caculate进行增加操作。
        for(int i = 0; i < 10; i++)
        {
            Thread1 t = new Thread1(caculate);
            t.start();
        }
        
        //使用多个线程对实例caculate进行减少操作。
        for(int i = 0; i < 2; i++)
        {
            Thread2 t = new Thread2(caculate);
            t.start();
        }
    }
}
 
//Thread1线程进行增加操作
class Thread1 extends Thread
{
    private Caculate caculate;
    
    public Thread1()
    {
        
    }   
    
    public Thread1(Caculate caculate)
    {
        this.caculate = caculate;
    }
    
    @Override
    public void run()
    {
        int i = 0;
 
        //死循环,手动停止
        while(true)
        {
            try
            {
                caculate.add();
            } catch (InterruptedException e)
            {
                e.printStackTrace();
            }
            i++;
            System.out.println("加线程执行第 " + i + " 次");
        }
    }   
}
 
//Thread2进行减少操作。
class Thread2 extends Thread
{   
    private Caculate caculate;
    
    public Thread2()
    {
    }
 
    public Thread2(Caculate caculate)
    {
        this.caculate = caculate;
    }
 
    @Override
    public void run()
    {   
        int i = 0;
        
        //死循环,手动停止
        while(true)
        {
            try
            {
                caculate.sub();
            } catch (InterruptedException e)
            {
                e.printStackTrace();
            }
            i++;
            System.out.println("减线程执行第 " + i + " 次");
        }
    }   
}
 
//
class Caculate
{
    private int j = 0;   
 
    //增加操作
    public synchronized void add() throws InterruptedException
    {
     //当 j = 1 的时候说明不符合操作条件,要放弃对象锁。
        while(j == 1)
        {
            wait();
            System.out.println();
        }   
        j++;
        System.out.println(j);
        notify();
    }
 
    //减少操作
    public synchronized void sub() throws InterruptedException
    {
     //当j = 0 的时候说明不符合操作条件,放弃对象锁
        while(j == 0)
        {
            wait();
            System.out.println();
        }
        j--;
        System.out.println(j);
        notify();
    }
}

以上代码并不能一直循环执行,按道理说应该是一直循环执行的。

为什么呢????????

这就涉及到了notify() 和 notifyAll()的其中一个区别了。

这个区别就是:调用 notify() 方法只能随机唤醒一个线程,调用notifyAll() 方法的唤醒所有的等待的线程。

比如这里,当一个线程在正常执行。。。假设这里正常执行完一个增加操作的线程,然后调用 notify() 方法 那么它会随机唤醒一个线程。

  ①、如果唤醒的是进行减少操作的线程,此时 j = 1,线程能够正常执行减少操作。

  ②、如果唤醒的是进行增加操作的线程,此时 j = 1,那么不符合增加操作的条件,他就会调用 wait() 方法。那么调用完wait()方法后程序就会发现已经没有被唤醒的线程了。唯一一个被唤醒的线程因不符合条件放弃了对象锁,其他线程又没有被唤醒。此时程序只能一直等到其他线程被唤醒,但是它等不到了。

解决:

把notify() 改成notifyAll() 这个问题就解决了。因为如果唤醒一个线程,但是这个线程因不符合执行条件而放弃对象,还有很多唤醒的线程。

所以,当多个(两个以上的)线程操作同一个对象的时候最好使用的notifyAll(),这样就不会出现上述的问题了。

发现一个问题,既然使用notify()会出问题那为什么不在每个地方的使用notifyAll()呢??这二者还有其他我没了解的区别吗???难道使用notifyAll() 会使性能大大下降???有待解决。

到此这篇关于Java中notify和notifyAll的区别及何时使用的文章就介绍到这了,更多相关Java notify和notifyAll内容请搜索服务器之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持服务器之家! 

原文链接:https://www.cnblogs.com/GooPolaris/p/7920343.html

标签:

相关文章

热门资讯

yue是什么意思 网络流行语yue了是什么梗
yue是什么意思 网络流行语yue了是什么梗 2020-10-11
背刺什么意思 网络词语背刺是什么梗
背刺什么意思 网络词语背刺是什么梗 2020-05-22
2020微信伤感网名听哭了 让对方看到心疼的伤感网名大全
2020微信伤感网名听哭了 让对方看到心疼的伤感网名大全 2019-12-26
2021年耽改剧名单 2021要播出的59部耽改剧列表
2021年耽改剧名单 2021要播出的59部耽改剧列表 2021-03-05
蜘蛛侠3英雄无归3正片免费播放 蜘蛛侠3在线观看免费高清完整
蜘蛛侠3英雄无归3正片免费播放 蜘蛛侠3在线观看免费高清完整 2021-08-24
返回顶部