Thread Signaling

倾然丶 夕夏残阳落幕 提交于 2020-03-02 10:50:02

    本文大概意思都是都下边链接文章转换过来的,没有进行一字一句的翻译,只是把大概意思整里出来。

       http://tutorials.jenkov.com/java-concurrency/thread-signaling.html

     线程信号的目的是为了线程之间相互通信。线程信号可以使线程等待另其他线程信号,例如thread B 或许等待从thread A 发出的数据准备处理信号。


1  Signaling via Shared Objects

    线程之间可以通过共享对象来相互发送信号。Thread A 可以通过在synchronized同步块里设置变量 hasDataToProcess为true ,线程B同样在synchronized同步块里读取hasDataToProcess的值来确定是否有数据可读。

     下面是一个简单的信号对象

 public class MySignal{

  protected boolean hasDataToProcess = false;
  
  public synchronized boolean hasDataToProcess(){
    return this.hasDataToProcess;
  }
  
  public synchronized void setHasDataToProcess(boolean hasData){
    this.hasDataToProcess = hasData;  
  }
}

    thread A 与 thread B 必要通过同一个MySignal 实例来进行通信。如果 A B 引用了不同的MySignal实例,它们之间将不会接收到相互的信号。

2 Busy Wait

    由于thread B 一直在等待是否有数据可以处理,如果采用上面的MySignal实例,那么thread B 必须一直循环调用hasDataToProcess来判断是否有数据处理。这样就会产生忙等,消耗大量的CPU。

3 wait(), notify() and notifyAll()

     忙等除了在平均时间较短的情况下比较有效,其他情况不能有效的利用CPU。如果线程在收到信号之前可以sleep,

  或者变成inactive 状态。java 内嵌了使等待线程变成inactive状态的机制。使用java.lang.Object 上面的wait(),notify(),

  notifyAll()可以实现。当一个线程在任意object 上调用wait(),它会变成inactive状态,直到有其他线程在该object上调用notify(),该线程才会继续执行。为了调用wait 或者notify ,调用线程必须获取在object上的锁。换句话说调用线程必须在同步块里调用wait ,notify,notifyAll。

    下面是改造版的信号类MyWaitNotify    

  public class MyWaitNotify{
	
      Object myMonitorObject = new Object();

      public void doWait(){
         synchronized(myMonitorObject){
          try{
            myMonitorObject.wait();
          } catch(InterruptedException e){...}
        }
      }

      public void doNotify(){
        synchronized(myMonitorObject){
          myMonitorObject.notify();
        }
      }
}

    

    等待线程可以调用doWait,发出通知的线程可以调用doNotify。当一个线程在某个object上调用notify,所有在该object等待的线程,只有一个线程将会被唤醒继续执行代码。如果调用notifyAll可以唤醒该对象上所有等待的线程。所有的等待线程 ,通知线程都必须在synchronized 中调用wait,notify ,notifyAll。这是强制的。一个线程如果没有获取一个object上的锁,将不能调用这几个方法。否则会抛出IllegalMonitorStateException异常。一旦一个线程调用wait ,它将释放它所持有的monitor object上的锁。这样就可以允许其他线程调用同步块中的wait 或者notify 。当一个线程未离开notify同步块之前,唤醒的线程不能退出wait调用块,因为没有获得monitor object 上的锁。

4 Missed Signals

    notify,notifyAll 不保存对它们的方法调用当没有线程等待在该monitor object。notify 信号就丢死了。因此,如果一个线程在调用wait之前调用notify,信号将会被等待线程丢失。这样在一些案例中将导致等待线程一直在等待,不会醒来,因为通知信号被丢失了。为了避免信号的丢失,信号可以被存储在signal类中。

    下面是会存储信号的MyWaitNotify类

public class MyWaitNotify2{

  MonitorObject myMonitorObject = new MonitorObject();
  boolean wasSignalled = false;

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


5 Spurious Wakeups

    虚假唤醒指的是即使线程没有调用监视器对象上的notify或者notifyAll,等待线程就醒了。唤醒可能没有任何原因的发生。如果一个虚假唤醒发生在MyWaitNofiy2中的doWait()中,等待线程在没有接收到一个恰当的信号就开始执行。这会导致严重的后果。下边把MyWaitNotify2中的if 判断改为while循环,只有wasSignalled状态改变,才认为是notify真正的被发出。

public class MyWaitNotify3{

  MonitorObject myMonitorObject = new MonitorObject();
  boolean wasSignalled = false;

  public void doWait(){
    synchronized(myMonitorObject){
      while(!wasSignalled){
        try{
          myMonitorObject.wait();
         } catch(InterruptedException e){...}
      }
      //clear signal and continue running.
      wasSignalled = false;
    }
  }

  public void doNotify(){
    synchronized(myMonitorObject){
      wasSignalled = true;
      myMonitorObject.notify();
    }
  }
}

改成while循环后,即使发生虚假唤醒,如果wasSignalled的值没有被改变,那么线程将再次调用wait(),使线程变为inactive状态。

6 Multiple Threads Waiting for the Same Signals

        while 循环同样在多线程等待的情况中仍是一个很好的解决方案。当监视器上的notifyAll被调用时,只有其中一个线程会被允许继续执行,其他的将会被再次等待,因为其他线程获得锁后,wasSignalled上的信号已经被第一个唤醒的线程擦除掉了。

7 Don't call wait() on constant String's or global objects

        当线程把一个把常量字符串当作监视器对象时,会出现异常。原因是JVM/Compiler内部会把常量字符串转换为同一对象对待。这意味着即使你有两个不同的MyWaitNotify实例,它们里面的监视器对象将会是同一个字符对象。这意味着如果一个线程在第一信号实例调用wait方法,可能将会被另一信号实例上notify的调用唤醒。因此不要把全局对象,string 常量当作监视器对象用。

本文链接: http://my.oschina.net/robinyao/blog/611885

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!