Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/multithreading/4.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Java 我可以使用ForkJoinPool的工作窃取行为来避免线程饥饿死锁吗?_Java_Multithreading_Concurrency_Java.util.concurrent_Fork Join - Fatal编程技术网

Java 我可以使用ForkJoinPool的工作窃取行为来避免线程饥饿死锁吗?

Java 我可以使用ForkJoinPool的工作窃取行为来避免线程饥饿死锁吗?,java,multithreading,concurrency,java.util.concurrent,fork-join,Java,Multithreading,Concurrency,Java.util.concurrent,Fork Join,如果池中的所有线程都在等待同一池中排队的任务完成,则正常线程池中会发生线程饥饿死锁ForkJoinPool通过从join()调用内部窃取其他线程的工作,而不是简单地等待,从而避免了此问题。例如: private static class ForkableTask extends RecursiveTask<Integer> { private final CyclicBarrier barrier; ForkableTask(CyclicBarrier barrie

如果池中的所有线程都在等待同一池中排队的任务完成,则正常线程池中会发生线程饥饿死锁
ForkJoinPool
通过从
join()
调用内部窃取其他线程的工作,而不是简单地等待,从而避免了此问题。例如:

private static class ForkableTask extends RecursiveTask<Integer> {
    private final CyclicBarrier barrier;

    ForkableTask(CyclicBarrier barrier) {
        this.barrier = barrier;
    }

    @Override
    protected Integer compute() {
        try {
            barrier.await();
            return 1;
        } catch (InterruptedException | BrokenBarrierException e) {
            throw new RuntimeException(e);
        }
    }
}

@Test
public void testForkJoinPool() throws Exception {
    final int parallelism = 4;
    final ForkJoinPool pool = new ForkJoinPool(parallelism);
    final CyclicBarrier barrier = new CyclicBarrier(parallelism);

    final List<ForkableTask> forkableTasks = new ArrayList<>(parallelism);
    for (int i = 0; i < parallelism; ++i) {
        forkableTasks.add(new ForkableTask(barrier));
    }

    int result = pool.invoke(new RecursiveTask<Integer>() {
        @Override
        protected Integer compute() {
            for (ForkableTask task : forkableTasks) {
                task.fork();
            }

            int result = 0;
            for (ForkableTask task : forkableTasks) {
                result += task.join();
            }
            return result;
        }
    });
    assertThat(result, equalTo(parallelism));
}
private static class CallableTask implements Callable<Integer> {
    private final CyclicBarrier barrier;

    CallableTask(CyclicBarrier barrier) {
        this.barrier = barrier;
    }

    @Override
    public Integer call() throws Exception {
        barrier.await();
        return 1;
    }
}

@Test
public void testWorkStealing() throws Exception {
    final int parallelism = 4;
    final ExecutorService pool = new ForkJoinPool(parallelism);
    final CyclicBarrier barrier = new CyclicBarrier(parallelism);

    final List<CallableTask> callableTasks = Collections.nCopies(parallelism, new CallableTask(barrier));
    int result = pool.submit(new Callable<Integer>() {
        @Override
        public Integer call() throws Exception {
            int result = 0;
            // Deadlock in invokeAll(), rather than stealing work
            for (Future<Integer> future : pool.invokeAll(callableTasks)) {
                result += future.get();
            }
            return result;
        }
    }).get();
    assertThat(result, equalTo(parallelism));
}

粗略地看一下
ForkJoinPool
的实现,所有常规的
ExecutorService
API都是使用
ForkJoinTask
实现的,所以我不知道为什么会发生死锁。

我知道您在做什么,但我不知道为什么。屏障的概念是,独立的线程可以等待彼此到达一个公共点。你没有独立的线程。线程池(F/J)用于

你正在做一些更适合你的事情


F/J继续的原因是框架创建“继续线程”,以便在所有工作线程都在等待时继续从DEQUE获取工作

你几乎在回答你自己的问题。解决方案是声明“
ForkJoinPool
通过从
join()
调用内部窃取其他线程的工作来避免此问题”。每当线程由于除
ForkJoinPool.join()之外的其他原因被阻塞时,此工作窃取不会发生,线程只是等待,什么也不做

这是因为在Java中,
ForkJoinPool
不可能阻止它的线程阻塞,而是给它们一些其他的工作。线程本身需要避免阻塞,而是向池请求它应该做的工作。这只在
ForkJoinTask.join()方法中实现,而不在任何其他阻塞方法中实现。如果在
ForkJoinPool
中使用
Future
,还将看到饥饿死锁

为什么工作窃取只在
ForkJoinTask.join()中实现,而不在Java API中的任何其他阻塞方法中实现?嗯,有很多这样的阻塞方法(
Object.wait()
Future.get()
java.util.concurrent
中的任何并发原语,I/O方法等等),它们与
ForkJoinPool
无关,后者只是API中的一个任意类,因此,在所有这些方法中添加特殊情况将是糟糕的设计。这也可能导致非常令人惊讶和不期望的影响。例如,假设一个用户将一个任务传递给一个等待
未来的
ExecutorService
,然后发现该任务在
未来挂起很长时间。get()
仅仅是因为正在运行的线程窃取了其他线程(长时间运行)工作项,而不是等待
未来
并在结果可用后立即继续。一旦一个线程开始处理另一个任务,它就不能返回到原始任务,直到第二个任务完成。因此,其他阻塞方法不起作用实际上是件好事。对于
ForkJoinTask
,不存在此问题,因为尽快继续执行主要任务并不重要,重要的是尽可能有效地处理所有任务

ForkJoinPool
中,也不可能实现您自己的工作窃取方法,因为所有相关部分都不是公共的

然而,实际上还有第二种方法可以防止饥饿死锁。这称为管理阻塞。它不使用工作窃取(以避免上述问题),但还需要将被阻塞的线程与线程池积极协作。对于托管阻塞,线程会在调用潜在阻塞方法之前通知线程池它可能已被阻塞,并在阻塞方法完成时通知线程池。然后,线程池知道存在饥饿死锁的风险,如果其所有线程当前都处于某个阻塞操作中,并且还有其他任务要执行,则可能会产生额外的线程。注意,由于额外线程的开销,这比工作窃取效率低。如果您使用普通未来和托管阻塞实现递归并行算法,而不是使用
ForkJoinTask
和工作窃取,那么额外线程的数量可能会非常大(因为在“divide”中在算法阶段,将创建大量任务并将其分配给线程,这些线程立即阻塞并等待子任务的结果)。但是,饥饿死锁仍然是可以避免的,它避免了一个任务必须等待很长时间的问题,因为它的线程同时开始处理另一个任务

Java的
ForkJoinPool
也支持托管阻塞。要使用它,需要实现接口,以便从该接口的
block
方法中调用任务想要执行的潜在阻塞方法。然后任务可能不会直接调用阻塞方法,而是需要调用静态方法。此方法处理阻塞前后与线程池的通信。如果当前任务没有在
ForkJoinPool
中执行,那么它也可以工作,然后只调用阻塞方法

我在JavaAPI(用于Java7)中发现的唯一实际使用托管阻塞的地方是类。(此类与互斥锁和锁存器类似,但更灵活、更强大。)因此,与
ForkJoinPool
任务中的
Phaser
同步应使用托管阻塞,并可避免饥饿死锁(但
ForkJoinTask.join()
仍然更可取,因为它使用工作窃取而不是托管阻塞)。无论您是直接使用
ForkJoinPool
还是