johnbanq 在排除了IMSE错误之后,我们接着解决这个问题——
在修好了异常并且去掉了.get()调用(它是阻塞的,会等待这个任务(thread1)整个函数跑完,对于这个双线程协同任务而言是不可接受的)后,我们运行程序,发现程序会随机地卡在某个count值上,使用debugger进行检查,发现两个线程检查线程状态,发现两个线程都进入了WAIT状态,并且都卡在了各自的await调用里。
这看起来像是条件变量通知时刻和其它线程等待时刻的错位问题,那我们就加点println:
Callable<String> thread1= new Callable<String>() {
@Override
public String call() throws Exception {
while(count<100) {
if(count==100){
break;
}
int cnt = count;
if (cnt % 2 == 0) {
//先判断再获取锁
lock.lock();
count++;
//Thread.sleep(100);
System.out.println("loop:"+cnt+" thread 1 is waking everyone up");
condition.signalAll();
lock.unlock();
}else{
lock.lock();
System.out.println("loop:"+cnt+" thread 1 decided to suspend");
condition.await();
System.out.println("loop:"+cnt+" thread 1 is awake");
lock.unlock();
}
}
return "线程1执行完毕";
}
};
Callable<String> thread2= new Callable<String>() {
@Override
public String call() throws Exception {
while(count<100){
if(count==100){
break;
}
int cnt = count;
if(cnt%2==1){
//Thread.sleep(100);
lock.lock();
count++;
System.out.println("loop:"+cnt+" thread 2 is waking everyone up");
condition.signalAll();
lock.unlock();
} else {
lock.lock();
System.out.println("loop:"+cnt+" thread 2 decided to suspend");
System.out.flush();
condition.await();
System.out.println("loop:"+cnt+" thread 2 is awake");
lock.unlock();
}
//lock.unlock();
}
return "线程2结束";
}
};
追问:为什么要单独看一个覆盖范围这么大的cnt变量来保存count的值?
出于原子性考虑,从判断到打印中间可能发生任何事,拷进变量里能避开这个坑
执行得到如下日志:
loop:0 thread 1 is waking everyone up
loop:1 thread 1 decided to suspend
loop:0 thread 2 decided to suspend
然后就卡住不动了。
如果我们仔细看看打出来的东西,会发现结尾2个线程都跑去suspend了,没有人在干活。
再动动脑筋,我们就可以还原出真相:
最开始是循环0:
线程2先发制人,执行到了int cnt=count, 然后出于某些原因(例如太窜了),被OS从CPU上面挪了下来,把1换上去了。
线程1起来把活干了,通知了一下正在等的人可以开始干活了,就去suspend了。
线程2被OS重新挪上了CPU,开始等待1的通知。
需要注意的是Condition 的通知 不是持久的,如果你来晚了,就不会收到那个通知。于是可怜的2在等待队列里一直suspend,等那个早就发了的通知。