私は次のような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回のデータベース挿入を行うのではなく、データベースへのバッチ挿入を実行したいと思います。
どうすればこれを達成できるか考えていますか?
データベースの更新を行う前にすべての結果を収集する場合は、 invokeAll
メソッドを使用できます。これにより、 daveb が示唆するように、タスクを一度に1つずつ送信する場合に必要となる簿記が処理されます。
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. */
...
正規のアプローチは、Callable
とExecutorService
を使用することです。 submit
をCallable
にExecutorService
すると、(typesafe)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
あなたの場合、おそらくinvokeAll
のList
を返すFutures
を使用するか、エグゼキュータにタスクを追加するときにそのリストを自分で作成することをお勧めします。結果を収集するには、それぞれでget
を呼び出すだけです。
結果をオブジェクトに保存します。完了したら、同期されたコレクションにドロップします(同期されたキューが思い浮かびます)。
結果を収集して送信する場合は、キューからすべてを取得し、オブジェクトから結果を読み取ります。各オブジェクトに、それ自体の結果をデータベースに「投稿」する方法を知ってもらうこともできます。これにより、さまざまなクラスを送信して、まったく同じ小さなエレガントなループですべて処理できます。
JDKにはこれを支援するツールがたくさんありますが、スレッドを「実行」メソッドの周りの単なるくだらないものではなく、真のオブジェクトとして考え始めると、それは本当に簡単です。このようにオブジェクトについて考え始めると、プログラミングははるかに簡単で満足のいくものになります。
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
});
Observableを拡張するクラスを作成できます。次に、スレッドはObservableクラスのメソッドを呼び出すことができます。このメソッドは、Observable.notifyObservers(Object)を呼び出すことにより、そのオブザーバーに登録されているクラスに通知します。
監視クラスはObserverを実装し、それ自体をObservableに登録します。次に、Observerable.notifyObservers(Object)が呼び出されたときに呼び出されるupdate(Observable、Object)メソッドを実装します。
結果をシングルトンのようなものに保存する必要があります。これは適切に同期する必要があります。
[〜#〜] edit [〜#〜]:生のThreads
を処理するのは良い考えではないので、これは最善のアドバイスではないことを私は知っています。しかし、これがうまくいくという質問を考えると、そうではありませんか?私は賛成しないかもしれませんが、なぜ反対票を投じるのですか?
作成したスレッドに渡すキューまたはリストを作成できます。スレッドは、バッチ挿入を実行するコンシューマーによって空になるリストに結果を追加します。
最も簡単なアプローチは、後で結果を含むオブジェクトを各スレッドに渡すことです(スレッドごとに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);
}
}
}