1.はじめに

このチュートリアルでは、スレッドを起動して並列タスクを実行するさまざまな方法を探ります。

これは、特にメインスレッド上で実行できないような長時間または繰り返しのある操作を処理する場合、または操作の結果を待っている間にUIインタラクションを保留にできない場合に非常に便利です。

スレッドの詳細については、https://www.baeldung.com/java-thread-lifecycle[Javaでのスレッドのライフサイクル]に関するチュートリアルを必ずお読みください。]

2.スレッド実行の基本


Thread

フレームワークを使用すると、並列スレッドで実行されるロジックを簡単に書くことができます。


Thread

クラスを拡張して、基本的な例を試してみましょう。

public class NewThread extends Thread {
    public void run() {
        long startTime = System.currentTimeMillis();
        int i = 0;
        while (true) {
            System.out.println(this.getName() + ": New Thread is running..." + i++);
            try {
               //Wait for one sec so it doesn't print too fast
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            ...
        }
    }
}

そして今度はスレッドを初期化して開始するための2番目のクラスを作成します。

public class SingleThreadExample {
    public static void main(String[]args) {
        NewThread t = new NewThread();
        t.start();
    }
}

それでは、複数のスレッドを起動する必要があるとしましょう。

public class MultipleThreadsExample {
    public static void main(String[]args) {
        NewThread t1 = new NewThread();
        t1.setName("MyThread-1");
        NewThread t2 = new NewThread();
        t2.setName("MyThread-2");
        t1.start();
        t2.start();
    }
}

私たちのコードはまだ非常にシンプルに見え、オンラインで見つけることができる例と非常によく似ています。

もちろん、** これは本番用コードからは程遠いため、コンテキストの切り替えやメモリの使用量が増えすぎないように、リソースを正しい方法で管理することが非常に重要です。

  • それで、生産準備を整えるために、我々は今扱う追加の定型文を書く必要があります。

  • 新しいスレッドの一貫した作成

  • 同時ライブスレッド数

  • スレッド割り当て解除:デーモンスレッドにとって順不同

漏れを防ぐために

必要に応じて、これらすべてのケースのシナリオとそれ以上のシナリオにも独自のコードを書くことができます。

3.

ExecutorService

フレームワーク


ExecutorService

は、スレッドプール設計パターン(複製ワーカーモデルまたはワーカークルーモデルとも呼ばれます)を実装し、前述のスレッド管理の面倒を見ます。さらに、スレッドの再利用性やタスクキューなどの非常に便利な機能を追加します。

特にスレッドの再利用性は非常に重要です。大規模なアプリケーションでは、多数のスレッドオブジェクトを割り当てたり割り当て解除したりすると、メモリ管理のオーバーヘッドが大きくなります。

ワーカースレッドを使用すると、スレッドの作成によるオーバーヘッドを最小限に抑えることができます。

プールの設定を簡単にするために、

ExecutorService

には簡単なコンストラクタと、キューの種類、スレッドの最小数と最大数、それらの命名規則などのカスタマイズオプションが付属しています。

__ExecutorServiceの詳細については、https://www.baeldung.com/java-executor-service-tutorial[Java ExecutorServiceの手引き]を参照してください。

4.エグゼキュータによるタスクの開始

  • この強力なフレームワークのおかげで、スレッドの開始からタスクの送信に考え方を変えることができます。

非同期タスクをエグゼキュータに送信する方法を見てみましょう。

ExecutorService executor = Executors.newFixedThreadPool(10);
...
executor.submit(() -> {
    new Task();
});

使用できる方法は2つあります。何も返さない

execute

と、計算結果をカプセル化した

Future

を返す

submit

です。

__Futuresについて詳しくは、https://www.baeldung.com/java-future[java.util.concurrent.Futureのガイド]をお読みください。

5.

CompletableFutures

でタスクを開始する


Future

オブジェクトから最終結果を取得するために、オブジェクトで利用可能な

get

メソッドを使用できますが、これは計算の終わりまで親スレッドをブロックします。

あるいは、タスクにロジックを追加してブロックを回避することもできますが、コードの複雑さを増す必要があります。

  • Java 1.8では、計算結果をよりうまく処理するために

    Future

    構文の上に新しいフレームワーク、

    CompletableFuture

    が導入されました。


  • CompletableFuture

    には

    CompletableStage

    が実装されています。これは、コールバックを付加し、準備が整った後に結果に対して操作を実行するために必要なすべての配管を回避するための多数のメソッドを追加します。

タスクを送信するための実装ははるかに簡単です。

CompletableFuture.supplyAsync(() -> "Hello");


supplyAsync

は非同期に実行したいコードを含む

Supplier

を取ります – この場合はlambdaパラメータです。

  • タスクは暗黙のうちに

    ForkJoinPool.commonPool()

    に送信されるようになりました。または、2番目のパラメータとして

    Executor

    を指定することもできます。

__CompletableFutureの詳細については、https://www.baeldung.com/java-completablefuture[Guide to CompletableFuture]をご覧ください。

6.遅延タスクまたは定期タスクの実行

