参考URL:Java并发性和多线程介绍
1.线程间通信,例如线程A通知线程B已经准备好了数据
2.通过共享对象通信,设置boolean值
3.忙等待,类似于偏向锁,等待然后自旋直到前一个线程执行结束
4.wait(),notify()和notifyAll()
以下是一个线程等待和唤醒的例子,doWait()等待,doNotify()唤醒。
public class MonitorObject{
}
public class MyWaitNotify{
MonitorObject myMonitorObject = new MonitorObject();
public void doWait(){
synchronized(myMonitorObject){
try{
myMonitorObject.wait();// 先获得锁,再wait()
} catch(InterruptedException e){...}
}
}
public void doNotify(){
synchronized(myMonitorObject){
myMonitorObject.notify();
}
}
}
(1)一个线程调用了任意一个对象的wait就变为非运行状态,直到调用同一个对象的notify才进入就绪状态。
(2)线程必须在同步块里调用wait()或者notify(),为了获得对象的锁。这是强制性的!
(3)当一个线程调用一个对象的notify(),正在等待该对象的所有线程中将有一个线程被唤醒并允许执行,这个将被唤醒的线程是随机的,不可以指定唤醒哪个线程。
(4)一旦一个线程被唤醒,先要获取锁,然后才能wait()退出该线程。也就是说如果多个线程被notifyAll()唤醒,那么在同一时刻将只有一个线程可以退出wait()方法。
5.丢失的信号:!!!
(1)如果一个线程先于被通知线程调用wait()前调用了notify(),等待的线程将错过这个信号。假如前一个线程先唤醒了,此时后一个线程还没来,那么当后一个线程来了的时候就会丢失前一个线程的信号量。
(2)信号类包含了共享变量类和一个boolean信号。
public class MyWaitNotify2{// 信号类
MonitorObject myMonitorObject = new MonitorObject();// 共享对象
boolean wasSignalled = false;// 信号
public void doWait(){
synchronized(myMonitorObject){
// 防止假唤醒,用while 而不是if,为假的时候一直等待,直到为真,但是会消耗cpu性能,即一直自旋
while(!wasSignalled){// 在wait前,先判断信号量为真
try{
myMonitorObject.wait();
} catch(InterruptedException e){...}
}
wasSignalled = false;// 在wait后,设置信号量为假,表示自己没有被通知过,需要等待通知
}
}
public void doNotify(){
synchronized(myMonitorObject){
wasSignalled = true;// 在notify前,设置信号量为假,表示已经被通知过
myMonitorObject.notify();
}
}
}
6.不要在字符串常量或全局对象中调用wait(),应该使用唯一的对象
当有两个信号类的时候,jvm把它们用到的不同的字符串转换为同一个对象,会造成信号量丢失和伪唤醒问题。
假设AB线程为实例1的线程,CD线程为实例2的线程,两个实例的信号量分别保存在两个实例中,但是他们的对象是同一个。
AB线程结束后错误地唤醒了CD,即伪唤醒;或者AB等待的信号被CD收到,即信号丢失。