这与记录器无关,尽管它的使用使问题浮出水面。你有一个竞争条件,就这么简单。在解释竞争条件之前,您需要了解一些事情LockSupport::unpark
首先是文档:
使给定线程的许可可用(如果该许可尚不可用)。如果线程在 Park 时被阻塞,那么它将解除阻塞。否则,保证其下一次对 Park 的调用不会被阻塞。
第一点解释一下here https://stackoverflow.com/questions/7497793/understanding-java-lang-thread-state-waiting-parking/51788005#51788005。简短的版本是:如果你有thread
这已经开始了,但是还没有 called park
,并在这段时间内(之间start
线程和park
),一些其他线程调用unpark
关于第一个:该线程根本不会停顿。许可证将立即可用。也许这张小图会让它更清楚:
(ThreadA) start ------------------ park --------- ....
(ThreadB) start ----- unpark -----
注意如何ThreadB
calls unpark(ThreadA)
期间之间ThreadA
已致电start
and park
。因此,当ThreadA
达到park
: 保证不阻塞,正如文档所说。
同一文档的第二点是:
如果给定线程尚未启动,则不能保证此操作有任何效果。
我们通过一张图来看看:
Thread B calls unpark(ThreadA) --- Thread A starts --- Thread A calls park
After ThreadA
calls park
,它将永远挂起,因为ThreadB
从不打电话unpark
再次关于它。请注意,调用unpark
被做了before ThreadA
已经开始(与前面的示例不同)。
这正是您的情况所发生的情况:
LockSupport.unpark(w);
(from unparkWorkers
) 叫做before t.start();
from public void start(){...}
。简而言之 - 您的代码调用unpark
双方workers
before他们甚至开始,当他们最终到达时park
- 他们被困住了,没有人能够unpark
他们。事实上,你看到这个logger
并且不与System::out
最有可能与你的脸有关println
- 有一个synchronized
方法在幕后。
事实上,LockSupport
准确地提供了证明这一点所需的语义。为此我们需要(为简单起见:SOProblem service = new SOProblem(1);
)
static class ParkBlocker {
private volatile int x;
public ParkBlocker(int x) {
this.x = x;
}
public int getX() {
return x;
}
}
现在我们需要将其插入到正确的方法中。首先标记我们已经调用的事实unpark
:
private void unparkWorkers() {
Arrays.stream(workers).forEach(w -> {
LockSupport.unpark(w);
logger.debug("Un-park call is done on " + w.getName());
});
/*
* add "1" to whatever there is already in pb.x, meaning
* we have done unparking _also_
*/
int y = pb.x;
y = y + 1;
pb.x = y;
}
然后在一个周期结束后重置标志:
public boolean stop(long timeout, TimeUnit unit) throws InterruptedException {
boolean stoppedSuccefully = false;
stopped = true;
unparkWorkers();
if (stopLatch.await(timeout, unit)) {
stoppedSuccefully = true;
// reset the flag
pb.x = 0;
}
return stoppedSuccefully;
}
然后更改构造函数以标记线程已启动:
.....
while (!stopped) {
logger.debug("Parking " + Thread.currentThread().getName());
// flag the fact that thread has started. add "2", meaning
// thread has started
int y = pb.x;
y = y + 2;
pb.x = y;
LockSupport.park(pb);
logger.debug(Thread.currentThread().getName() + " unparked");
}
然后,当线程冻结时,您需要检查该标志:
public static void main(String[] args) throws InterruptedException {
while (true) {
SOProblem service = new SOProblem(1); // <-- notice a single worker, for simplicity
service.start();
if (!service.stop(10000, TimeUnit.MILLISECONDS)) {
service.debug();
throw new RuntimeException();
}
}
}
where debug
方法是:
public void debug() {
Arrays.stream(workers)
.forEach(x -> {
ParkBlocker pb = (ParkBlocker) LockSupport.getBlocker(x);
if (pb != null) {
System.out.println("x = " + pb.getX());
}
});
}
当问题再次出现时,您已致电unpark
before你打过电话了park
,这发生在当x = 3
作为输出。