Java - splitting work to multiple threads

前端 未结 6 1962
谎友^
谎友^ 2020-12-14 12:07

I am posed with the following problem: I need to split work across multiple threads for perfomance reasons, but I am not sure what approach to take.

Firstly, the tas

相关标签:
6条回答
  • 2020-12-14 12:19

    Your approach with ExecutorService is pretty much the most modern and safe way to do this. It is recommended to extract your Callables to separate class:

    public class ExpensiveTask implements Callable<String> {
    
        private final String param;
    
        public ExpensiveTask(String param) {
            this.param = param;
        }
    
        @Override
        public String call() throws Exception {
            return expensiveMethod(param);
        }
    
    }
    

    which will make your code much cleaner:

    final ExecutorService executorService = Executors.newFixedThreadPool(16);
    final Future<String> res1 = executorService.submit(new ExpensiveTask("param1"));
    final Future<String> res2 = executorService.submit(new ExpensiveTask("param2"));
    String obj1 = res1.get();
    String obj2 = res2.get();
    

    A few notes:

    • 16 threads are too much if you only want to process two tasks simultaneously - or maybe you want to reuse that pool from several client threads?

    • remember to close the pool

    • use lightweight ExecutorCompletionService to wait for the first task that finished, not necessarily for the first one that was submitted.

    If you need a completely different design idea, check out akka with its actor based concurrency model.

    0 讨论(0)
  • 2020-12-14 12:22

    I shall add a proposal that is in my eyes more elegant than creating a whole new class for your parametrized Callable. My solution is a method that returns a Callable instance:

    Callable<String> expensive(final String param) {
      return new Callable<String>() { public String call() { 
        return expensiveMethod(param);
      }};
    }
    

    This even makes client code more palatable:

    final Future<String> f1 = executor.submit(expensive("param1"));
    
    0 讨论(0)
  • 2020-12-14 12:27

    You want to use the CompletionService and keep track of the submitted tasks.
    In your loop you then take() and exit the loop when you've got all you tasks completed.
    Scale very well is you add more tasks later.

    0 讨论(0)
  • 2020-12-14 12:28
    private static final ExecutorService threadpool = Executors.newFixedThreadPool(3);
    
        ArrayList<Future<List<Data>>> futures = new ArrayList<Future<List<Data>>>();
        for (ArrayList<Data> data : map.values()) {
            final Future<List<Data>> future = threadpool.submit(new ValidationTaskExecutor(data));
            futures.add(future);
        }
        List<List<Data>> res = new ArrayList<List<Data>>();
        for (Future<List<Data>> future : futures) {
            allValidRequest.addAll(future.get());
    
        }
    
    0 讨论(0)
  • 2020-12-14 12:29

    Firstly, you may want to externalize the creation of ExecutorService from your mainMethod() If this is getting called frequently, you are potentially creating a lot of threads.

    Future approach is better as this is exactly what Futures are for. Also, it makes reading code a lot easier.

    On a lighter note, although you may have to define your objects as final, you can always have setter methods on the object which can be called no matter your reference is final or not, potentially allowing you to change values of final Objects. (References are final objects are not!)

    0 讨论(0)
  • 2020-12-14 12:29

    Slightly different approach is:

    • create a LinkedBlockingQueue

    • pass it to each task. Tasks can be Threads, or Runnables upon j.u.c.Executor.

    • each task adds its result to the queue

    • the main thread reads results using queue.take() in a loop

    This way results are handled as soon as they are computed.

    0 讨论(0)
提交回复
热议问题