线程间通信

线程间通信

线程通信的目标是使线程间能够互相发送信号。另一方面,线程通信使线程能够等待其他线程的信号

通过共享对象通信

线程A和B必须获得指向一个MySignal共享实例的引用,然后两者在读取的时候便可以有"交流".

public class MySignal{

  protected boolean hasDataToProcess = false;

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

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

}

忙等待(Busy Wait)


//这是线程B的方法
protected MySignal sharedSignal = ...

...

while(!sharedSignal.hasDataToProcess()){
  //do nothing... busy waiting
  //这里什么也不做, 一直访问sharedSingnal的属性值
}

线程B正在等待sharedSignal变为true,则跳出循环,执行其他….
刚开始没看懂这逻辑,一个循环扔这,浪费资源啊.要是一直不跳出来呢…
当然,不只是你我发现了这一点(忙等待没有对运行等待线程的CPU进行有效的利用),且看下文

wait(),notify()和notifyAll()

我们可以让等待线程进入睡眠或者非运行状态,直到它接收到它等待的信号。

  • 一个线程如果执行到某一对象的wait方法, 那么这个线程会立即进入等待状态,等到什么时候?当然不是等到海枯石烂,等到另一个线程调用了同一个对象的notify方法的时候
    为了调用wait()或者notify(),线程必须先获得那个对象的锁。也就是说,线程必须在同步块里调用wait()或者notify()。(问:为什么这样设计?不这样设计有什么后果?)
    (校注:JVM是这么实现的,当你调用wait时候它首先要检查下当前线程是否是锁的拥有者,不是则抛出IllegalMonitorStateExcept,参考JVM源码的 1422行。)
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();//随机唤醒某一线程(如果多个在wait状态)
    }
  }
}

丢失的信号(Missed Signals)

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


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

  public void doWait(){
    synchronized(myMonitorObject){
      if(!wasSignalled){//检查信号是否接受过notify信号
        try{
          myMonitorObject.wait();
         } catch(InterruptedException e){...}
      }
      //clear signal and continue running.
      wasSignalled = false;
    }
  }

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

为了避免信号丢失, 用一个变量来保存是否被通知过。在notify前,设置自己已经被通知过。在wait后,设置自己没有被通知过,需要等待通知

假唤醒

由于莫名其妙的原因,线程有可能在没有调用过notify()和notifyAll()的情况下醒来。这就是所谓的假唤醒(spurious wakeups)。无端端地醒过来了。
加一个自旋锁

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循环)里的条件变为false

多个线程等待相同信号

如果你有多个线程在等待,被notifyAll()唤醒,但只有一个被允许继续执行,使用while循环也是个好方法。每次只有一个线程可以获得监视器对象锁,意味着只有一个线程可以退出wait()调用并清除wasSignalled标志(设为false)。一旦这个线程退出doWait()的同步块,其他线程退出wait()调用,并在while循环里检查wasSignalled变量值。但是,这个标志已经被第一个唤醒的线程清除了(又设置成true了),所以其余醒来的线程将回到等待状态,直到下次信号到来。

不要在字符串常量或全局对象中调用wait()

在wait()/notify()机制中,不要使用全局对象,字符串常量等。应该使用对应唯一的对象。例如,每一个MyWaitNotify3的实例(前一节的例子)拥有一个属于自己的监视器对象,而不是在空字符串上调用wait()/notify()。
JVM/编译器内部会把常量字符串转换成同一个对象。这意味着,即使你有2个不同的MyWaitNotify实例,它们都引用了相同的空字符串实例。这种情况相当于引发了一次假唤醒。

  • 补充:8种数据类型(String,boolean,int,double……)都是放到常量池中

    String xxx;
    int xxx;
    这是java原始数据,是不用开辟新空间

后面的我没有具体解释,重要的是理解原理.和内存模型,你就很容易推导出后面的解决方案.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值