为什么`ExecutorService`不能一致地安排线程?

时间:2016-03-23 15:09:16

标签: java multithreading synchronization cyclicbarrier

我正在尝试使用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次。有人帮我数学吗?

2 个答案:

答案 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>