`
春花秋月何时了
  • 浏览: 42459 次
  • 性别: Icon_minigender_1
  • 来自: 成都
社区版块
存档分类
最新评论

synchronized实用之线程通信wait&notify

 
阅读更多

一、Wait/Notify线程通信机制原理

线程通信的目标是使线程间能够互相发送信号。另一方面,线程通信使线程能够等待其他线程的信号。例如,线程B可以等待线程A的一个信号,这个信号会通知线程B数据已经准备好了。

 

Java有一个内建的线程通信机制就是使用wait()notify()notifyAll()。这种通信机制也是synchronized锁机制的实际运用,因为线程必须在同步块里调用wait()或者notify()。

 

一个线程一旦调用了任意对象的wait()方法,它就释放了所持有的监视器对象上的锁,并转为非运行状态。这将允许其他线程也可以调用 wait()或者notify()。 而一旦另一个线程持有了相同的锁之后,通过调用同一个对象的notify()方法将可以随机唤醒一个等待在同一个对象上的阻塞线程。notifyAll()则可以唤醒所有等待在同一个对象上的阻塞线程。

 

一旦一个线程被唤醒,必须等到调用了notify或notifyAll方法的线程退出它自己的同步块释放锁之后,并重新获得监视器对象的锁,才可以退出wait()的方法调用,继续执行wait()代码行后面的代码。如果多个线程被notifyAll()唤醒,那么在同一时刻 将只有一个线程可以退出wait()方法,因为每个线程在退出wait()前必须获得监视器对象的锁。

 二、线程通信时要注意的问题

        1. 唤醒信号丢失

          如果一个线程先于被通知线程调用wait()前调用了notify(),等待的线程将错过这个信号。在某些 情况下,这可能使等待线程永远在等待,不再醒来,因为线程错过了唤醒信号。

          为了避免丢失信号,必须把它们保存在信号类里。示例:

          

public class WaitNotifyTester{
 
  MonitorObject monitorObject = new MonitorObject();
  boolean wasSignalled = false;
 
  public void doWait(){
    synchronized(monitorObject){
      if(!wasSignalled){
        try{
          monitorObject.wait();
         } catch(InterruptedException e){...}
      }
      //clear signal and continue running.
      wasSignalled = false;
    }
  }
 
  public void doNotify(){
    synchronized(monitorObject){
      wasSignalled = true;
      monitorObject.notify();
    }
  }
}

    以上示例中,为了避免唤醒信号丢失, 用一个变量wasSignalled来保存是否被通知过。在notify前,设置自己已经被通知过。在wait前,先检测是否被通知过再决定是否等待通知。在wait后,设置自己没有被通知过,需要等待通知。

       2. 无效的唤醒

           有时候由于莫名其妙的原因,线程有可能在没有调用过notify()和notifyAll()的情况下醒来。这也就是所谓的假唤醒(spurious wakeups)。这可能会在某些时候导致你的应用程序出现严重问题。

           为了防止这种无效的假唤醒,可以通过自旋锁检测通知信号,而不是if表达式。诚然,自旋锁对CPU的消耗非常大,慎用之。

public class WaitNotifyTester{
 
  MonitorObject monitorObject = new MonitorObject();
  boolean wasSignalled = false;
 
  public void doWait(){
    synchronized(monitorObject){
      while(!wasSignalled){
        try{
          monitorObject.wait();
         } catch(InterruptedException e){...}
      }
      //clear signal and continue running.
      wasSignalled = false;
    }
  }
 
  public void doNotify(){
    synchronized(monitorObject){
      wasSignalled = true;
      monitorObject.notify();
    }
  }
}
          通过以上示例中将检测通知信号放在while循环里面,一旦发生假唤醒之后,可以再次判断是否条件满足,如果没有满足则继续调用wait()等待。

 

 

       3. 多个线程被同时唤醒

          如果你有多个线程在等待,被notifyAll()全部唤醒,但只允许一个线程能继续执行,以上while示例也可以解决这种要求:每次只有一个线程能够获得监视器monitorObject对象锁,意味着只有一个线程可以退出wait()调用并清除wasSignalled标志(设为false),而当接下来其他线程进入同步块之后,由于wasSignalled标识已经被第一个线程清除,所以全部继续调用wait回到等待状态,直到下次信号到来。

        4.不要使用字符串常量或全局对象调用wait()

         以下是以空字符串作为锁的同步块的示例:

public class WaitNotifyTester{
 
  String monitorObject = "";
  boolean wasSignalled = false;
 
  public void doWait(){
    synchronized(monitorObject){
      while(!wasSignalled){
        try{
          monitorObject.wait();
         } catch(InterruptedException e){...}
      }
      //clear signal and continue running.
      wasSignalled = false;
    }
  }
 
  public void doNotify(){
    synchronized(monitorObject){
      wasSignalled = true;
      monitorObject.notify();
    }
  }
}

由于字符串常量对象指向的内存地址都是常量池中相同的一块地址,所以就算有两个以上WaitNotifyTester对象实例,其内部的monitorObject监视器对象锁其实都是同一个。这会导致什么问题呢?

假设:

线程AB操作WaitNotifyTester对象实例WaitNotifyTester1,并且线程A正wait在WaitNotifyTester1的monitorObject对象上。 

线程CD操作WaitNotifyTester对象实例WaitNotifyTester2,并且线程C正wait在WaitNotifyTester2的monitorObject对象上。

按照设想,可以通过B唤醒A,通过D唤醒C。但是由于使用了字符串常量作为monitorObject,所以他们持有的其实是相同的锁。也就是说,当B线程执行notify时,可能唤醒的不是A线程而是C线程。这时候,C线程醒来之后,发现是假唤醒(因为wasSignalled保存的是特定WaitNotifyTester实例对象的信号),能够继续等待,这没有问题,但是A线程却错过了对唤醒信号的响应而继续处于等待状态。
如何解决这种情况?
1. 可能我们首先想到的则是将notify()换成notifyAll(),但是这在性能上是个糟糕的解决办法,因为唤醒了不必要的线程,并且这些无效线程在唤醒和继续等待这种线程上下文切换中会消耗大量的CPU资源。
2.  最好的解决办法是不要使用全局对象,字符串常量等。应该使用对应唯一的对象作为监视器对象锁。

 

分享到:
评论

相关推荐

    java之wait,notify的用法([ 详解+实例 ])

    Java之wait和notify的用法详解 在Java多线程编程中,wait和notify是两个非常重要的方法,它们都是Object类的方法,用于线程之间的通信和同步。下面我们将详细解释wait和notify的用法。 wait方法 wait方法是Object...

    浅谈线程通信wait,notify作用

    浅谈线程通信wait、notify作用 在多线程编程中,线程之间的通信非常重要。wait()和notify()方法是Java语言中最基本的线程通信机制。它们可以使得线程之间相互发送信号,从而实现线程之间的协作。 wait()方法的作用...

    Java多线程wait和notify

    在Java中,`wait()` 和 `notify()` 方法是实现线程间通信和协作的重要工具,它们属于 `java.lang.Object` 类,这意味着所有类都默认继承了这两个方法。本文将详细探讨如何使用 `wait()` 和 `notify()` 来控制子线程...

    Java 同步方式 wait和notify/notifyall

    在Java中,`wait()`, `notify()`, 和 `notifyAll()` 是Java Object类的三个方法,它们在实现线程间通信和协作时扮演着关键角色。这些方法主要用于解决线程等待和唤醒的问题,是基于Java Monitor(监视器)模型的。 ...

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

    Java中多线程编程中,wait、notify、notifyAll三个方法是非常重要的,它们都是Object对象的方法,用于线程之间的通信。下面我们将详细介绍这三个方法的使用和作用。 一、wait()方法 wait()方法是使当前线程自动...

    主线程去控制子线程wait与notify

    在Java多线程编程中,`wait()`和`notify()`是两个非常重要的方法,它们用于线程间的协作和通信。这两个方法是Java语言中的Object类提供的,因此所有的对象都可以使用。在本文中,我们将深入探讨如何使用主线程来控制...

    浅谈java多线程wait,notify

    在Java多线程编程中,wait和notify是两个非常重要的机制,用于实现线程之间的通信和同步。在本文中,我们将通过示例代码详细介绍Java多线程wait和notify的使用,帮助读者更好地理解和掌握这两个机制。 wait机制 在...

    wait_notify_demo

    `wait()`、`notify()`和`notifyAll()`是Java中的三个关键字,它们属于Object类的方法,主要用于线程间的通信,尤其在实现生产者消费者模式时发挥着重要作用。本文将深入探讨这些方法以及如何在实际场景中应用它们。 ...

    一个理解wait()与notify()的例子

    这两个方法是Java中实现线程间通信的重要手段之一,尤其在解决生产者消费者模型、读者写者问题等经典同步问题时非常有用。 #### 代码分析 给出的代码示例包括两个类:`ThreadA`和`ThreadB`。`ThreadA`作为主程序...

    Java的sychronized、wait和notify范例

    `synchronized`关键字、`wait()`和`notify()`方法是Java多线程中用于控制并发访问共享资源的重要工具,它们是Java内存模型(JMM)的一部分,主要用于解决线程间的同步问题。 一、`synchronized`关键字 `...

    wait,notify等线程知识.pdf

    在调用wait()之前,线程必须已经获取了对象的锁,即wait()、notify()和notifyAll()都必须在`synchronized`代码块或方法中。 2. **notify()**: notify()方法用于唤醒一个正在等待同一对象监视器锁的线程。如果有多...

    Java 同步锁 wait notify 学习心得

    #### 线程通信 在多线程环境中,线程之间往往需要进行通信,以避免数据竞争或死锁。`wait`和`notify`提供了基本的信号机制,使得线程可以在完成某些操作后通知其他线程。例如,一个线程可能在完成计算任务后调用`...

    多线程通信和等待机制.docx

    其中,wait()和notify()方法是Java语言中实现多线程通信和等待机制的两个核心方法。 wait()方法是Object类的一个方法,用来将当前线程置入“预执行队列”中,并且在wait()所在的代码行处停止执行,直到接到通知或被...

    java中几个notify、wait使用实例

    在Java的多线程编程中,`notify()`与`wait()`是实现线程间通信的重要方法,它们主要用于解决生产者消费者问题、读者写者问题等典型同步问题。这两个方法定义在`Object`类中,因此所有Java对象都可以作为锁来使用。在...

    线程通信安全问题

    在Java编程中,多线程通信是一个至关重要的概念,特别是在并发编程中,它涉及到线程间的协作和数据共享。线程通信安全问题是指在多线程环境下,如何保证多个线程对共享资源进行访问时的正确性和一致性。在这个场景下...

    深入理解Wait、Notify和Wait与sleep区别

    首先,`wait()`, `notify()`和`notifyAll()`是Object类中的方法,它们主要用于线程间通信和协作。这些方法只能在同步环境中(如`synchronized`块或方法)使用,否则会抛出`IllegalMonitorStateException`。它们的...

    如何在Java中正确使用 wait, notify 和 notifyAll

     在 Java 中可以用 wait、notify 和 notifyAll 来实现线程间的通信。。举个例子,如果你的Java程序中有两个线程——即生产者和消费者,那么生产者可以通知消费者,让消费者开始消耗数据,因为队列缓冲区中有内容待...

    多线程通信ThreadDemo

    在Java编程中,多线程通信是一个重要的概念,特别是在并发编程中。`ThreadDemo`示例可能演示了如何在不同的线程之间有效地传递信息。线程通信是解决多个执行流同步和协作的关键,确保数据的一致性和正确性。以下是...

    Java 线程通信示例 源代码

    在Java编程中,多线程通信是一个至关重要的概念,特别是在设计高效的并发应用程序时。这个"Java线程通信示例源代码"很可能包含了演示如何在不同线程之间共享数据和协调执行顺序的实例。线程通信主要涉及两个核心概念...

Global site tag (gtag.js) - Google Analytics