您的位置:首页 > 大数据 > 人工智能

Thread Signaling

2016-01-31 12:37 555 查看
本文大概意思都是都下边链接文章转换过来的,没有进行一字一句的翻译,只是把大概意思整里出来。

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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息