我正在试验线程停车并决定建立某种服务。这是它的样子:
public class TestService {
private static final Logger logger = LoggerFactory.getLogger(TestService.class); // logback I think this logger causes some troubles
private final CountDownLatch stopLatch;
private final Object parkBlocker = new Object();
private volatile boolean stopped;
private final Thread[] workers;
public TestService(int parallelizm) {
stopLatch = new CountDownLatch(parallelizm);
workers = new Thread[parallelizm];
for (int i = 0; i < parallelizm; i++) {
workers[i] = new Thread(() -> {
try {
while (!stopped) {
logger.debug("Parking " + Thread.currentThread().getName());
LockSupport.park(parkBlocker);
logger.debug(Thread.currentThread().getName() + " unparked");
}
} finally {
stopLatch.countDown();
}
});
}
}
public void start() {
Arrays.stream(workers).forEach(t -> {
t.start();
logger.debug(t.getName() + " started");
});
}
public boolean stop(long timeout, TimeUnit unit) throws InterruptedException {
boolean stoppedSuccefully = false;
this.stopped = true;
unparkWorkers();
if (stopLatch.await(timeout, unit)) {
stoppedSuccefully = true;
}
return stoppedSuccefully;
}
private void unparkWorkers() {
Arrays.stream(workers).forEach(w -> {
LockSupport.unpark(w);
logger.debug("Un-park call is done on " + w.getName());
});
}
}
我面临的问题是,如果我按如下方式测试此服务:
public static void main(String[] args) = {
while(true) {
TestService service = new TestService(2);
service.start();
if (!service.stop(10000, TimeUnit.MILLISECONDS))
throw new RuntimeException();
}
}
我有时会遇到以下行为:
14:58:55.226 [main] DEBUG com.pack.age.TestService - Thread-648 started
14:58:55.227 [Thread-648] DEBUG com.pack.age.TestService - Parking Thread-648
14:58:55.227 [main] DEBUG com.pack.age.TestService - Thread-649 started
14:58:55.227 [main] DEBUG com.pack.age.TestService - Un-park call is done on Thread-648
14:58:55.227 [Thread-648] DEBUG com.pack.age.TestService - Thread-648 unparked
14:58:55.227 [main] DEBUG com.pack.age.TestService - Un-park call is done on Thread-649
14:58:55.227 [Thread-649] DEBUG com.pack.age.TestService - Parking Thread-649
Exception in thread "main" java.lang.RuntimeException
at com.pack.age.Test$.main(Test.scala:12)
at com.pack.age.Test.main(Test.scala)
线程停在停车场上:
"Thread-649" #659 prio=5 os_prio=0 tid=0x00007efe4433f000 nid=0x7691 waiting on condition [0x00007efe211c8000]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x0000000720739a68> (a java.lang.Object)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
at com.pack.age.TestService.lambda$new$0(TestService.java:27)
at com.pack.age.TestService$$Lambda$1/1327763628.run(Unknown Source)
at java.lang.Thread.run(Thread.java:748)
我没有在停车场看到任何比赛。此外,如果在unpark
之前调用park
,则park
保证不会阻止(这就是javadoc所说的)。
也许我误用了LockSupport::park
。你能建议任何修复吗?
答案 0 :(得分:3)
这与记录器无关,尽管它的用法使问题浮出水面。就这么简单,您就有了比赛条件。在解释种族状况之前,您需要首先了解LockSupport::unpark
文档中的一些知识:
使给定线程的许可(如果尚不可用)可用。如果线程在驻留时被阻止,则它将取消阻止。 否则,它的下一个停泊呼叫保证不会阻塞。
第一点是解释here。简短的版本是:如果您已经启动了thread
,但尚未 调用park
,并且在这段时间内(介于{{1 }}和start
),其他一些线程在第一个调用park
:该线程根本不会驻留。许可证将立即可用。也许这张小图可以使它更清晰:
unpark
请注意,(ThreadA) start ------------------ park --------- ....
(ThreadB) start ----- unpark -----
在ThreadB
呼叫unpark(ThreadA)
到ThreadA
的期间之间如何呼叫start
。这样,当park
到达ThreadA
时:保证不会阻塞,就像文档中所说的一样。
同一文档中的第二点是:
如果未启动给定线程,则不能保证此操作完全无效。
让我们通过绘图来看看:
park
Thread B calls unpark(ThreadA) --- Thread A starts --- Thread A calls park
调用ThreadA
之后,它将永远挂起,因为park
再也不会对其调用ThreadB
。请注意,对unpark
的调用是在 unpark
开始之前进行的(与前面的示例不同)。
这正是您的情况:
(来自ThreadA
的 LockSupport.unpark(w);
在unparkWorkers
之前被称为 t.start();
。用简单的话来说-您的代码在public void start(){...}
之前都在unpark
上都调用了workers
,因此当它们最终到达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();
}
}
}
其中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
之前先呼叫park
,这是在x = 3
作为输出时发生的。