java.util.concurrentの概要

java.util.concurrentの概要

1. 概要

java.util.concurrentパッケージは、並行アプリケーションを作成するためのツールを提供します。

この記事では、パッケージ全体の概要を説明します。

2. メインコンポーネント

java.util.concurrentには、1回の記事で説明するには多すぎる機能が含まれています。 この記事では、主にこのパッケージの最も有用なユーティリティのいくつかに焦点を当てます。

  • エグゼキューター

  • ExecutorService

  • ScheduledExecutorService

  • 未来

  • CountDownLatch

  • CyclicBarrier

  • セマフォ

  • ThreadFactory

  • BlockingQueue

  • DelayQueue

  • ロック

  • フェイザー

また、個々のクラスに関する多くの専用記事もここで見つけることができます。

2.1. Executor

Executorは、提供されたタスクを実行するオブジェクトを表すインターフェイスです。

タスクを新しいスレッドまたは現在のスレッドで実行する必要があるかどうかは、特定の実装(呼び出しの開始元)によって異なります。 したがって、このインターフェースを使用して、タスク実行フローを実際のタスク実行メカニズムから切り離すことができます。

ここで注意すべき点の1つは、Executorではタスクの実行が非同期である必要はないということです。 最も単純なケースでは、エグゼキューターは送信されたタスクを呼び出しスレッドで即座に呼び出すことができます。

executorインスタンスを作成するには、invokerを作成する必要があります。

public class Invoker implements Executor {
    @Override
    public void execute(Runnable r) {
        r.run();
    }
}

これで、このインボーカーを使用してタスクを実行できます。

public void execute() {
    Executor executor = new Invoker();
    executor.execute( () -> {
        // task to be performed
    });
}

ここで注意すべき点は、エグゼキュータがタスクの実行を受け入れることができない場合、RejectedExecutionExceptionをスローすることです。

2.2. ExecutorService

ExecutorServiceは、非同期処理の完全なソリューションです。 メモリ内のキューを管理し、スレッドの可用性に基づいて送信されたタスクをスケジュールします。

ExecutorService,を使用するには、1つのRunnableクラスを作成する必要があります。

public class Task implements Runnable {
    @Override
    public void run() {
        // task details
    }
}

これで、ExecutorServiceインスタンスを作成し、このタスクを割り当てることができます。 作成時に、スレッドプールのサイズを指定する必要があります。

ExecutorService executor = Executors.newFixedThreadPool(10);

シングルスレッドのExecutorServiceインスタンスを作成する場合は、newSingleThreadExecutor(ThreadFactory threadFactory)を使用してインスタンスを作成できます。

executorが作成されたら、それを使用してタスクを送信できます。

public void execute() {
    executor.submit(new Task());
}

タスクの送信中にRunnableインスタンスを作成することもできます。

executor.submit(() -> {
    new Task();
});

また、2つのすぐに使用可能な実行終了メソッドが付属しています。 最初のものはshutdown()です。送信されたすべてのタスクの実行が完了するまで待機します。 もう1つの方法は、shutdownNow()です。hは、保留中/実行中のすべてのタスクをただちに終了します。

シャットダウンイベントがトリガーされた後、実行タイムアウトが発生した後、または実行スレッド自体が中断された後、すべてのタスクが実行を完了するまで強制的にブロックする別のメソッドawaitTermination(long timeout, TimeUnit unit)もあります。

try {
    executor.awaitTermination( 20l, TimeUnit.NANOSECONDS );
} catch (InterruptedException e) {
    e.printStackTrace();
}

2.3. ScheduledExecutorService

ScheduledExecutorServiceExecutorService,と同様のインターフェースですが、定期的にタスクを実行できます。

Executor and ExecutorService‘s methods are scheduled on the spot without introducing any artificial delay.ゼロまたは任意の負の値は、要求を即座に実行する必要があることを示します。

RunnableインターフェースとCallableインターフェースの両方を使用して、タスクを定義できます。

public void execute() {
    ScheduledExecutorService executorService
      = Executors.newSingleThreadScheduledExecutor();

    Future future = executorService.schedule(() -> {
        // ...
        return "Hello world";
    }, 1, TimeUnit.SECONDS);

    ScheduledFuture scheduledFuture = executorService.schedule(() -> {
        // ...
    }, 1, TimeUnit.SECONDS);

    executorService.shutdown();
}

