マルチスレッドの方法を実現するJava呼び出し可能インターフェース


Java 1.5より前のバージョンでは、スレッドを作成する方法は2つあります。1つは直接スレッドを継承する方法で、もう1つはRunnableインターフェースを実装する方法です。マルチスレッドの実装にどのような形式を使用する場合でも、オペレーティングシステムからio、cupなどのリソースを要求するには、Threadクラスのstartメソッドを呼び出す必要があります。スレッド実行メソッドには戻り値がないため、実行結果を取得する必要がある場合は、共有変数またはスレッド通信を使用することで効果が得られ、使用が面倒です。

Java 1.5以降、CallableとFutureが提供され、タスクの実行が完了した後にタスクの実行結果を取得できます。

CallableとFutureの紹介

Callableインターフェースは、結果を呼び出して返すことができるコードの一部を表し、Futureインターフェースは、まだ完了していないタスクによって与えられる将来の結果である非同期タスクを表します。したがって、Callableは結果の生成に使用され、Futureは結果の取得に使用されます。

Callableインターフェースは、ジェネリックを使用して戻り値の型を定義します。Executorsクラスは、スレッドプールのCallable内でタスクを実行するためのいくつかの便利なメソッドを提供します。呼び出し可能タスクは並列であるため(並列とは、全体が並列に見えることを意味します。実際、特定の時点で実行されているスレッドは1つだけです)、それが返す結果を待つ必要があります。java.util.concurrent.Futureオブジェクトがこの問題を解決します。スレッドプールでCallableタスクを送信すると、Futureオブジェクトが返されます。これを使用して、Callableタスクのステータスを確認し、Callableから返された実行結果を取得できます。Futureにはget()メソッドが用意されているため、Callableが終了してその実行結果を取得するまで待機できます。

呼び出し可能と実行可能

java.lang.Runnable、これはインターフェースであり、1つのrun()メソッドのみが宣言されています。

public interface Runnable {

    public abstract void run();

}复制代码

  run()メソッドの戻り値はvoid型であるため、タスクの実行後に結果を返すことはできません。

  Callableはjava.util.concurrentパッケージの下にあります。これもインターフェースであり、1つのメソッドのみが宣言されていますが、このメソッドはcall()と呼ばれます。

public interface Callable<V> {
    /**
     * Computes a result, or throws an exception if unable to do so.
     * @return computed result
     * @throws Exception if unable to compute a result
     */
    V call() throws Exception;
}复制代码

  ご覧のとおり、これは汎用インターフェースであり、call()関数によって返されるタイプは渡されたVタイプです。

では、Callableの使い方は?

一般に、ExecutorServiceと組み合わせて使用​​されます。サブミットメソッドのいくつかのオーバーロードバージョンは、ExecutorServiceインターフェイスで宣言されています。

<T> Future<T> submit(Callable<T> task);

<T> Future<T> submit(Runnable task, T result);

Future<?> submit(Runnable task);复制代码

未来

  将来的には、特定のRunnableタスクまたはCallableタスクの実行結果をキャンセルし、クエリが完了したかどうかを確認して、結果を取得する予定です。必要に応じて、タスクが結果を返すまでブロックするgetメソッドを介して実行結果を取得できます。

  Futureクラスは、インターフェースであるjava.util.concurrentパッケージの下にあります。

<T> Future<T> submit(Callable<T> task);

<T> Future<T> submit(Runnable task, T result);

Future<?> submit(Runnable task);复制代码

Futureインターフェースでは5つのメソッドが宣言されていますが、以下では各メソッドの機能を順に説明します。

cancelメソッドは  、タスクをキャンセルするために使用され、タスクが正常にキャンセルされた場合はtrueを返し、タスクがキャンセルされた場合はfalseを返します。mayInterruptIfRunningパラメータは、実行中のタスクをキャンセルできるかどうかを示します。trueに設定すると、実行中のタスクをキャンセルできることを意味します。タスクが完了した場合、mayInterruptIfRunningがtrueかfalseかにかかわらず、このメソッドはfalseを返す必要があります。つまり、キャンセルされたタスクがキャンセルされた場合は、falseを返します。タスクが実行されている場合、mayInterruptIfRunningがtrueに設定されている場合は、mayInterruptIfRunningがfalseに設定されている場合はtrueを返します。 、falseを返します。タスクが実行されていない場合は、mayInterruptIfRunningがtrueかfalseかにかかわらず、必ずtrueを返します。

isCancelledメソッド  は、タスクが正常にキャンセルされたかどうかを示し、タスクが正常に完了する前にタスクが正常にキャンセルされた場合はtrueを返します。

isDoneメソッド  は、タスクが完了したかどうかを示し、タスクが完了した場合はtrueを返します。

get()メソッドは   、実行結果を取得するために使用されます。このメソッドは、ブロックされ、タスクが完了するまで待機してから戻ります。

get(ロングタイムアウト、TimeUnit単位)で   実行結果を取得し、指定時間内に結果が得られない場合は直接nullを返します。

つまり、Futureには3つの機能があります。

  1)タスクが完了したかどうかを判断します。

  2)タスクを中断する機能。

  3)タスクの実行結果を取得できる。

Futureは、非同期計算の結果を表すために使用されます。その実装クラスはjava.util.concurrent.FutureTask <V>およびjavax.swing.SwingWorker <T、V>です。分岐スレッドがAndroidプラットフォームのメインスレッドをブロックしないようにして、分岐スレッドの実行結果を取得する場合は、FutureTaskを使用できます。 。

FutureTask

