Java でマルチスレッドを作成する 4 つの方法、Thread クラスを継承してマルチスレッドを作成する、Runnable インターフェイスを実装する、Callable と Future を介して作成する、およびスレッド プールを使用する


序文

なぜスレッドを使用するのでしょうか?

プログラム内で特定の機能が完了するとき、それをタスクとして記述しますが、このタスクはスレッド内で完了する必要があります。


1. シリアル化と同時実行性

プログラム内で処理する必要があるタスクが複数ある場合、この時点での処理方法はシリアルまたは並行で行うことができます。

  • シリアル(同期): すべてのタスクは特定の順序で実行されます。前のタスクの実行が完了していない場合は、次のタスクが待機します。
  • 同時実行性(非同期): 複数のタスクを同時に実行し、一定時間内に複数のタスクを同時に処理します。

2. プロセスとスレッド

  • プロセス: プログラムの動作中にプログラムが占有するさまざまなリソースの説明です。
  • スレッド: プロセス内の最小の実行単位です。オペレーティング システムでは、最小のタスク実行単位はスレッドではなくハンドルです。ただ、ハンドルが小さすぎて操作が非常に面倒なので、スレッドは制御できる最小のタスク実行単位です。

1. プロセスとスレッドの類似点と相違点

  • 類似点: プロセスとスレッドは両方とも、複数のタスクを同時に処理するために存在します。
  • 違い: リソースはプロセス間で共有されず、あるプロセスは別のプロセスのデータにアクセスできません。リソースはスレッド間で共有され、複数のスレッドが同じデータを共有できます。リソースはスレッド間で共有されるため、重大なリソースの問題が発生します。

2. プロセスとスレッドの関係

プロセスが作成されると、プロセス内のタスクを処理するためのスレッドが自動的に作成されます。このスレッドをメインスレッドと呼びます。プログラムの実行中に、他のスレッドも開くことができますが、これらの他のスレッドはすべてサブスレッドです。
つまり、プロセスには複数のスレッドを含めることができます。プロセス内のスレッドがクラッシュしても、他のスレッドが存在する限り、プロセス全体の実行には影響しません。ただし、プロセス内のすべてのスレッドが実行を終了すると、プロセスも終了します。

3. プロセスとスレッドの概要

  • プログラム: 実行可能ファイル
  • プロセス: 実行中のプログラム。メモリ内のスペースを開くこととしても理解できます。
  • スレッド: プログラムの実行を担当し、実行チャネルまたは実行ユニットとみなすことができるため、通常はプロセスの作業をスレッドの作業として理解します。
  • プロセス内にスレッドが存在しない可能性がありますか? 少なくとも 1 つのスレッドが存在する必要があります。
  • スレッドが 1 つ存在する場合はシングルスレッド (メイン スレッドのみ) と呼び、複数のスレッドが同時に存在する場合はマルチスレッドと呼びます。
  • マルチスレッドの役割: 複数のことを同時に達成する (同時実行)。

3. スレッドの作成

1. スレッド作成の4つの方法

スレッドを作成するには主に 4 つの方法があります。

  • Thread クラスを継承してスレッド オブジェクトを作成する
  • Runnableインターフェイスを使用する
  • Callable と Future を通じてスレッドを作成する
  • スレッドプールを介してスレッドを作成する

2. Thread クラスを継承してスレッド オブジェクトを作成します

Threadクラスを継承してThreadのサブクラスを作成します。サブクラスで、親クラスの run メソッドをオーバーライドします。このオーバーライドされたメソッドで、このスレッドが処理する必要があるタスクを指定します。

コード例:

/**
 * @author: Mercury
 * Date: 2022/3/27
 * Time: 13:15
 * Description:通过继承Thread来创建线程
 * Version:1.0
 */
public class Test3 {
    
    
    public static void main(String[] args) {
    
    
        Task2 task2 = new Task2("通过继承Thread创建的线程1");
        task2.start();
    }
}
class Task2 extends Thread{
    
    
    private String threadName;

    public Task2() {
    
    
    }