ScheduledExecutorServiceは、タスクafter some given fixed delayをスケジュールすることもできます。

executorService.scheduleAtFixedRate(() -> {
    // ...
}, 1, 10, TimeUnit.SECONDS);

executorService.scheduleWithFixedDelay(() -> {
    // ...
}, 1, 10, TimeUnit.SECONDS);

ここで、scheduleAtFixedRate( Runnable command, long initialDelay, long period, TimeUnit unit )メソッドは、指定された初期遅延の後に最初に呼び出され、その後サービスインスタンスがシャットダウンするまで指定された期間で呼び出される定期的なアクションを作成して実行します。

scheduleWithFixedDelay( Runnable command, long initialDelay, long delay, TimeUnit unit )メソッドは、指定された初期遅延の後に最初に呼び出され、実行中のアクションの終了から次のアクションの呼び出しまでの指定された遅延で繰り返し呼び出される定期的なアクションを作成して実行します。

2.4. Future

Future is used to represent the result of an asynchronous operation.非同期操作が完了したかどうかを確認したり、計算結果を取得したりするためのメソッドが付属しています。

さらに、cancel(boolean mayInterruptIfRunning) APIは操作をキャンセルし、実行中のスレッドを解放します。 mayInterruptIfRunningの値がtrueの場合、タスクを実行しているスレッドは即座に終了します。

それ以外の場合、進行中のタスクの完了が許可されます。

以下のコードスニペットを使用して、将来のインスタンスを作成できます。

public void invoke() {
    ExecutorService executorService = Executors.newFixedThreadPool(10);

    Future future = executorService.submit(() -> {
        // ...
        Thread.sleep(10000l);
        return "Hello world";
    });
}

次のコードスニペットを使用して、将来の結果の準備ができているかどうかを確認し、計算が完了したらデータを取得できます。

if (future.isDone() && !future.isCancelled()) {
    try {
        str = future.get();
    } catch (InterruptedException | ExecutionException e) {
        e.printStackTrace();
    }
}

特定の操作のタイムアウトを指定することもできます。 タスクにこの時間がかかる場合は、TimeoutExceptionがスローされます。

try {
    future.get(10, TimeUnit.SECONDS);
} catch (InterruptedException | ExecutionException | TimeoutException e) {
    e.printStackTrace();
}

2.5. CountDownLatch

CountDownLatchJDK 5で導入)は、いくつかの操作が完了するまでスレッドのセットをブロックするユーティリティクラスです。

CountDownLatchcounter(Integerタイプで初期化されます);このカウンターは、依存スレッドが実行を完了すると減少します。 ただし、カウンターがゼロに達すると、他のスレッドが解放されます。

CountDownLatchhereについて詳しく知ることができます。

2.6. CyclicBarrier

CyclicBarrierは、再利用できることを除いて、CountDownLatchとほぼ同じように機能します。 CountDownLatchとは異なり、複数のスレッドがawait()メソッド(バリア条件と呼ばれる)を使用して相互に待機してから、最終タスクを呼び出すことができます。

Runnableタスクインスタンスを作成してバリア条件を開始する必要があります。

public class Task implements Runnable {

    private CyclicBarrier barrier;

    public Task(CyclicBarrier barrier) {
        this.barrier = barrier;
    }

    @Override
    public void run() {
        try {
            LOG.info(Thread.currentThread().getName() +
              " is waiting");
            barrier.await();
            LOG.info(Thread.currentThread().getName() +
              " is released");
        } catch (InterruptedException | BrokenBarrierException e) {
            e.printStackTrace();
        }
    }

}

これで、いくつかのスレッドを呼び出して、バリア条件を競います。

public void start() {

    CyclicBarrier cyclicBarrier = new CyclicBarrier(3, () -> {
        // ...
        LOG.info("All previous tasks are completed");
    });

    Thread t1 = new Thread(new Task(cyclicBarrier), "T1");
    Thread t2 = new Thread(new Task(cyclicBarrier), "T2");
    Thread t3 = new Thread(new Task(cyclicBarrier), "T3");

    if (!cyclicBarrier.isBroken()) {
        t1.start();
        t2.start();
        t3.start();
    }
}

ここで、isBroken()メソッドは、実行時にスレッドのいずれかが中断されたかどうかを確認します。 実際のプロセスを実行する前に、常にこのチェックを実行する必要があります。

