Java线程池和runnables创建runnables

时间:2013-08-06 18:11:45

标签: java multithreading threadpool runnable callable

请耐心等待我,因为我在多线程编程方面并不是非常精明......

我目前正在构建一个使用ThreadPool ExecutorService进行各种runnable的系统。这很简单。但是,我正在考虑让runnables自己根据原始runnable中发生的事情产生额外的runnable的可能性(即,如果成功,请执行此操作,如果失败,请执行此操作等,因为某些任务必须先完成其他任务执行)。应该注意的是,主线程不需要被告知这些任务的结果,尽管它可能很方便处理异常,即,如果无法联系外部服务并且所有线程都因此抛出异常,那么停止提交任务并定期检查外部服务,直到它恢复。这不是完全必要的,但它会很好。

即,提交任务A.任务A做了一些事情。如果一切顺利,任务A将执行任务B.如果某些内容无法正常运行或抛出异常,请执行任务C.每个子任务也可能有其他任务,但只有几个级别。我宁愿做一些像这样的事情而不是单一任务中的大型咆哮条件,因为这种方法可以提供更大的灵活性。

但是,我不确定这会如何影响线程池。我假设从池中的线程中创建的任何其他线程将存在于池外部,因为它们本身未直接提交到池中。这是正确的假设吗?如果是这样,那可能是一个坏主意(好吧,如果没有,它可能不是一个非常好的主意),因为它可能会导致更多的线程,因为原始线程完成并且提交了一个新任务,同时线程产生于之前的任务仍在继续(并且可能比其他任务持续更长时间)。

我还考虑将这些实现为Callables,并在Future中放置一个响应对象,然后根据响应将相应的Callable添加到线程池中。但是,这会将所有操作都绑定回主线程,这似乎是一个不必要的瓶颈。我想我可以将Runnable放入池中,该池本身处理Callable和后续操作的执行,但之后我获得了两倍的线程。

我是在正确的轨道上还是完全不在轨道上?

3 个答案:

答案 0 :(得分:0)

我从未使用过这个,但它对你有用:http://docs.oracle.com/javase/tutorial/essential/concurrency/forkjoin.html

答案 1 :(得分:0)

有很多方法可以做你想要的。你需要小心,不要创建太多的线程。

以下是一个示例,您可以使用ExecutorCompletionService提高效率,也可以使用Runnable。

import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;


public class ThreadsMakeThreads {

    public static void main(String[] args) {
        new ThreadsMakeThreads().start();
    }

    public void start() {
        //Create resources
        ExecutorService threadPool = Executors.newCachedThreadPool();
        Random random = new Random(System.currentTimeMillis());
        int numberOfThreads = 5;

        //Prepare threads
        ArrayList<Leader> leaders = new ArrayList<Leader>();
        for(int i=0; i < numberOfThreads; i++) {
            leaders.add(new Leader(threadPool, random));
        }

        //Get the results
        try {
            List<Future<Integer>> results = threadPool.invokeAll(leaders);
            for(Future<Integer> result : results) {
                System.out.println("Result is " + result.get());
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }

        threadPool.shutdown();

    }

    class Leader implements Callable<Integer> {

        private ExecutorService threadPool;
        private Random random;

        public Leader(ExecutorService threadPool, Random random) {
            this.threadPool = threadPool;
            this.random = random;
        }

        @Override
        public Integer call() throws Exception {
            int numberOfWorkers = random.nextInt(10);
            ArrayList<Worker> workers = new ArrayList<Worker>();
            for(int i=0; i < numberOfWorkers; i++) {
                workers.add(new Worker(random));
            }
            List<Future<Integer>> tasks = threadPool.invokeAll(workers);
            int result = 0;
            for(Future<Integer> task : tasks) {
                result += task.get();
            }
            return result;
        }

    }

    class Worker implements Callable<Integer> {

        private Random random;

        public Worker(Random random) {
            this.random = random;
        }

        @Override
        public Integer call() throws Exception {
            return random.nextInt(100);
        }

    }
}

答案 2 :(得分:0)

从其他任务向线程池提交任务是非常有意义的想法。但我担心你会想到在不同的线程上运行新任务,这真的可以占用所有的内存。只需在创建池时设置线程数限制,并将新任务提交到该线程池。

这种方法可以在不同的方向进一步阐述。首先,使用接口方法将任务视为普通对象,并让该方法决定是否要将此对象提交给线程池。这要求每个任务都知道其线程池 - 在创建时将其作为参数传递。更方便的是,继续引用线程池作为线程局部变量。

您可以轻松模拟函数式编程:对象表示函数调用,对于每个参数,它都有相应的set方法。设置所有参数后,该对象将提交给线程池。

另一个方向是actor编程:task类有单集方法,但可以多次调用,如果还没有处理上一个参数,set方法不会将任务提交给线程池,而只是存储它队列中的参数。 run()方法处理队列中的所有可用参数,然后返回。

所有这些功能都在数据流库https://github.com/rfqu/df4j中实现。我故意写它来支持基于任务的并行性。