1
  1. いいタイトルが思いつかなかったので編集よろしくお願いします。
  2. 私が抱えている問題は、ライブラリによって公開されている多くのメソッドが、I/O で多くの頭の痛い作業やブロックを行うため、多くの時間がかかることです。たとえば、Solr にドキュメントのインデックスを作成するように依頼した場合です。
  3. したがって、この問題を回避するために、スレッドの run() メソッドで本質的にメソッドを呼び出すラッパー コードを作成することになります。ワーカー スレッドをキックして並列処理を実現し、結果を抽出して元の呼び出しと比較します。
  4. 本質的に、誰かがこれを行うためのエレガントな方法を思いついたかもしれないことに気づきました。

    1. メソッドを取る
    2. ある種の Factory メソッドを取得して、非同期呼び出しに変換します
    3. さらに、これに ExecutorService をラップして、呼び出しをキューに入れ、ワーカー スレッドのプールがすべてを処理できるようにします。

次のようなクラスがあるとします。

public class SomeUtilClass {

  public int someComplicatedTask(int x, int y, int z) {
    // some very very complicated IO CPU bound task
  }

  // other equally lifting methods go here
}

各メソッドの前にスレッド プールとタスク キューを配置するエレガントな方法はありますか。

だから基本的に私はこのようなことをします:

wrappedObject = EncapsulateWithThreadPoolAndTaskQueueFactory.wrap(SomeUtilClass,
    10 /*worker threads*/);

String ticket1=wrappedObject.asyncExecSomeComplicatedTask(1,2,3);
String ticket2=wrappedObject.asyncExecSomeComplicatedTask(4,5,6);
String ticket2=wrappedObject.asyncExecSomeComplicatedTask(7,8,9);

wrappedObject.joinOnTicket(ticket1); 
int result1=wrappedObject.getRestTicket(ticket1);

など...

4

3 に答える 3

2

私のより一般的な並行性の答え以外のもう 1 つのオプションは、Java のProxyクラスを使用することです。

クラスがあり、Fooそれをラップして、バックグラウンド スレッドですべてのメソッドを実行するとします。doSomething実行に時間がかかるメソッドがあります。

public class Foo {

    public void doSomething() {
        // network I/O or something
        Thread.sleep(5000);
    }
}

を使用するProxyには、インターフェースが必要です。(このチュートリアルを参照してください。) 以下からインターフェースを作成しますFoo

public interface Foo {
    void doSomething();
}

古いFooクラスに新しいインターフェイスを実装させます。

public class FooImpl implements Foo { ... }

Java がProxy機能するためには、インターフェイス (取得済み) とInvocationHandler. これInvocationHandlerは、バックグラウンド スレッドでメソッドを実行するものです。

Proxyのインスタンスで を作成する方法は次のFooImplとおりです。

Foo foo = new FooImpl();
foo = (Foo) Proxy.newInstance(
        System.getClassLoader(),
        new Class[] { Foo.class },
        new BackgroundInvocationHandler(foo));

本当に簡単です。あとは次のように書くだけBackgroundInvocationHandlerです:

public class BackgroundInvocationHandler implements InvocationHandler {

    private static final int THREAD_COUNT = 20;
    private static final Executor executor = Executors.newFixedThreadPool(THREAD_COUNT);

    private final Object obj;

    public BackgroundInvocationHandler(Object obj) {
        this.obj = obj;
    }

    public Object invoke(Object proxy, Method m, Object[] args) throws Throwable {
        executor.execute(new MethodInvoker(m, args, obj));
        return null;
    }

    private static class MethodInvoker implements Runnable {

        private final Method m;
        private final Object[] args;
        private final Object target;

        MethodInvoker(Method m, Object[] args, Object target) {
            this.m = m;
            this.args = args;
            this.target = target;
        }

        @Override
        public void run() {
            try {
                m.invoke(target, args);
            } catch(Exception e) {
                // TODO log me
            }
        }
    }
}

おそらく予想通りだと思いますが、これは を返すメソッドでのみうまく機能しますvoidProxyインスタンスは、nullではないものを返すものに対して返されますvoid。ただし、これをコールバックまたはイベントディスパッチといつでも組み合わせて、このようなものから結果を取得できます。これは、ジョブを送信して終了するようなものであるため、何らかのコールバック システムを実装しないと何も返されないことに注意してください。

もう 1 つ注意してください。すべてのオブジェクトがラップされていることを確認したい場合は、オブジェクトを作成するためのファクトリが必要になりFooます。

public class FooFactory {
    public static Foo createFoo() {
        Foo foo = new FooImpl();
        foo = (Foo) Proxy.newInstance(
                System.getClassLoader(),
                new Class[] { Foo.class },
                new BackgroundInvocationHandler(foo));
        return foo;
    }
}