2.7. Semaphore

Semaphoreは、物理リソースまたは論理リソースの一部へのスレッドレベルのアクセスをブロックするために使用されます。 セマフォには許可のセットが含まれています。スレッドがクリティカルセクションに入ろうとするたびに、パーミットが使用可能かどうかを確認するためにセマフォをチェックする必要があります。

tryAcquire()を介して)許可が利用できない場合、スレッドはクリティカルセクションにジャンプできません。ただし、許可が利用可能な場合はアクセスが許可され、許可カウンターが減少します。

実行中のスレッドがクリティカルセクションを解放すると、許可カウンターが再び増加します(release()メソッドによって実行されます)。

tryAcquire(long timeout, TimeUnit unit)メソッドを使用して、アクセスを取得するためのタイムアウトを指定できます。

また、使用可能な許可の数や、セマフォの取得を待機しているスレッドの数を確認することもできます。

次のコードスニペットを使用して、セマフォの実装を使用できます。

static Semaphore semaphore = new Semaphore(10);

public void execute() throws InterruptedException {

    LOG.info("Available permit : " + semaphore.availablePermits());
    LOG.info("Number of threads waiting to acquire: " +
      semaphore.getQueueLength());

    if (semaphore.tryAcquire()) {
        try {
            // ...
        }
        finally {
            semaphore.release();
        }
    }

}

Semaphoreを使用して、データ構造のようなMutexを実装できます。 このcan be found here.の詳細

2.8. ThreadFactory

名前が示すように、ThreadFactoryは、オンデマンドで新しいスレッドを作成するスレッド(存在しない)プールとして機能します。 効率的なスレッド作成メカニズムを実装するためのボイラープレートコーディングの多くの必要性を排除します。

ThreadFactoryを定義できます:

public class exampleThreadFactory implements ThreadFactory {
    private int threadId;
    private String name;

    public exampleThreadFactory(String name) {
        threadId = 1;
        this.name = name;
    }

    @Override
    public Thread newThread(Runnable r) {
        Thread t = new Thread(r, name + "-Thread_" + threadId);
        LOG.info("created new thread with id : " + threadId +
            " and name : " + t.getName());
        threadId++;
        return t;
    }
}

このnewThread(Runnable r)メソッドを使用して、実行時に新しいスレッドを作成できます。

exampleThreadFactory factory = new exampleThreadFactory(
    "exampleThreadFactory");
for (int i = 0; i < 10; i++) {
    Thread t = factory.newThread(new Task());
    t.start();
}

2.9. BlockingQueue

非同期プログラミングでは、最も一般的な統合パターンの1つはproducer-consumer patternです。 java.util.concurrentパッケージには、BlockingQueueと呼ばれるデータ構造が付属しています。これは、これらの非同期シナリオで非常に役立ちます。

これに関する詳細情報と実際の例は、hereで入手できます。

2.10. DelayQueue

DelayQueueは、要素の無限サイズのブロッキングキューであり、有効期限(ユーザー定義の遅延と呼ばれます)が完了した場合にのみ要素をプルできます。 したがって、最上位の要素(head)の遅延が最も大きくなり、最後にポーリングされます。

これに関する詳細情報と実際の例は、hereで入手できます。

2.11. Locks

当然のことながら、Lockは、現在実行しているスレッドとは別に、他のスレッドがコードの特定のセグメントにアクセスするのをブロックするためのユーティリティです。

ロックブロックと同期ブロックの主な違いは、同期ブロックがメソッドに完全に含まれていることです。ただし、Lock APIのlock()操作とunlock()操作を別々のメソッドで実行できます。

これに関する詳細情報と実際の例は、hereで入手できます。

2.12. Phaser

Phaserは、CyclicBarrierおよびCountDownLatchよりも柔軟なソリューションです。これは、実行を続行する前に動的な数のスレッドが待機する必要がある再利用可能なバリアとして機能するために使用されます。 プログラムフェーズごとにPhaserインスタンスを再利用して、実行の複数のフェーズを調整できます。

これに関する詳細情報と実際の例は、hereで入手できます。

3. 結論

この高レベルの概要記事では、java.util.concurrentパッケージで利用できるさまざまなユーティリティに焦点を当てました。

いつものように、完全なソースコードはover on GitHubで利用できます。