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

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

  • 他们之间有啥区别?
  • 如果我使用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!
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来实现)

   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监视器方法(waitnotifynotifyAll)为不同的对象,以得到具有多个等待集的每个对象,通过将它们与使用任意的相结合的效果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关键字。

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

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内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Java的wait(), notify()和notifyAll()使用心得

    wait(),notify()和notifyAll()都是java.lang.Object的方法:wait(): Causes the current thread to wait until another thread invokes the notify() method or the notifyAll() method for this object.notify(): Wakes up a single thread that is waiting on this object's

  • Java 中Object的wait() notify() notifyAll()方法使用

    Java 中Object的wait() notify() notifyAll()方法使用 一.前言 对于并发编程而言,除了Thread以外,对Object对象的wati和notify对象也应该深入了解其用法,虽然知识点不多. 二.线程安全基本知识 首先应该记住以下基本点,先背下来也无妨: 同一时间一个锁只能被一个线程持有 调用对象的wait()和notify()前必须持有它 三.wait()和notify()理解 3.1 wait()和notify()方法简介 wait()和notify()都是

  • java多线程之wait(),notify(),notifyAll()的详解分析

    wait(),notify(),notifyAll()不属于Thread类,而是属于Object基础类,也就是说每个对象都有wait(),notify(),notifyAll()的功能.因为每个对象都有锁,锁是每个对象的基础,当然操作锁的方法也是最基础了. wait导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或被其他线程中断.wait只能由持有对像锁的线程来调用. notify唤醒在此对象监视器上等待的单个线程.如果所有线程都在此对象上等

  • Java中的notyfy()和notifyAll()的本质区别

    wait()方法表示,放弃当前对资源的占有权,等啊等啊,一直等到有人通知我,我才会运行后面的代码. notify()方法表示,当前的线程已经放弃对资源的占有, 通知等待的线程来获得对资源的占有权,但是只有一个线程能够从wait状态中恢复, 然后继续运行wait()后面的语句: notifyAll()方法表示,当前的线程已经放弃对资源的占有, 通知所有的等待线程从wait()方法后的语句开始运行. 读出什么区别没有? 上例子,先是一个nofiyAll()的例子: Java代码 package co

  • java notify和notifyAll的对比

     java notify和notifyAll 首先从名字可以了解,notify是通知一个线程获取锁,notifyAll是通知所有相关的线程去竞争锁. notify不能保证获得锁的线程,真正需要锁,并且可能产生死锁. 举例1: 所有人(消费者线程)准备吃饭,食堂没有开放(没有释放锁)打饭窗口(锁),所有人等待(WAITING). 食堂开饭打饭窗口(释放锁),并广播消息"开饭了"(notifyAll),所有人竞争排队,并等待吃饭(BLOCKED).每一个人依次在打饭窗口(获得锁)打饭(RU

  • Java多线程中wait、notify、notifyAll使用详解

    基础知识 首先我们需要知道,这几个都是Object对象的方法.换言之,Java中所有的对象都有这些方法. public final native void notify(); public final native void notifyAll(); public final native void wait(long timeout) throws InterruptedException; public final void wait() throws InterruptedExceptio

  • Java中notify()和notifyAll()的使用区别

    notify() 和 notifyAll() 有什么区别? 先解释两个概念. 等待池:假设一个线程A调用了某个对象的wait()方法,线程A就会释放该对象的锁后,进入到了该对象的等待池,等待池中的线程不会去竞争该对象的锁. 锁池:只有获取了对象的锁,线程才能执行对象的 synchronized 代码,对象的锁每次只有一个线程可以获得,其他线程只能在锁池中等待 然后再来说notify和notifyAll的区别 如果线程调用了对象的 wait()方法,那么线程便会处于该对象的等待池中,等待池中的线程

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

    提几个问题,从问题中去了解去学习: 他们之间有啥区别? 如果我使用notify(),将通知哪个线程? 我怎么知道有多少线程在等待,所以我可以使用notifyAll()? 如何调用notify()? 什么是这些线程等待被通知等? 我给点建议:建议使用jdk8里的lock包 java.util.concurrent.locks下的Condition 他可以支持唤醒指定的线程. 他只是一个接口 具体实现类是在AbstractQueuedSynchronizer 也就是AQS框架里的 你可以自己继承他

  • 基于Java多线程notify与notifyall的区别分析

    当一个线程进入wait之后,就必须等其他线程notify/notifyall,使用notifyall,可以唤醒所有处于wait状态的线程,使其重新进入锁的争夺队列中,而notify只能唤醒一个.注意,任何时候只有一个线程可以获得锁,也就是说只有一个线程可以运行synchronized 中的代码,notifyall只是让处于wait的线程重新拥有锁的争夺权,但是只会有一个获得锁并执行. 那么notify和notifyall在效果上又什么实质区别呢?主要的效果区别是notify用得不好容易导致死锁,

  • Java notify和notifyAll的区别和相同

    经常在往上逛,关于在java中notify和notifyAll,经常有人有以下的说法: notify只会通知一个在等待的对象,而notifyAll会通知所有在等待的对象,并且所有对象都会继续运行 并且,好像都有例子可以证明.上面的说法,可以说对,也可以说不对.究其原因,在于其中有一点很关键,官方的说法如下所示: wait,notify,notifyAll: 此方法只应由作为此对象监视器的所有者的线程来调用.通过以下三种方法之一,线程可以成为此对象监视器的所有者 : 通过执行此对象的同步实例方法.

  • 详解Java中wait和sleep的区别

    1.概述 在这篇简短的文章中,我们将看一下核心Java 中的标准sleep()和wait()方法,并了解它们之间的差异和相似之处. 2. wait和sleep之间的一般差异 简单地说,wait()是一个用于线程同步的实例方法. 它可以在任何对象上调用,因为它在java.lang.Object上定义,但它只能从synchronized块中调用.它释放对象的锁定,以便另一个线程可以跳入并获取锁. 另一方面,Thread.sleep()是一个可以从任何上下文调用的静态方法.Thread.sleep()

  • Java中wait与sleep的区别讲解(wait有参及无参区别)

    目录 1. wait() 与wait( long timeout ) 区别 2. wait(0) 与 sleep(0)区别 3. wait 和sleep 释放代码 4. wait 与 sleep 区别 相同点: 不同点: 1. wait() 与wait( long timeout ) 区别 public class WaitDemo4 { public static void main(String[] args) { Object lock = new Object(); Object loc

  • 详解Java中Callable和Future的区别

    目录 Java中为什么需要Callable Callable和Runnable的区别 Future和RunnableFuture 不使用Callable和Future,仅使用Runnable实现相同功能 Java中为什么需要Callable 在java中有两种创建线程的方法: 一种是继承Thread类,重写run方法: public class TestMain { public static void main(String[] args) { MyThread t1 = new MyThre

  • 详谈Java中instanceof和isInstance的区别

    一次性搞定instanceof和isInstance,instanceof和isInstance长的非常像,用法也很类似,先看看这两个的用法: obj.instanceof(class) 也就是说这个对象是不是这种类型, 1. 一个对象是本身类的一个对象 2. 一个对象是本身类父类(父类的父类)和接口(接口的接口)的一个对象 3. 所有对象都是Object 4. 凡是null有关的都是false  null.instanceof(class) class.inInstance(obj) 这个对象

  • 浅谈Java中Collection和Collections的区别

    1.java.util.Collection 是一个集合接口.它提供了对集合对象进行基本操作的通用接口方法.Collection接口在Java 类库中有很多具体的实现.Collection接口的意义是为各种具体的集合提供了最大化的统一操作方式. Collection ├List │├LinkedList │├ArrayList │└Vector │ └Stack └Set 2.java.util.Collections 是一个包装类.它包含有各种有关集合操作的静态多态方法.此类不能实例化,就像一

  • Java中HashSet和HashMap的区别_动力节点Java学院整理

    什么是HashSet? HashSet实现了Set接口,它不允许集合中有重复的值,当我们提到HashSet时,第一件事情就是在将对象存储在HashSet之前,要先确保对象重写equals()和hashCode()方法,这样才能比较对象的值是否相等,以确保set中没有储存相等的对象.如果我们没有重写这两个方法,将会使用这个方法的默认实现.. public boolean add(Object o)方法用来在Set中添加元素,当元素值重复时则会立即返回false,如果成功添加的话会返回true. 什

随机推荐