我正在尝试使用CyclicBarrier
重新实现我的并发代码,这对我来说是新的。我可以没有它,但我正在试图反对我的其他解决方案,我遇到的问题是一个死锁情况下面的代码:
//instance variables (fully initialised elsewhere).
private final ExecutorService exec = Executors.newFixedThreadPool(4);
private ArrayList<IListener> listeners = new ArrayList<IListener>();
private int[] playerIds;
private class WorldUpdater {
final CyclicBarrier barrier1;
final CyclicBarrier barrier2;
volatile boolean anyChange;
List<Callable<Void>> calls = new ArrayList<Callable<Void>>();
class SyncedCallable implements Callable<Void> {
final IListener listener;
private SyncedCallable(IListener listener) {
this.listener = listener;
}
@Override
public Void call() throws Exception {
listener.startUpdate();
if (barrier1.await() == 0) {
anyChange = processCommons();
}
barrier2.await();
listener.endUpdate(anyChange);
return null;
}
}
public WorldUpdater(ArrayList<IListener> listeners, int[] playerIds) {
barrier2 = new CyclicBarrier(listeners.size());
barrier1 = new CyclicBarrier(listeners.size());
for (int i : playerIds)
calls.add(new SyncedCallable(listeners.get(i)));
}
void start(){
try {
exec.invokeAll(calls);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
void someMethodCalledEveryFrame() {
//Calls some Fisher-something method that shuffles int[]
shufflePIDs();
WorldUpdater updater = new WorldUpdater(listeners, playerIds);
updater.start();
}
我在Android Studio(intelliJ)中使用调试器在此阶段暂停执行。我得到多个线程显示我的await
调用作为我要执行的最后一个代码
- &GT;
Unsafe.park
- &GT;
LockSupport.park
- &GT;
AbstractQueuedSynchronizer$ConditionObject.await
- &GT;
CyclicBarrier.doWait
- &GT;
CyclicBarrier.await
至少有一个线程将拥有此堆栈:
- &GT; Unsafe.park
- &GT; LockSupport.park
- &GT; AbstractQueuedSynchronizer$ConditionObject.await
- &GT; LinkedBlockingQueue.take
- &GT; ThreadPoolExecutor.getTask
- &GT; ThreadPoolExecutor.runWorker
- &GT; ThreadPoolExecutor$Worker.run
- &GT; Thread.run
我注意到CyclicBarrier
在这些后面的迷路线程中不起作用。
processCommons
调用exec.invokeAll
(在3个侦听器上),我想这意味着我的线程已经用完了。但很多时候这种情况并没有发生,所以请有人澄清为什么ExecutorService
无法一致地安排我的线程?他们有自己的堆栈和程序计数器,所以我认为这不是一个问题。我一次只能跑4次。有人帮我数学吗?
答案 0 :(得分:1)
创建listeners.size()
时WorldUpdater
的价值是多少?如果它超过四个,那么你的线程永远不会越过障碍。
您的ExecutorService
完全四个主题。不多也不少。在barrier1.await()
个线程正在等待之前,barrier2.await()
和listeners.size()
的来电者不会越过障碍。
我的直觉反应是,池线程使用CyclicBarrier
会出错。 CyclicBarrier
仅在您确切知道将使用多少线程时才有用。但是,当您使用线程池时,通常不知道池的大小。实际上,在现实世界(即商业)应用程序中,如果您正在使用线程池,那么它可能根本不是由您的代码创建的。它可能是在其他地方创建的,并作为注入的依赖项传递给您的代码。
答案 1 :(得分:0)
我做了一个小实验并想出了:
@Override
public Void call() throws Exception {
System.out.println("startUpdate, Thread:" + Thread.currentThread());
listener.startUpdate();
if (barrier1.await() == 0) {
System.out.println("processCommons, Thread:" + Thread.currentThread());
anyChange = processCommons();
}
barrier2.await();
System.out.println("endUpdate, Thread:" + Thread.currentThread());
listener.endUpdate(anyChange);
return null;
}
使用3个3 listeners
的游泳池时显示,我将始终挂在processCommons
中,其中包含以下内容:
List<Callable<Void>> calls = new ArrayList<Callable<Void>>();
for (IListener listiner : listeners)
calls.add(new CommonsCallable(listener));
try {
exec.invokeAll(calls);
} catch (InterruptedException e) {
e.printStackTrace();
}
2个线程在屏障处等待,第三个线程尝试再创建3个线程。我需要在ExecutorService
中添加一个额外的线程,屏障中的2个线程可以回收&#34;正如我在问题中提出的那样。当exec
只持有4时,我已经在这个阶段引用了6个线程。这可以很快地运行很多分钟。
private final ExecutorService exec = Executors.newFixedThreadPool(8);
应该更好,但事实并非如此。
最后我在intelliJ中做了断点踩(感谢ideaC!)
问题是
if (barrier1.await() == 0) {
anyChange = processCommons();
}
barrier2.await();
在2 await
之间,您可能会获得几个尚未实际到达await
的悬线。如果3个听众从一个4人的游泳池中出来,那么只需要一个人就可以获得&#34;不计划的&#34; (或其他)和barrier2
永远不会得到完整的补充。但是当我有8个游泳池时呢?除了两个线程之外的所有线程都表现出相同的行为:
- &GT; Unsafe.park
- &GT; LockSupport.park
- &GT;的AbstractQueuedSynchronizer $ ConditionObject.await
- &GT; LinkedBlockingQueue.take
- &GT; ThreadPoolExecutor.getTask
- &GT; ThreadPoolExecutor.runWorker
- &GT;的ThreadPoolExecutor $ Worker.run
- &GT; Thread.run
这里可以发生什么来禁用所有5个线程?我应该接受James Large的建议,并避免在这个过于精细的CyclicBarrier
中撬开.--更新 - 它可以整夜运行而不用CyclicBarrier
。< / p>