本文大概意思都是都下边链接文章转换过来的,没有进行一字一句的翻译,只是把大概意思整里出来。
http://tutorials.jenkov.com/java-concurrency/thread-signaling.html
线程信号的目的是为了线程之间相互通信。线程信号可以使线程等待另其他线程信号,例如thread B 或许等待从thread A 发出的数据准备处理信号。
线程之间可以通过共享对象来相互发送信号。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实例,它们之间将不会接收到相互的信号。
由于thread B 一直在等待是否有数据可以处理,如果采用上面的MySignal实例,那么thread B 必须一直循环调用hasDataToProcess来判断是否有数据处理。这样就会产生忙等,消耗大量的CPU。
忙等除了在平均时间较短的情况下比较有效,其他情况不能有效的利用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 上的锁。
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(); } } }
虚假唤醒指的是即使线程没有调用监视器对象上的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状态。
while 循环同样在多线程等待的情况中仍是一个很好的解决方案。当监视器上的notifyAll被调用时,只有其中一个线程会被允许继续执行,其他的将会被再次等待,因为其他线程获得锁后,wasSignalled上的信号已经被第一个唤醒的线程擦除掉了。
当线程把一个把常量字符串当作监视器对象时,会出现异常。原因是JVM/Compiler内部会把常量字符串转换为同一对象对待。这意味着即使你有两个不同的MyWaitNotify实例,它们里面的监视器对象将会是同一个字符对象。这意味着如果一个线程在第一信号实例调用wait方法,可能将会被另一信号实例上notify的调用唤醒。因此不要把全局对象,string 常量当作监视器对象用。
本文链接: http://my.oschina.net/robinyao/blog/611885