我有一个类似以下的Java线程:
public class MyThread extends Thread {
MyService service;
String id;
public MyThread(String id) {
this.id = node;
}
public void run() {
User user = service.getUser(id)
}
}
我有大约300个ID,每隔几秒钟 - 我启动线程来为每个id打个电话。例如
for(String id: ids) {
MyThread thread = new MyThread(id);
thread.start();
}
现在,我想收集每个线程的结果,并对数据库进行批量插入,而不是每2秒进行300次数据库插入。
知道我怎么能做到这一点吗?
答案 0 :(得分:35)
规范方法是使用Callable
和ExecutorService
。 submit
Callable
ExecutorService
Future
会返回{类型安全的} get
,您可以从中class TaskAsCallable implements Callable<Result> {
@Override
public Result call() {
return a new Result() // this is where the work is done.
}
}
ExecutorService executor = Executors.newFixedThreadPool(300);
Future<Result> task = executor.submit(new TaskAsCallable());
Result result = task.get(); // this blocks until result is ready
结果。{/ p>
invokeAll
在您的情况下,您可能希望使用List
返回Futures
get
,或者在向执行程序添加任务时自己创建该列表。要收集结果,只需在每个结果上调用{{1}}。
答案 1 :(得分:18)
如果要在执行数据库更新之前收集所有结果,可以使用invokeAll
方法。如果您一次提交一个任务,这将负责记账,如daveb建议。
private static final ExecutorService workers = Executors.newCachedThreadPool();
...
Collection<Callable<User>> tasks = new ArrayList<Callable<User>>();
for (final String id : ids) {
tasks.add(new Callable<User>()
{
public User call()
throws Exception
{
return svc.getUser(id);
}
});
}
/* invokeAll blocks until all service requests complete,
* or a max of 10 seconds. */
List<Future<User>> results = workers.invokeAll(tasks, 10, TimeUnit.SECONDS);
for (Future<User> f : results) {
User user = f.get();
/* Add user to batch update. */
...
}
/* Commit batch. */
...
答案 2 :(得分:4)
将结果存储在对象中。当它完成时,让它自己放入同步集合中(想到一个同步的队列)。
如果您希望收集要提交的结果,请从队列中获取所有内容并从对象中读取结果。你甚至可能让每个对象都知道如何将它自己的结果“发布”到数据库中,这样就可以提交不同的类,并且所有类都使用完全相同的小巧,优雅的循环来处理。
JDK中有很多工具可以帮助解决这个问题,但是一旦你开始认为你的线程是一个真正的对象而不仅仅是围绕一个“运行”方法的一堆垃圾,它真的很容易。一旦你开始考虑对象,编程变得更简单,更令人满意。
答案 3 :(得分:3)
在Java8中,使用CompletableFuture有更好的方法。假设我们有从数据库获取id的类,为简单起见,我们只需返回一个数字,如下所示,
static class GenerateNumber implements Supplier<Integer>{
private final int number;
GenerateNumber(int number){
this.number = number;
}
@Override
public Integer get() {
try {
TimeUnit.SECONDS.sleep(1);
}catch (InterruptedException e){
e.printStackTrace();
}
return this.number;
}
}
现在,我们可以在每个未来的结果准备就绪后将结果添加到并发集合中。
Collection<Integer> results = new ConcurrentLinkedQueue<>();
int tasks = 10;
CompletableFuture<?>[] allFutures = new CompletableFuture[tasks];
for (int i = 0; i < tasks; i++) {
int temp = i;
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(()-> new GenerateNumber(temp).get(), executor);
allFutures[i] = future.thenAccept(results::add);
}
现在我们可以在所有期货准备就绪时添加回调,
CompletableFuture.allOf(allFutures).thenAccept(c->{
System.out.println(results); // do something with result
});
答案 4 :(得分:1)
你需要将结果存储在像singleton这样的东西中。这必须正确同步。
编辑:我知道这不是最好的建议,因为处理原始Threads
不是一个好主意。但鉴于这个问题会起作用,不是吗?我可能不会被投票,但为什么要投票?
答案 5 :(得分:1)
您可以创建一个传递给您创建的线程的队列或列表,线程将其结果添加到执行批量插入的使用者清空的列表中。
答案 6 :(得分:1)
最简单的方法是将一个对象传递给稍后将包含结果的每个线程(每个线程一个对象)。主线程应该保持对每个结果对象的引用。连接所有线程后,您可以使用结果。
答案 7 :(得分:1)
public class TopClass {
List<User> users = new ArrayList<User>();
void addUser(User user) {
synchronized(users) {
users.add(user);
}
}
void store() throws SQLException {
//storing code goes here
}
class MyThread extends Thread {
MyService service;
String id;
public MyThread(String id) {
this.id = node;
}
public void run() {
User user = service.getUser(id)
addUser(user);
}
}
}
答案 8 :(得分:1)
你可以创建一个扩展Observable的类。然后你的线程可以调用Observable类中的一个方法,该方法通过调用Observable.notifyObservers(Object)来通知在该观察者中注册的任何类。
观察类将实现Observer,并使用Observable注册自己。然后,您将实现一个更新(Observable,Object)方法,该方法在调用Observerable.notifyObservers(Object)时被调用。