原文:http://tutorials.jenkov.com/java-concurrency/thread-signaling.html
线程信号的目的是为了线程间能够相互发送信号,并且也能使线程去等待其他线程的信号。例如一个线程B可能等待一个来至于线程A的信号,用来表明数据已经处理好了。
Signaling via Shared Objects
一种简单的线程通信(线程间进行信号的发送)是使用共享相同的对象变量。
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
一个线程一直在等待另外一个线程改变共享变量的为其期望值,称为 Busy Wait。
protected MySignal sharedSignal = ...
...
while(!sharedSignal.hasDataToProcess()){
//do nothing... busy waiting
}
wait(),notify() and notifyAll()
Busy waiting 不是一种非常有效地利用CPU来运行等待线程,即容易造成CPU的浪费,除非等待线程所耗费的时间非常短。更好的方式waiting thread 能够sleep或者inactive当接受到等待信号时。
Java中使用wait(),notify(),notifyAll()来完成这些工作。
A thread that calls wait() on any object becomes inactive until another thread calls notify() on that object. In order to call either wait() or notify the calling thread must first obtain the lock on that object. In other words, the calling thread must call wait() or notify() from inside a synchronized block. Here is a modified version of MySignal called MyWaitNotify that uses wait() and notify().
也就是说,wait、notify、notifyAll必须加上synchronized关键字,且所对象必须是用一个对象。
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();
}
}
}
Missed Signals
如果一个线程在调用wait时先调用了notify,容易出现信号丢失的情况,直接导致线程waiting forever
public class MyWaitNotify2 {
MonitorObject myMonitorObject = new MonitorObject();
boolean wasSignalled = false;
public void doWait() {
synchronized (myMonitorObject) {
if (!wasSignalled) {
// In fact it only calls wait() if no signal was received in between the previous doWait() call and this.
try {
myMonitorObject.wait();
} catch (InterruptedException e) {...}
}
//clear signal and continue running.
wasSignalled = false;
}
}
public void doNotify() {
synchronized (myMonitorObject) {
wasSignalled = true;
myMonitorObject.notify();
}
}
}
Spurious Wakeups
For inexplicable reasons it is possible for threads to wake up even if notify() and notifyAll() has not been called. This is known as spurious wakeups. Wakeups without any reason.
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();
}
}
}
Multiple Threads Waiting for the Same Signals
The while loop is also a nice solution if you have multiple threads waiting, which are all awakened using notifyAll(), but only one of them should be allowed to continue. Only one thread at a time will be able to obtain the lock on the monitor object, meaning only one thread can exit the wait() call and clear the wasSignalled flag. Once this thread then exits the synchronized block in the doWait() method, the other threads can exit the wait() call and check the wasSignalled member variable inside the while loop. However, this flag was cleared by the first thread waking up, so the rest of the awakened threads go back to waiting, until the next signal arrives.
Don’t call wait() on constant String’s or global objects
不要使用String类型去做锁对象,因为在编译期间很多常量String都会直接放入常量池中,被共享。
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();
}
}
}