そして、クラス宣言からFooImpl削除することにより、パッケージの外部の誰もアクセスできないように、package-privateを作成します。public

class FooImpl implements Foo { ... }

そして、そこに行きます。これは、単純なメソッド呼び出しでバックグラウンド呼び出しを行うためにオブジェクトをラップする良い方法です。このコードはdoSomethingバックグラウンドで実行され、指定されたステートメントをすぐに出力します。

Foo myFoo = FooFactory.createFoo();
myFoo.doSomething();
System.out.println("Prints immediately without waiting");

これは、私の他の回答と比較して、あなたが望んでいたものにはるかに近いですが、戻り値の型の要件により、より制限されています。

于 2012-09-14T02:38:10.767 に答える
2

私たちはTaskFactory以前、私たちのものにコールバックを持つシステムを使用しました。つまり、リクエストとレスポンスに使用しましたが、さまざまな用途に使用できます。基本的に、次のTaskFactoryインターフェースがあります。

public interface TaskFactory<T, R> {
    Callable<R> taskFor(T t);
}

public interface Callback<R> {
    void onCallback(R r);
    void onException(Throwable t);
}

リクエスト/レスポンスの場合、 のインスタンスを使用して、TaskFactory<Request, Response>ネットワークI/O を実行し、 を返すオブジェクトのCallableインスタンスを提供しました。RequestResponse

public class RequestTaskFactory implements TaskFactory<Request, Response> {
    public Callable<Response> taskFor(Request request) {
        return new RequestTask(request);
    }
}

そしてタスク:

class RequestTask implements Callable<Response> {
    private final Request request;

    public RequestTask(Request request) {
        this.request = request;
    }

    public Response call() {
        // Open socket
        // Send request
        // Receive response
        Response resp = getResponse(socket);
        return resp;
    }
}

最後のインターフェイスとクラスは、すべてを結び付けます。

public interface TaskHandler<T, R> {

    TaskFactory<T, R> getTaskFactory();

    Future<R> submit(T t);

    void submit(T t, Callback<R> callback);
}

SwingUtilities.invokeLaterSwing を使用している場合に備えて、イベント ディスパッチ スレッドでコールバックを実行する別のメソッドを使用することもできます。実装:

public class TaskHandlerImpl<T, R> implements TaskHandler<T, R> {

    private final ExecutorService executor;
    private final TaskFactory<T, R> taskFactory;

    public TaskHandlerImpl(TaskFactory<T, R> taskFactory, int threadPoolSize) {
        this.taskFactory = taskFactory;
        this.executor = Executors.newFixedThreadPool(threadPoolSize);
    }

    public TaskFactory<T, R> getTaskFactory() {
        return taskFactory;
    }

    public Future<R> submit(T t) {
        return executor.submit(taskFactory.taskFor(t));
    }

    public void submit(T t, Callback<R> callback) {
        executor.execute(new CallbackTask(taskFactory.taskFor(t)
    }

    private static class CallbackTask<R> implements Runnable {
        private final Callback<R> callback;
        private final Callable<R> callable;

        CallbackTask(Callback<R> callback, Callable<R> callable) {
            this.callback = callback;
            this.callable = callable;
        }

        public void run() {
            try {
                callback.onCallback(callable.call());
            } catch (Exception ex) {
                callback.onException(ex);
            }
        }
    }
}

TaskFactory次に、 、Callable、およびCallback実装を記述するだけです。

于 2012-09-13T22:40:34.580 に答える
1

さまざまな (おそらく複数の) クライアント クラスがコードの小さなラッパーを作成する代わりに、Runnable または Callable バージョンのメソッドを実装する場所に実装して、(簡単にすることで) ユーザーがそれらを非同期で使用できるようにすることを好みます。明らかにこれは、それらが非同期で使用されることを予期していたことを意味します。引数を渡すこともできます。例えば

public class Foo {

   public String someLongMethodA(Object...args) {
      // code that takes a long time here
      return someResult;
   }

   public Callable<String> callableLongMethodA(final Object...args) {
      return new Callable<String>() {
         @Override
         public String call() throws Exception {
            return someLongMethodA(args);
         }

      };
   }


   public Integer someLongMethod2(int loopCount) {
      // yet more long running code here
      return 42;  // the meaning of life
   }

   public Callable<Integer> callableLongMethod2(final int loopCount) {
      return new Callable<Integer>() {
         @Override
         public Integer call() throws Exception {
            return someLongMethod2(loopCount);
         }         
      };
   }  


}

これは、ボイラープレート コードを大量に作成しているため、完璧とは言えません。しかし、それが Eclipse テンプレートの目的です。また、場合によっては、クライアントが最初の呼び出しを行った後に引数を変更しないように注意する必要があります。

于 2012-09-13T23:17:15.980 に答える