    public Task2(String threadName) {
    
    
        this.threadName = threadName;
    }
    @Override
    public void run() {
    
    
        for (int i = 0; i < 10; i++) {
    
    
            System.out.println(threadName+": "+i);
        }
    }
}

1) Threadクラスの共通メソッド

メソッド名 説明する
currentThread() 現在実行中のスレッドオブジェクトへの参照を返します。
getId() このスレッドの識別子を返します
getName() このスレッドの名前を返します
getPriority() このスレッドの優先度を返します
getState() このスレッドのステータスを返します
setName(文字列名) このスレッドの名前をパラメータ名に変更します
スリープ(長いミリ秒) 現在のスレッドを指定されたミリ秒間一時停止します。
始める() このスレッドの実行を開始します
toString() このスレッドの名前、優先順位、およびスレッド グループを返します。
収率() 現在のスレッドが現在使用されているプロセッサを生成する意思があるというスケジューラへのヒント

3. Runnableインターフェイスを使用する

Thread クラスのコンストラクターには、Runnable インターフェイスをパラメーターとするオーバーロードされたコンストラクターがあります。したがって、Thread オブジェクトは、Runnable インターフェイスの実装クラス オブジェクトを通じてインスタンス化できます。

コード例:

/**
 * @author: Mercury
 * Date: 2022/3/27
 * Time: 13:05
 * Description:通过实现Runnable接口来创建线程
 * Version:1.0
 */
public class Test2 {
    
    
    public static void main(String[] args) {
    
    
        Task task = new Task();
        Thread thread = new Thread(task,"线程1");
        thread. start();
    }
}
class Task implements Runnable{
    
    
    @Override
    public void run() {
    
    
        for (int i = 0; i < 10; i++) {
    
    
            System.out.println(Thread.currentThread().getName()+":"+i);
        }
    }
}

5. Thread継承とRunnableインタフェース利用のメリット・デメリットの比較

  • 継承方法: 利点は読みやすいことですが、欠点は柔軟性が十分ではないことです。スレッドをカスタマイズする場合は、Thread クラスから継承する必要があります。これは、元の継承システムに影響を与える可能性があります。
  • インターフェイス メソッド: 利点は、柔軟性があり、クラスの継承システムに影響を与えないことです。欠点は可読性が低いことです。

6. Callable と Future を通じてスレッドを作成する

Callable インターフェースの実装クラスを作成し、スレッドの実行本体として使用でき、戻り値を持つ call() メソッドを実装します。次に、Callable 実装クラスのインスタンスを作成し、FutureTask クラスを使用して Callable オブジェクトをラップします。FutureTask オブジェクトは、Callable オブジェクトの call() メソッドの戻り値をカプセル化します。FutureTask オブジェクトを Thread のターゲットとして使用して、新しいスレッドを作成して開始します。次に、FutureTask オブジェクトの get() を呼び出して、子スレッドの実行終了後の戻り値を取得します。戻り値を受け入れる必要がない場合は、操作のこの部分を実行する必要はありません。

コード例:

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

/**
 * @author: Mercury
 * Date: 2022/3/27
 * Time: 13:29
 * Description:通过 Callable 和 Future 创建线程
 * Version:1.0
 */
public class Test4 {
    
    
    public static void main(String[] args) {
    
    
        CallableTest callableTest = new CallableTest();
        //使用FutureTask类包装Callable对象
        FutureTask<Integer> futureTask = new FutureTask<Integer>(callableTest);
        //将FutureTask的对象作为参数传递到Thread类中,创建Thread对象,并调用start()
        new Thread(futureTask).start();
        //如果需要获取返回值的话
        try {
    
    
            Integer i = futureTask.get();
            System.out.println("i="+i);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        } catch (ExecutionException e) {
    
    
            e.printStackTrace();
        }
    }
}
//创建Callable接口的实现类
class CallableTest implements Callable<Integer>{
    
    

    //实现call()方法
    @Override
    public Integer call() throws Exception {
    
    
        int i = 0;
        for (; i < 100; i++) {
    
    
            System.out.println(Thread.currentThread().getName()+": "+i);
        }
        return i;
    }
}

