Java:循环执行直到完成ThreadPoolExecutor的任务,然后继续

时间:2019-07-10 16:16:04

标签: java multithreading threadpool dijkstra threadpoolexecutor

我正在努力使Dijkstra算法并行化。使每个节点线程查看当前节点的所有边缘。这是与线程并行执行的,但是开销太大。这比顺序算法的时间更长。

添加了ThreadPool来解决此问题,但是我无法等待任务完成才可以进行下一个迭代。只有完成一个节点的所有任务后,我们才应该继续。我们需要所有任务的结果,然后才能按节点搜索下一个最接近的对象。

我尝试做executor.shutdown(),但是有了这个方法,它不会接受新任务。我们如何在循环中等待直到每个任务完成,而不必每次都声明ThreadPoolExecutor。通过使用此线程而不是常规线程,这样做将无法达到减少开销的目的。

我想到的一件事是添加任务(边)的BlockingQueue。但对于这种解决方案,我仍然坚持等待任务完成而无需shudown()。

public void apply(int numberOfThreads) {
        ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(numberOfThreads);

        class DijkstraTask implements Runnable {

            private String name;

            public DijkstraTask(String name) {
                this.name = name;
            }

            public String getName() {
                return name;
            }

            @Override
            public void run() {
                calculateShortestDistances(numberOfThreads);
            }
        }

        // Visit every node, in order of stored distance
        for (int i = 0; i < this.nodes.length; i++) {

            //Add task for each node
            for (int t = 0; t < numberOfThreads; t++) {
                executor.execute(new DijkstraTask("Task " + t));
            }

            //Wait until finished?
            while (executor.getActiveCount() > 0) {
                System.out.println("Active count: " + executor.getActiveCount());
            }

            //Look through the results of the tasks and get the next node that is closest by
            currentNode = getNodeShortestDistanced();

            //Reset the threadCounter for next iteration
            this.setCount(0);
        }
    }

边的数量除以线程数。所以8条边和2条线程意味着每个线程将并行处理4条边。

public void calculateShortestDistances(int numberOfThreads) {

        int threadCounter = this.getCount();
        this.setCount(count + 1);

        // Loop round the edges that are joined to the current node
        currentNodeEdges = this.nodes[currentNode].getEdges();

        int edgesPerThread = currentNodeEdges.size() / numberOfThreads;
        int modulo = currentNodeEdges.size() % numberOfThreads;
        this.nodes[0].setDistanceFromSource(0);
        //Process the edges per thread
        for (int joinedEdge = (edgesPerThread * threadCounter); joinedEdge < (edgesPerThread * (threadCounter + 1)); joinedEdge++) {

            System.out.println("Start: " + (edgesPerThread * threadCounter) + ". End: " + (edgesPerThread * (threadCounter + 1) + ".JoinedEdge: " + joinedEdge) + ". Total: " + currentNodeEdges.size());
            // Determine the joined edge neighbour of the current node
            int neighbourIndex = currentNodeEdges.get(joinedEdge).getNeighbourIndex(currentNode);

            // Only interested in an unvisited neighbour
            if (!this.nodes[neighbourIndex].isVisited()) {
                // Calculate the tentative distance for the neighbour
                int tentative = this.nodes[currentNode].getDistanceFromSource() + currentNodeEdges.get(joinedEdge).getLength();
                // Overwrite if the tentative distance is less than what's currently stored
                if (tentative < nodes[neighbourIndex].getDistanceFromSource()) {
                    nodes[neighbourIndex].setDistanceFromSource(tentative);
                }
            }
        }

        //if we have a modulo above 0, the last thread will process the remaining edges
        if (modulo > 0 && numberOfThreads == (threadCounter + 1)) {
            for (int joinedEdge = (edgesPerThread * threadCounter); joinedEdge < (edgesPerThread * (threadCounter) + modulo); joinedEdge++) {
                // Determine the joined edge neighbour of the current node
                int neighbourIndex = currentNodeEdges.get(joinedEdge).getNeighbourIndex(currentNode);

                // Only interested in an unvisited neighbour
                if (!this.nodes[neighbourIndex].isVisited()) {
                    // Calculate the tentative distance for the neighbour
                    int tentative = this.nodes[currentNode].getDistanceFromSource() + currentNodeEdges.get(joinedEdge).getLength();
                    // Overwrite if the tentative distance is less than what's currently stored
                    if (tentative < nodes[neighbourIndex].getDistanceFromSource()) {
                        nodes[neighbourIndex].setDistanceFromSource(tentative);
                    }
                }
            }
        }
        // All neighbours are checked so this node is now visited
        nodes[currentNode].setVisited(true);
    }