Javaクラスは単一の継承設計です。スレッドを継承してマルチスレッドを実装すると、他のクラスを継承できなくなります。インターフェースを使用すると、データ共有をより適切に実現できます。

FutureTaskは、次のように定義されたRunnableFutureインターフェースを実装します。

public interface RunnableFuture<V> extends Runnable, Future<V> {  
    void run();  
} 复制代码

このインターフェースはRunnableおよびFutureインターフェースを実装しており、インターフェースの具体的な実装はFutureTaskによって実装されていることがわかります。このクラスの2つの構築メソッドは次のとおりです。

public FutureTask(Callable<V> callable) {  
        if (callable == null) 
            throw new NullPointerException();  
        sync = new Sync(callable);  
}  
    
public FutureTask(Runnable runnable, V result) {  
	sync = new Sync(Executors.callable(runnable, result));  
}复制代码

上記のように、2つのコンストラクターが提供されます。1つはパラメーターとしてCallable、もう1つはパラメーターとしてRunnableです。これらのクラス間の関連付けは、タスクモデリングメソッドに対して非常に柔軟性があり、FutureTaskのRunnable機能に基づいてCallableとしてタスクを記述でき(Runnableインターフェイスを実装しているため)、必要に応じてエグゼキューターがキャンセルできるスケジュールにカプセル化できます。 FutureTask。

FutureTaskはexecutorによってスケジュールできますが、これは重要です。このメソッドが提供するメソッドは基本的に、FutureインターフェースとRunnableインターフェースの組み合わせです:get()、cancel、isDone()、isCancelled()とrun()。run()メソッドは通常、エグゼキューターによって呼び出されますが、基本的には行いません直接呼び出す必要があります。FutureTaskクラスはRunnableインターフェースも実装しているため、実行のためThreadおよびExecutorに直接送信できます

public class CallableAndFuture {  
        public static void main(String[] args) {  
            Callable<Integer> callable = new Callable<Integer>() {  
                public Integer call() throws Exception {  
                    return new Random().nextInt(100);  
                }  
            }; 

        FutureTask<Integer> future = new FutureTask<Integer>(callable);  
        new Thread(future).start();  
 
        try {  
            Thread.sleep(5000);// 可能做一些事情  
 
            int result = future.get());  
 
        } catch (InterruptedException e) {  
            e.printStackTrace();  
        } catch (ExecutionException e) {  
            e.printStackTrace();  
        }  
    }  
} 复制代码

public class CallableAndFuture {  
    public static void main(String[] args) { 
 
        //ExecutorService.submit()
        ExecutorService threadPool = Executors.newSingleThreadExecutor();  
        Future<Integer> future = threadPool.submit(new Callable<Integer>() {  
            public Integer call() throws Exception {  
                return new Random().nextInt(100);  
            }  
        }); 
 
        try {  
            Thread.sleep(5000);// 可能做一些事情  
 
            int result = future.get()); //Future.get()
 
        } catch (InterruptedException e) {  
            e.printStackTrace();  
        } catch (ExecutionException e) {  
            e.printStackTrace();  
        }  
    }  复制代码

戻り値を使用して複数のタスクを実行し、複数の戻り値を取得する場合は、CompletionServiceを使用できます

CompletionServiceは、ExecutorとBlockingQueueを組み合わせたものと同等です。使用シナリオでは、子スレッドが一連のタスクを同時に実行するときに、メインスレッドが子スレッドタスクの戻り値をリアルタイムで取得し、これらの戻り値を順次処理する必要があります。最初に戻る方が最初に処理されます誰だ。

public class CallableAndFuture {  
    public static void main(String[] args) {  
        ExecutorService threadPool = Executors.newCachedThreadPool();  
        CompletionService<Integer> cs = new ExecutorCompletionService<Integer>(threadPool);  
        for(int i = 1; i < 5; i++) {  
            final int taskID = i;  
            //CompletionService.submit()
            cs.submit(new Callable<Integer>() {  
                public Integer call() throws Exception {  
                    return taskID;  
                }  
            });  
        }  
        // 可能做一些事情  
        for(int i = 1; i < 5; i++) {  
            try {  
                int result = cs.take().get());  //CompletionService.take()返回Future
            } catch (InterruptedException e) {  
                e.printStackTrace();  
            } catch (ExecutionException e) {  
                e.printStackTrace();  
            }  
        }  
    }  
}   复制代码

または、CompletionServiceを使用しないでください。最初にFuture型のコレクションを作成し、Executorによって送信されたタスクの戻り値をコレクションに追加します。最後に、コレクションでデータを取り出しやすくします。

違い:

Futureコレクション方式では、送信タスクは、自身が保持するリストを追加する順序で完了する必要はありません。リストからトラバースする各Futureオブジェクトは必ずしも完了した状態であるとは限らず、get()メソッドはこの時点でブロックされます。各スレッドが完了後の結果に応じて次のことを続行できるようにシステムが設計されている場合、これにより、リストの後ろにあるが最初に完了するスレッドの待機時間が長くなります。

CompletionServiceの実装は、Futureオブジェクトを保持するBlockingQueueを維持することです。Futureオブジェクトの状態が終了した場合にのみ、それがキューに追加されますtake()メソッドは、実際にはProducer-Consumerのコンシューマです。キューからFutureオブジェクトを取得します。Queueが空の場合、完了したFutureオブジェクトがキューに追加されるまで、キューでブロックされます。

したがって、最初に完成したものを最初に取り出す必要があります。これにより、不要な待機時間が短縮されます。


おすすめ

転載: juejin.im/post/5e9af92f6fb9a03c3a0894e8