6. スレッド プールを介してスレッドを作成する

スレッド プール: 実際には、複数のスレッドを格納するコンテナです。

スレッド プールを使用する主な目的は、スレッドの再利用の問題を解決することです。以前にスレッドを使用したときは、スレッドを使用する必要があるときに、新しいスレッドをインスタンス化していました。このスレッドの使用が完了すると、このスレッドは破棄されます。要件の実現には問題ありませんが、スレッドのオープンと破棄が頻繁に行われると、むしろ CPU に負荷がかかるため、最適化に努める必要があります。

この問題は、再利用メカニズムを使用して解決できます。スレッドを使用する必要がある場合、スレッドを直接インスタンス化するのではなく、まずスレッド プールにアクセスして、使用できるアイドル状態のスレッドがあるかどうかを確認します。存在する場合はそれを直接使用し、存在しない場合は新しいスレッドをインスタンス化します。また、このスレッドの使用が終了すると、すぐに破棄されるのではなく、次回も継続して使用できるようにスレッド プールに置かれます。

スレッド プール内のすべてのスレッドは、コア スレッドと一時スレッドの2 つの部分に分割できます。

コアスレッド: コアスレッドはスレッドプール内に存在し、スレッドプールが存在する限りこれらのスレッドは破棄されません。これらは、スレッド プールを破棄する必要がある場合にのみ破棄されます。

臨時スレッド: 臨時職員です。一時的な高密度スレッド要件が発生すると、一部のタスクを処理するために一部のスレッドが一時的に開かれます。これらの一時スレッドは、処理する必要があるタスクの処理を完了した後、他に処理するタスクがない場合はアイドル状態になります。アイドル時間が指定された時間に達すると、この一時スレッドは破棄されます。

コード例:

package test;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * @author: Mercury
 * Date: 2022/3/27
 * Time: 12:48
 * Description:线程池
 * Version:1.0
 */
public class Test1 {
    
    
    public static void main(String[] args) {
    
    
        //创建线程池对象,参数10 表示线程池中有10个线程
        ExecutorService service = Executors.newFixedThreadPool(10);
        //创建Runnable线程对象
        Task3 task3 = new Task3();
        //从线程池中获取线程对象
        service.submit(task3);
        service.submit(task3);
        //关闭线程池
        service.shutdown();
    }
}
class Task3 implements Runnable{
    
    

    @Override
    public void run() {
    
    
        for (int i = 0; i < 100; i++) {
    
    
            System.out.println(Thread.currentThread().getName()+": "+i);
        }
    }
}

1. スレッドプールの一般的な方法

方法 説明する
実行(実行可能 実行可能) タスクをスレッド プールに送信すると、同時処理用のスレッドが割り当てられます。
シャットダウン() スレッド プールに停止信号を送信しても、スレッド プール内のスレッドは停止されませんが、スレッド プール内のすべてのタスクの実行が終了した後でスレッドとスレッド プールが終了します。
shutdownNow() スレッド プール内のすべてのスレッドとスレッド プールをただちに停止します。

2. スレッドプールツールクラス

コンストラクター メソッドを使用してスレッド プールをインスタンス化するだけでなく、Executors ツール クラスを通じてスレッド プールを取得することもできます。実際のアプリケーションでは、ほとんどのシナリオで、以前の構築メソッドを使用してスレッド プールをインスタンス化することはできませんが、Executors ツール クラスのメソッドを使用してスレッド プールを取得します。

方法 説明する
Executors.newSingleThreadExecutor() コアスレッド数:1、最大スレッド数:1
Executors.newFixedThreadPool(int サイズ) コアネジサイズ、最大ネジサイズ
提出する() スレッドプールにタスクを追加する
シャットダウン() スレッドプールを停止する

要約する

上記はマルチスレッドに関する内容で、プロセス、スレッド、マルチスレッドの 4 つの作成方法を簡単に紹介します。

おすすめ

転載: blog.csdn.net/qq_45263520/article/details/123774458