感谢您的帮助!

3 个答案:

答案 0 :(得分:2)

您应该查看CyclicBarrierCountDownLatch。这两种方法都可以防止线程启动,除非其他线程已发出信号已完成。它们之间的区别在于CyclicBarrier是可重复使用的,即可以多次使用,而CountDownLatch是一次性的,您无法重置计数。

Javadocs中的措辞:

  

CountDownLatch 是一种同步帮助,它允许一个或多个线程等待,直到在其他线程中执行的一组操作完成为止。

     

CyclicBarrier 是一种同步辅助工具,它允许一组线程互相等待,以到达一个公共的障碍点。 CyclicBarriers在涉及固定大小的线程方的程序中很有用,该线程方有时必须互相等待。该屏障称为循环屏障,因为它可以在释放等待线程之后重新使用。

https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/CyclicBarrier.html

https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/CountDownLatch.html

答案 1 :(得分:2)

以下是使用CountDownLatch等待池中所有线程的简单演示:

import java.io.IOException;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class WaitForAllThreadsInPool {

    private static int MAX_CYCLES = 10;

    public static void main(String args[]) throws InterruptedException, IOException {
        new WaitForAllThreadsInPool().apply(4);
    }

    public void apply(int numberOfThreads) {

        ExecutorService executor = Executors.newFixedThreadPool(numberOfThreads);
        CountDownLatch cdl = new CountDownLatch(numberOfThreads);

        class DijkstraTask implements Runnable {

            private final String name;
            private final CountDownLatch cdl;
            private final Random rnd = new Random();

            public DijkstraTask(String name, CountDownLatch cdl) {
                this.name = name;
                this.cdl = cdl;
            }

            @Override
            public void run() {
                calculateShortestDistances(1+ rnd.nextInt(MAX_CYCLES), cdl, name);
            }
        }

        for (int t = 0; t < numberOfThreads; t++) {
            executor.execute(new DijkstraTask("Task " + t, cdl));
        }

        //wait for all threads to finish
        try {
            cdl.await();
            System.out.println("-all done-");
        } catch (InterruptedException ex) {
            ex.printStackTrace();
        }
    }

    public void calculateShortestDistances(int numberOfWorkCycles, CountDownLatch cdl, String name) {

        //simulate long process
        for(int cycle = 1 ; cycle <= numberOfWorkCycles; cycle++){
            System.out.println(name + " cycle  "+ cycle + "/"+ numberOfWorkCycles );
            try {
                TimeUnit.MILLISECONDS.sleep(1000);
            } catch (InterruptedException ex) {
                ex.printStackTrace();
            }
        }

        cdl.countDown(); //thread finished
    }
}

输出样本:

  

任务0周期1/3
任务1周期1/2
任务3周期1/9
  任务2周期1/3
任务0周期2/3
任务1周期2/2
  任务2周期2/3
任务3周期2/9
任务0周期3/3
  任务2周期3/3
任务3周期3/9
任务3周期4/9
  任务3周期5/9
任务3周期6/9
任务3周期7/9
  任务3周期8/9
任务3周期9/9
  -全部完成-

答案 2 :(得分:1)

您可以使用invokeAll

//Add task for each node
Collection<Callable<Object>> tasks = new ArrayList<>(numberOfThreads);
for (int t = 0; t < numberOfThreads; t++) {
    tasks.add(Executors.callable(new DijkstraTask("Task " + t)));
}

//Wait until finished
executor.invokeAll(tasks);