What is the proper way to implement concurrency in Java applications? I know about Threads and stuff, of course, I have been programming for Java for 10 years now, but haven't had too much experience with concurrency.
For example, I have to asynchronously load a few resources, and only after all have been loaded, can I proceed and do more work. Needless to say, there is no order how they will finish. How do I do this?
In JavaScript, I like using the jQuery.deferred
infrastructure, to say
$.when(deferred1,deferred2,deferred3...)
.done(
function(){//here everything is done
...
});
But what do I do in Java?
答案 0 :(得分:5)
你可以通过多种方式实现它。
1。ExecutorService invokeAll()
API
执行给定的任务,在完成所有任务后返回持有其状态和结果的Futures列表。
允许一个或多个线程等待直到在其他线程中执行的一组操作完成的同步辅助。
使用给定计数初始化
CountDownLatch
。由于countDown()
方法的调用,await方法阻塞直到当前计数达到零,之后释放所有等待的线程,并且任何后续的await调用立即返回。这是一次性现象 - 计数无法重置。如果您需要一个重置计数的版本,请考虑使用CyclicBarrier。
3. ForkJoinPool中的Executors或newWorkStealingPool()
是另一种方式
查看相关的SE问题:
How to wait for a thread that spawns it's own thread?
Executors: How to synchronously wait until all tasks have finished if tasks are created recursively?
答案 1 :(得分:4)
I would use parallel stream.
Stream.of(runnable1, runnable2, runnable3).parallel().forEach(r -> r.run());
// do something after all these are done.
If you need this to be asynchronous, then you might use a pool or Thread.
I have to asynchronously load a few resources,
You could collect these resources like this.
List<String> urls = ....
Map<String, String> map = urls.parallelStream()
.collect(Collectors.toMap(u -> u, u -> download(u)));
This will give you a mapping of all the resources once they have been downloaded concurrently. The concurrency will be the number of CPUs you have by default.
答案 2 :(得分:3)
If I'm not using parallel Streams or Spring MVC's TaskExecutor, I usually use CountDownLatch
. Instantiate with # of tasks, reduce once for each thread that completes its task. CountDownLatch.await()
waits until the latch is at 0. Really useful.
Read more here: JavaDocs
答案 3 :(得分:2)
如果使用Java 8或更高版本,就个人而言,我会做类似的事情。
// Retrieving instagram followers
CompletableFuture<Integer> instagramFollowers = CompletableFuture.supplyAsync(() -> {
// getInstaFollowers(userId);
return 0; // default value
});
// Retrieving twitter followers
CompletableFuture<Integer> twitterFollowers = CompletableFuture.supplyAsync(() -> {
// getTwFollowers(userId);
return 0; // default value
});
System.out.println("Calculating Total Followers...");
CompletableFuture<Integer> totalFollowers = instagramFollowers
.thenCombine(twitterFollowers, (instaFollowers, twFollowers) -> {
return instaFollowers + twFollowers; // can be replaced with method reference
});
System.out.println("Total followers: " + totalFollowers.get()); // blocks until both the above tasks are complete
我使用supplyAsync()
是因为我从任务中返回了一些值(在这种情况下为追随者数量),否则我可能会使用runAsync()
。两者都在单独的线程中运行任务。
最后,我使用thenCombine()
来加入两个CompletableFuture
。如果一个依赖另一个,则也可以使用thenCompose()
来加入两个CompletableFuture
。但是在这种情况下,由于两个任务可以并行执行,因此我使用了thenCombine()
。
方法getInstaFollowers(userId)
和getTwFollowers(userId)
是简单的HTTP调用之类的东西。
答案 4 :(得分:1)
You can use a ThreadPool and Executors to do this.
https://docs.oracle.com/javase/tutorial/essential/concurrency/pools.html
答案 5 :(得分:1)
This is an example I use Threads. Its a static executerService with a fixed size of 50 threads.
public class ThreadPoolExecutor {
private static final ExecutorService executorService = Executors.newFixedThreadPool(50,
new ThreadFactoryBuilder().setNameFormat("thread-%d").build());
private static ThreadPoolExecutor instance = new ThreadPoolExecutor();
public static ThreadPoolExecutor getInstance() {
return instance;
}
public <T> Future<? extends T> queueJob(Callable<? extends T> task) {
return executorService.submit(task);
}
public void shutdown() {
executorService.shutdown();
}
}
The business logic for the executer is used like this: (You can use Callable or Runnable. Callable can return something, Runnable not)
public class MultipleExecutor implements Callable<ReturnType> {//your code}
And the call of the executer:
ThreadPoolExecutor threadPoolExecutor = ThreadPoolExecutor.getInstance();
List<Future<? extends ReturnType>> results = new LinkedList<>();
for (Type Type : typeList) {
Future<? extends ReturnType> future = threadPoolExecutor.queueJob(
new MultipleExecutor(needed parameters));
results.add(future);
}
for (Future<? extends ReturnType> result : results) {
try {
if (result.get() != null) {
result.get(); // here you get the return of one thread
}
} catch (InterruptedException | ExecutionException e) {
logger.error(e, e);
}
}
答案 6 :(得分:1)
与java.lang.RuntimeException: java.lang.RuntimeException: Atom evaluation returned null!
$.Deferred
中的jQuery
相同的行为,您可以使用名为Java 8
的类在CompletableFuture
中归档。此类提供用于处理Promises
的API。为了创建异步代码,您可以使用static
#runAsync
创建方法之一,#supplyAsync
,#thenApply
。然后使用WYSIWYG
对结果进行一些计算。
答案 7 :(得分:0)
I usually opt for an async notify-start, notify-progress, notify-end approach:
class Task extends Thread {
private ThreadLauncher parent;
public Task(ThreadLauncher parent) {
super();
this.parent = parent;
}
public void run() {
doStuff();
parent.notifyEnd(this);
}
public /*abstract*/ void doStuff() {
// ...
}
}
class ThreadLauncher {
public void stuff() {
for (int i=0; i<10; i++)
new Task(this).start();
}
public void notifyEnd(Task who) {
// ...
}
}