说我有一个类似的任务:
for(Object object: objects) {
Result result = compute(object);
list.add(result);
}
并行化每个compute()的最简单方法是什么(假设它们已经可以并行化)?
我不需要一个严格符合上述代码的答案,只是一般答案。但是如果你需要更多信息:我的任务是IO绑定的,这是针对Spring Web应用程序的,任务将在HTTP请求中执行。
答案 0 :(得分:54)
我建议您查看ExecutorService。
特别是这样的事情:
ExecutorService EXEC = Executors.newCachedThreadPool();
List<Callable<Result>> tasks = new ArrayList<Callable<Result>>();
for (final Object object: objects) {
Callable<Result> c = new Callable<Result>() {
@Override
public Result call() throws Exception {
return compute(object);
}
};
tasks.add(c);
}
List<Future<Result>> results = EXEC.invokeAll(tasks);
请注意,如果newCachedThreadPool
是一个很重要的列表,那么使用objects
可能会很糟糕。缓存的线程池可以为每个任务创建一个线程!您可能希望使用newFixedThreadPool(n)
,其中n是合理的(例如,您拥有的核心数,假设compute()
受CPU限制)。
这是实际运行的完整代码:
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 ExecutorServiceExample {
private static final Random PRNG = new Random();
private static class Result {
private final int wait;
public Result(int code) {
this.wait = code;
}
}
public static Result compute(Object obj) throws InterruptedException {
int wait = PRNG.nextInt(3000);
Thread.sleep(wait);
return new Result(wait);
}
public static void main(String[] args) throws InterruptedException,
ExecutionException {
List<Object> objects = new ArrayList<Object>();
for (int i = 0; i < 100; i++) {
objects.add(new Object());
}
List<Callable<Result>> tasks = new ArrayList<Callable<Result>>();
for (final Object object : objects) {
Callable<Result> c = new Callable<Result>() {
@Override
public Result call() throws Exception {
return compute(object);
}
};
tasks.add(c);
}
ExecutorService exec = Executors.newCachedThreadPool();
// some other exectuors you could try to see the different behaviours
// ExecutorService exec = Executors.newFixedThreadPool(3);
// ExecutorService exec = Executors.newSingleThreadExecutor();
try {
long start = System.currentTimeMillis();
List<Future<Result>> results = exec.invokeAll(tasks);
int sum = 0;
for (Future<Result> fr : results) {
sum += fr.get().wait;
System.out.println(String.format("Task waited %d ms",
fr.get().wait));
}
long elapsed = System.currentTimeMillis() - start;
System.out.println(String.format("Elapsed time: %d ms", elapsed));
System.out.println(String.format("... but compute tasks waited for total of %d ms; speed-up of %.2fx", sum, sum / (elapsed * 1d)));
} finally {
exec.shutdown();
}
}
}
答案 1 :(得分:2)
要获得更详细的答案,请阅读Java Concurrency in Practice并使用java.util.concurrent。
答案 2 :(得分:1)
这是我在自己的项目中使用的东西:
public class ParallelTasks
{
private final Collection<Runnable> tasks = new ArrayList<Runnable>();
public ParallelTasks()
{
}
public void add(final Runnable task)
{
tasks.add(task);
}
public void go() throws InterruptedException
{
final ExecutorService threads = Executors.newFixedThreadPool(Runtime.getRuntime()
.availableProcessors());
try
{
final CountDownLatch latch = new CountDownLatch(tasks.size());
for (final Runnable task : tasks)
threads.execute(new Runnable() {
public void run()
{
try
{
task.run();
}
finally
{
latch.countDown();
}
}
});
latch.await();
}
finally
{
threads.shutdown();
}
}
}
// ...
public static void main(final String[] args) throws Exception
{
ParallelTasks tasks = new ParallelTasks();
final Runnable waitOneSecond = new Runnable() {
public void run()
{
try
{
Thread.sleep(1000);
}
catch (InterruptedException e)
{
}
}
};
tasks.add(waitOneSecond);
tasks.add(waitOneSecond);
tasks.add(waitOneSecond);
tasks.add(waitOneSecond);
final long start = System.currentTimeMillis();
tasks.go();
System.err.println(System.currentTimeMillis() - start);
}
在我的双核盒上打印超过2000个。
答案 3 :(得分:0)
您可以使用ThreadPoolExecutor。以下是示例代码:http://programmingexamples.wikidot.com/threadpoolexecutor(太长了,无法将其带到此处)
答案 4 :(得分:0)
可以简单地创建一些线程并获得结果。
Thread t = new Mythread(object);
if (t.done()) {
// get result
// add result
}
编辑:我认为其他解决方案更酷。
答案 5 :(得分:0)
我要提到一个执行者课程。下面是一些示例代码,您将放在执行程序类中。
private static ExecutorService threadLauncher = Executors.newFixedThreadPool(4);
private List<Callable<Object>> callableList = new ArrayList<Callable<Object>>();
public void addCallable(Callable<Object> callable) {
this.callableList.add(callable);
}
public void clearCallables(){
this.callableList.clear();
}
public void executeThreads(){
try {
threadLauncher.invokeAll(this.callableList);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public Object[] getResult() {
List<Future<Object>> resultList = null;
Object[] resultArray = null;
try {
resultList = threadLauncher.invokeAll(this.callableList);
resultArray = new Object[resultList.size()];
for (int i = 0; i < resultList.size(); i++) {
resultArray[i] = resultList.get(i).get();
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return resultArray;
}
然后使用它,您将调用执行程序类来填充和执行它。
executor.addCallable( some implementation of callable) // do this once for each task
Object[] results = executor.getResult();
答案 6 :(得分:0)
Fork/Join的并行数组是一个选项
答案 7 :(得分:0)
使用Java8和更高版本,您可以创建一个流,然后与 parallelStream 并行进行处理:
<div class="widget">
<div class="icon off">
<svg viewBox="0 0 48 48">
<use xlink:href="/static/matrix-theme/squidink.svg#thunder-1">
</use>
</svg>
</div>
<div class="name">Power</div>
<div class="valueGroup">
<div class="value">
{{itemValue('LivingRoomNum2')*1 + itemValue('KitchenNum2') | number:2}} kW
</div>
<div class="value">
{{itemValue('LivingRoomNum1')}} W (Living)
</div>
<div class="value">
{{itemValue('KitchenNum1')}} W (Kitchen)
</div>
</div>
</div>
注意:结果的顺序可能与列表中对象的顺序不匹配。
此堆栈溢出问题how-many-threads-are-spawned-in-parallelstream-in-java-8
中提供了有关如何设置正确数量的线程的详细信息。答案 8 :(得分:0)
一种巧妙的方法是利用ExecutorCompletionService。
假设您有以下代码(如您的示例所示):
public static void main(String[] args) {
List<Character> letters = IntStream.range(65, 91).mapToObj(i -> (char) i).collect(Collectors.toList());
List<List<Character>> list = new ArrayList<>();
for (char letter : letters) {
List<Character> result = computeLettersBefore(letter);
list.add(result);
}
System.out.println(list);
}
private static List<Character> computeLettersBefore(char letter) {
return IntStream.range(65, 1 + letter).mapToObj(i -> (char) i).collect(Collectors.toList());
}
现在要并行执行任务,您要做的就是创建由线程池支持的ExecutorCompletionService。然后提交任务并阅读结果。由于ExecutorCompletionService在后台使用LinkedBlockingQueue,因此结果在可用时就可以立即使用(如果运行代码,您会注意到结果的顺序是随机的):
public static void main(String[] args) throws InterruptedException, ExecutionException {
final ExecutorService threadPool = Executors.newFixedThreadPool(3);
final ExecutorCompletionService<List<Character>> completionService = new ExecutorCompletionService<>(threadPool);
final List<Character> letters = IntStream.range(65, 91).mapToObj(i -> (char) i).collect(Collectors.toList());
List<List<Character>> list = new ArrayList<>();
for (char letter : letters) {
completionService.submit(() -> computeLettersBefore(letter));
}
// NOTE: instead over iterating over letters again number of submitted tasks can be used as a base for loop
for (char letter : letters) {
final List<Character> result = completionService.take().get();
list.add(result);
}
threadPool.shutdownNow(); // NOTE: for safety place it inside finally block
System.out.println(list);
}
private static List<Character> computeLettersBefore(char letter) {
return IntStream.range(65, 1 + letter).mapToObj(i -> (char) i).collect(Collectors.toList());
}