Java线程通信

isen
isen
发布于 2023-09-26 / 32 阅读 / 0 评论 / 0 点赞

Java线程通信

Thread Signaling

笔记:

1、通过共享对象通信

public class MySignal{

  protected boolean hasDataToProcess = false;

  public synchronized boolean hasDataToProcess(){
    return this.hasDataToProcess;
  }

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

}

通信的线程必须要获取指向同一个MySignal共享实例的引用。

2、忙等待

protected MySignal sharedSignal = ...

...

while(!sharedSignal.hasDataToProcess()){
  //do nothing... busy waiting
}

问题:忙等待会浪费cpu,除非等待的时间很少,否则应该让等待的线程进入睡眠或者非运行状态。

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

public class MonitorObject{
}

public class MyWaitNotify{

  MonitorObject myMonitorObject = new MonitorObject();

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

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

问题:如果doNotify()在doWait()之前调用,会丢失信号,等待的线程无法收到信号。应该保存可能丢失的信号。

4、Missed Signals

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();
    }
  }
}

问题:如果由于未知的原因,导致等待的线程没有在其他线程调用notify()和notifyAll()的情况下醒来(假唤醒),即没有通过接收信号来唤醒,可能会导致严重的问题。

5、Spurious Wakeups

通过增加等待的线程退出等待的状态条件检查,即spin loop,解决假唤醒问题。

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();
    }
  }
}

问题:doNotify()只能唤醒单个线程,即使将myMonitorObject.notify(); 改成myMonitorObject.notifyAll();也只能唤醒一个线程。因为wasSignalled只有一个,并且在被唤醒的线程执行完,会清空该标记。

6、Don’t call wait() on constant String’s or global objects

public class MyWaitNotify{

  String myMonitorObject = "";
  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();
    }
  }
}

如果用常量字符串或者其他的常量或者全局对象作为监视器(monitor,管程),所有MyWaitNotify实例,会共用同一个监视器那个,可能导致使用notify可能没有通知到对应的等待线程,即唤醒了其他等待线程(相当于假唤醒)。虽然被错误唤醒的线程因为有wasSignalled保证,所以会重新进行等待,但是对于应该获取信号的线程而言,相当于信号丢失了。

所以,不要用常量作为锁监控对象。