  • 複雑なWebアプリケーションを扱う場合、特定の時間にタスクを実行する必要があるかもしれません。

Javaには、遅延操作や繰り返し操作の実行に役立つツールがほとんどありません。


  • java.util.Timer


  • java.util.concurrent.ScheduledThreadPoolExecutor

6.1.

タイマー


Timer

は、バックグラウンドスレッドで将来実行するためにタスクをスケジュールする機能です。

タスクは、1回限りの実行、または定期的な間隔での繰り返し実行をスケジュールすることができます。

1秒遅れてタスクを実行したい場合のコードの外観を見てみましょう。

TimerTask task = new TimerTask() {
    public void run() {
        System.out.println("Task performed on: " + new Date() + "n"
          + "Thread's name: " + Thread.currentThread().getName());
    }
};
Timer timer = new Timer("Timer");
long delay = 1000L;
timer.schedule(task, delay);

定期的なスケジュールを追加しましょう。

timer.scheduleAtFixedRate(repeatedTask, delay, period);

今回は、タスクは指定された遅延の後に実行され、一定期間が過ぎると再発します。

詳細については、https://www.baeldung.com/java-timer-and-timertask[Java Timer]のガイドをお読みください。

6.2.

ScheduledThreadPoolExecutor


ScheduledThreadPoolExecutor

には、

Timer

クラスに似たメソッドがあります。

ScheduledExecutorService executorService = Executors.newScheduledThreadPool(2);
ScheduledFuture<Object> resultFuture
  = executorService.schedule(callableTask, 1, TimeUnit.SECONDS);

この例を終了するために、繰り返しのタスクに

scheduleAtFixedRate()

を使用します。

ScheduledFuture<Object> resultFuture
 = executorService.scheduleAtFixedRate(runnableTask, 100, 450, TimeUnit.MILLISECONDS);

上記のコードは、100ミリ秒の初期遅延の後にタスクを実行します。その後、450ミリ秒ごとに同じタスクを実行します。

  • プロセッサが次のタスクが発生する前に時間内にタスクの処理を完了できない場合、

    ScheduledExecutorService

    は次のタスクを開始する前に現在のタスクが完了するまで待機します。

この待ち時間を避けるために、

scheduleWithFixedDelay()

を使用することができます。これは、その名前で説明されているように、タスクの繰り返し間の固定長の遅延を保証するものです。

__ScheduledExecutorServiceの詳細については、https://www.baeldung.com/java-executor-service-tutorial[Java ExecutorServiceの手引き]をお読みください。

6.3. どのツールが良いですか?

上記の例を実行すると、計算結果は同じに見えます。

では、どのようにして正しいツールを選択するのでしょうか。

フレームワークが複数の選択肢を提供する場合、情報に基づいた決定を下すには、基盤となるテクノロジを理解することが重要です。

フードの下でもう少し深く潜りましょう。


  • タイマー

    :**

  • リアルタイムの保証はありません。


__Object.wait(long)

__method
** バックグラウンドスレッドは1つだけなので、タスクは順番に実行されます。

長期実行タスクは他人を遅らせることができます
**

TimerTask

でスローされたランタイム例外は唯一のスレッドを強制終了する

利用可能、したがって

Timer

を殺す


  • ScheduledThreadPoolExecutor

    :**

  • 任意の数のスレッドで設定可能

  • 利用可能なすべてのCPUコアを利用することができます

  • 実行時例外をキャッチし、必要に応じてそれらを処理させます(


ThreadPoolExecutor

)から

afterExecute

メソッドをオーバーライドします。
** 例外を投げたタスクを取り消します

走り続ける
** タイムゾーンを追跡するためにOSのスケジューリングシステムに依存している、

遅れ、太陽時など

  • 複数の連携が必要な場合は共同APIを提供

送信されたすべてのタスクの完了を待つようなタスク
** スレッドライフサイクル管理のためのより良いAPIを提供します

今の選択は明らかですね。

7.

Future



ScheduledFuture

の違い

  • このコード例では、

    ScheduledThreadPoolExecutor

    が特定のタイプの

    Future

    を返すことがわかります。


ScheduledFuture

。**


__ScheduledFuture


は、

Future



Delayed

の両方のインタフェースを拡張します。したがって、現在のタスクに関連する残りの遅延を返す追加メソッド

getDelay

を継承します。タスクが定期的であるかどうかを確認するためのメソッドを追加する

RunnableScheduledFuture__によって拡張されました。


  • ScheduledThreadPoolExecutor

    は、内部クラス

    ScheduledFutureTask

    を通じてこれらすべての構成要素を実装し、それらを使用してタスクのライフサイクルを制御します。

8.結論

このチュートリアルでは、スレッドの開始とタスクの並列実行に使用できるさまざまなフレームワークを試しました。

それから、

Timer

と__ScheduledThreadPoolExecutorの違いを詳しく調べました。

この記事のソースコードはhttps://github.com/eugenp/tutorials/tree/master/core-java-concurrency[over on GitHub]から入手できます。