java解惑,77--乱锁之妖
下面的这段程序模拟了一个小车间。程序首先启动了一个工人线程,该线程在停止时间到来之前会一直工作(至少是假装在工作),然后程序安排了一个定时器任务(timer task)用来模拟一个恶毒的老板,他会试图阻止停止时间的到来。最后,主线程作为一个善良的老板会告诉工人停止时间到了,并且等待工人停止工作。那么这个程序会打印什么呢?
<span style="font-size:18px;">public class Worker extends Thread{
private long count = 0;
private volatile boolean quittingTime = false;
public void run(){
while(!quittingTime){
pretendToWork();
}
System.out.println("Beer is good");
}
private void pretendToWork(){
try{
Thread.sleep(300);
System.out.println("product:"+(count++));
}catch(InterruptedException ex){
}
}
//It's quitting time ,wait for worker-Called by good boss
synchronized void quit() throws InterruptedException{
quittingTime = true;
join();
}
//Recsind quiting time-Called by evil boss
synchronized void keepWorking(){
quittingTime = true;
}
public static synchronized void main(String[] args) throws InterruptedException{
final Worker worker = new Worker();
worker.start();
Timer t = new Timer(true);
t.schedule(new TimerTask(){
public void run(){
worker.keepWorking();
}
},500);
Thread.sleep(400);
worker.quit();
}
}
</span>
按照书上的解释:
- 300 ms:工人线程去检查易变的quittingTime 域,看看停止时间是否已经到了。这个时候并没有到停止时间,所 以工人线程会回去继续“工作”。
- 400ms:作为善良的老板的主线程会去调用工人线程的quit方法。主线程会获得工人线程实例上的锁(因为quit是一个同步化的方法),将quittingTime的值设为true,并且调用工人线程上的join方法。这个对join方法的调用并不会马上返回,而是会等待工人线程执行完毕。
- 500m:作为恶毒的老板定时器任务开始执行。它将试图调用工人线程的keepWorking方法,但是这个调用将会被阻塞,因为keepWorking是一个同步化的方法,而主线程当时正在执行工人线程上的另一个同步化方法(quit方法)。
- 600ms:工人线程会再次检查停止时间是否已经到来。由于quittingTime域是易变的,那么工人线程肯定会看到新的值true,所以它会打印 Beer is good 并结束运行。这会让主线程对join方法的调用执行返回,随后主线程也结束了运行。而定时器线程是后台的,所以它也会随之结束运行,整个程序也就结束了。
其实,并没有什么可以保证上述几个交叉的事件会按照上面的时间轴发生。无论是Timer类还是Thread.sleep方法,都不能保证具有实时(real-time)性。这就是说,由于这里计时的粒度太粗,所以上述几个事件很有可能会在时间轴上互有重叠地交替发生。100毫秒对于计算机来说是一段很长的时间。此外,这个程序被重复地挂起;看起来好像有什么其他的东西在工作着,事实上,确实是有这种东西。
我们的分析存在着一个基本的错误。在500ms时,当作为恶毒老板的定时器任务运行时,根据时间轴的显示,它对keepWorking方法的调用会被阻塞,因为keepWorking是一个同步化的方法并且主线程正在同一个对象上执行着同步化方法quit(在Thread.join中等待着)。这些都是对的,keepWorking确实是一个同步化的方法,并且主线程确实正在同一个对象上执行着同步化的quit方法。即使如此,定时器线程仍然可以获得这个对象上的锁,并且执行keepWorking方法。这是如何发生的呢?
问题的答案涉及到了Thread.join的实现。这部分内容在关于该方法的文档中(JDK文档)是找不到的,至少在迄今为止发布的文档中如此,也包括5.0版。在内部,Thread.join方法在表示正在被连接(join)的那个Thread实例上调用Object.wait方法。这样就在等待期间释放了该对象上的锁。在我们的程序中,这就使得作为恶毒老板的定时器线程能够堂而皇之的将quittingTime重新设置成false,尽管此时主线程正在执行同步化的quit方法。这样的结果是,工人线程永远不会看到停止时间的到来,它会永远运行下去。作为善良的老板的主线程也就永远不会从join方法中返回了。
使这个程序产生了预料之外的行为的根本原因就是WorkerThread类的作者使用了实例上的锁来确保quit方法和keepWorking方法的互斥,但是这种用法与超类(Thread)内部对该锁的用法发生了冲突。
但是我多次运行后,均能得到正常结果。。。。。
希望有人解惑,或许是因为我的程序有问题吧
再来说说编写程序中遇到的小问题吧!
//public class Seventyseventh{
// static class Worker extends Thread{//需要写上static,否则的话,在main里定义work会出错。原因:无法从静态上下文中引用非静态 变量
// private long count = 0;
// private volatile boolean quittingTime = false;//体会volatile有时并不能真正的实现锁机制
// public void run(){
// while(!quittingTime){
// pretendToWork();
// }
// System.out.println("Beer is good");
// }
// private void pretendToWork(){
// try{
// Thread.sleep(300);
// System.out.println("product:"+(count++));
// }catch(InterruptedException ex){
//
// }
// }
// //It's quitting time ,wait for worker-Called by good boss
// synchronized void quit() throws InterruptedException{
// quittingTime = true;
// join();//等待线程结束,也就是执行完run方法
// }
// //Recsind quiting time-Called by evil boss
// synchronized void keepWorking(){
// quittingTime = true;
// }
// }
// public static synchronized void main(String[] args) throws InterruptedException{
// final Worker worker = new Worker();//必须是final类型,否则的话,在new TimeTask里调用失败,原因是从内部类中访问本地变量worker; 需要被声明为最终类型
// worker.start();
// Timer t = new Timer(true);
// t.schedule(new TimerTask(){
// public void run(){
// worker.keepWorking();
// }
// },500);
// Thread.sleep(400);
// worker.quit();
// }
//}