Java でのスレッドの作成と使用、Thread クラスの一般的なメソッド

1. プロセスとスレッドとは何ですか

1.1 意味

1.1.1 プロセス

プロセスは、実行中のプログラムのインスタンスです。オペレーティング システムでは、プロセスは実行中のプログラムを表します。これには、プログラムの実行に必要なプログラム コード、データ、システム リソースが含まれます。

最も直感的なのはタスク マネージャーです。

タスク マネージャーのすべてのアプリケーションはプロセスです。

1.1.2 スレッド

スレッドはプロセス内の実行単位です。プロセスには複数のスレッドを含めることができ、各スレッドは独立した実行パスを持ち、特定のタスクを独立して実行でき、プロセス内のすべてのスレッドはこのプロセスのリソースを共有します。

以下の例のように。プロセス: レストランの運営プロセス全体をプロセスとみなすことができます。これには、キッチン、レストランのホール、ウェイター、料理人、顧客などのすべてのリソースとアクティビティが含まれます。スレッド: レストランでは、ウェイターはスレッドとして考えることができます。多くのウェイターがいて、顧客の受付、注文の記録、メニューの受け渡し、料理の提供などの業務を担当します。ウェイターはレストランのプロセスの実行単位であり、レストランのテーブル、キッチン、食材など、他のウェイターと同じリソースを共有します。効率を向上させるために、複数のサーバーでタスクを並行して実行できます。

1.1.3 スレッドとプロセスの違い

プロセスの誕生は、プログラムの同時実行を実現することです。いわゆる同時実行性とは、オペレーティング システムでは、起動から実行、実行までの間に複数のプログラムが存在し、これらのプログラムはすべて同じプロセッサ上で実行されることを意味します。

スレッドの誕生の目的は、同時実行中のプログラムの時間とスペースのオーバーヘッドを削減し、オペレーティング システムの同時実行性を向上させることです。

  • アドレス空間: スレッドはプロセスのアドレス空間を共有しますが、プロセスは独立したアドレス空間を持ちます。
  • リソース: スレッドはメモリ、I/O、CPU などのプロセスのリソースを共有しますが、プロセス間のリソースは独立しています。
  • 堅牢性: マルチプロセスはマルチスレッドよりも強力で、プロセスがクラッシュしても保護モードでは他のプロセスに影響を与えませんが、スレッドがクラッシュするとプロセス全体が停止します。
  • 実行プロセス: 独立した各プロセスにはプログラム実行エントリ、逐次実行シーケンス、プログラム終了があり、実行オーバーヘッドが高くなります。ただし、スレッドは独立して実行できず、アプリケーションプログラムに依存する必要があり、アプリケーションプログラムがマルチスレッドで実行制御を行うため、実行のオーバーヘッドが小さい。
  • 同時実行: 両方を同時に実行できます。
  • 切り替え時:プロセス切り替え時はリソースを多く消費し効率が悪くなります。したがって、頻繁に切り替える場合には、プロセスよりもスレッドを使用する方が適しています。
  • その他: プロセスはオペレーティング システムのリソース割り当ての基本単位であり、スレッドはオペレーティング システムのスケジューリングの基本単位です。

1.2 PCB と TCB

プロセス制御ブロック PCB (プロセス制御ブロック)。プロセスの動作を記述および制御するために使用されるデータ構造です。これはプロセスの存在を示す唯一の兆候であり、オペレーティング システムがプロセスを管理するための重要な基盤でもあります。

TCB (スレッド コントロール ブロック) は、スレッド コントロール ブロックの略語で、スレッドの実行を記述および制御するためにオペレーティング システムで使用されるデータ構造です。

彼らの関係:

プロセス識別子、ステータス、優先順位、レジスタ、プログラム カウンタ、スタック ポインタ、リソース リスト、同期および通信メカニズム、およびその他の情報が PCB に保存されます。PCB はプロセスの存在を示す唯一の兆候であり、プロセスの切り替えとスケジューリングを実現するための基盤でもあります。

TCB は PCB に似ており、スレッド識別子、ステータス、優先順位、レジスタ、プログラム カウンタ、スタック ポインタ、信号シールドなどの情報も保存します。TCB はスレッドの存在を示す唯一の兆候であり、スレッドの切り替えとスケジューリングの基礎でもあります。

2. スレッドを作成するいくつかの方法

スレッドはオペレーティング システムの概念です。オペレーティング システムのカーネルは、スレッドとしてそのようなメカニズムを実装し、ユーザーが使用できるようにいくつかの API をユーザー層に提供します。Java 標準ライブラリの Thread クラスは、オペレーティング システムによって提供される API をさらに抽象化し、カプセル化したものとみなすことができます。

2.1 Threadクラスの継承

(1) Threadを継承してスレッドクラスを作成します。

class MyThread extends Thread{
    //重写run方法,run 表示这个线程需要执行的任务
    @Override
    public void run() {
        System.out.println("这个线程需要执行的任务");
    }
}

(2) MyThreadのインスタンスを作成します。

//创建一个 MyThread 实例
MyThread myThread = new MyThread();

(3) start() メソッドを呼び出すと、スレッドが実行を開始します。

//start 方法表示这个线程开始执行,注意,这里不是调用 run()方法
myThread.start();

2.2 Runnableインターフェイスの実装

(1) Runnable インターフェースを実装します。

//通过 实现 Runnable 接口来创建一个线程
class MyRunnable implements Runnable{

    //需要重写run方法
    @Override
    public void run() {
        System.out.println("这个线程需要执行的任务");
    }
}

(2) Thread インスタンスを作成します。

//创建 Thread 类实例, 调用 Thread 的构造方法时将 Runnable 对象作为参数。
Thread thread = new Thread(new MyRunnable());

(3) start() メソッドを呼び出すと、スレッドが実行を開始します。

//线程开始运行
thread.start();

2.3 その他の変形

  • 匿名の内部クラスは、Thread サブクラス オブジェクトを作成します。
Thread thread1 = new Thread(){
    @Override
    public void run() {
        System.out.println("使用匿名类创建 Thread 子类对象");
    }
};
  • 匿名の内部クラスは、Runnable サブクラス オブジェクトを作成します。
Thread thread2 = new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("使用匿名类创建 Runnable 子类对象");
    }
});
  • ラムダ式は Runnable サブクラス オブジェクトを作成します。
Thread thread3 = new Thread(()-> {
    System.out.println("使用匿名类创建 Thread 子类对象");
});

3. Threadクラスの共通メソッド

3.1 スレッドの一般的な構築方法

方法 説明する
糸() スレッドオブジェクトを作成する
スレッド(実行可能なターゲット) Runnable オブジェクトを使用してスレッド オブジェクトを作成する
スレッド(文字列名) スレッドオブジェクトを作成して名前を付ける
スレッド(実行可能なターゲット、文字列名) Runnable オブジェクトを使用してスレッド オブジェクトを作成し、名前を付けます
Thread thread = new Thread("小明"){
    @Override
    public void run(){
        while(true){
        	System.out.println("我是小明");
    	}   
    }
};

このスレッドの名前は「Xiao Ming」ですが、このスレッドの名前を確認するにはどうすればよいですか? Java Development Kit (JDK) では、jconsole を使用してスレッドを監視できます。

  1. jdk パス (ここでは C:\Program Files\Java\jdk1.8.0_31\bin) を開き、このパスの下で jconsole ツールを開きます。
  2. 上記のコードを実行し、次の手順に進みます。

「Xiao Ming」という名前のスレッドがあり、これが私たちが作成したスレッドであることがわかります。

3.2 スレッド属性の取得方法

属性 アクセス方法
ID getId()
名前 getName()
getState()
優先順位 getPriority()
バックグラウンドスレッドかどうか isDaemon()
生き残るかどうか 生きている()
中断されましたか isInterrupted()
  • ID はスレッドの一意の識別子であり、異なるスレッドが繰り返されることはありません。
  • name はスレッドの名前です。
  • 状態は、スレッドが現在置かれている状況 (ブロック、実行など) を表します。
  • 優先度の高いスレッドはスケジュールされる可能性が高くなります。
  • バックグラウンド スレッドについて: デーモン スレッドは、プログラムの実行中にバックグラウンドでサービスを提供するスレッドです。フォアグラウンド スレッド (ユーザー スレッドとも呼ばれます) とは対照的に、バックグラウンド スレッドはプログラムの終了を妨げません。バックグラウンド スレッドは、実行がまだ終了していなくてもすべてのフォアグラウンド スレッド (ユーザー スレッド) が終了すると自動的に終了します。
  • 生きているかどうか、つまり簡単に理解すると、runメソッドが終了しているかどうかです。

3.3 スレッドを開始するメソッド start()

前回の記事で、 run メソッドをオーバーライドしてスレッド オブジェクトを作成する方法を説明しましたが、スレッド オブジェクトの作成は、スレッドの実行を開始することを意味しません。run メソッドをオーバーライドして、スレッドに実行内容の指示のリストを提供します。start() メソッドを呼び出すことによってのみスレッドを独立して実行でき、start メソッドを呼び出すことによってのみオペレーティング システムの下部にスレッドを作成できます。

3.4 スレッドスリープ sleep()

方法 説明する
public static void sleep(long millis) は InterruptedException をスローします 現在のスレッドをミリ秒間スリープさせます
public static void sleep(long millis, int nanos) は InterruptedException をスローします より高い精度で睡眠が可能

次のコード:

public static void main(String[] args) throws InterruptedException {
    long begin = System.currentTimeMillis();
    Thread.sleep(3000);//睡眠当前线程
    long end = System.currentTimeMillis();
    System.out.println(end - begin);
}

結果:

より詳細な内容については、後ほどご紹介します。

3.5 スレッドの中断

ここでいう中断とは、すぐに中断するという意味ではなく、詳しくは次のケースを見てみましょう。

ここには 2 つのスレッドがあり、1 つのスレッドはもう 1 つのスレッドに割り込むために使用され、ここでは Thread メソッドは使用されません。

public class Main {
    
    public static volatile boolean flag = true;
    
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(){
            @Override
            public void run(){
                // flag 是中断标记
                while(flag){
                    try {
                        Thread.sleep(500);//睡眠
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("Hi");
                }
            }
        };
        //开启线程
        thread.start();

        //中断线程
        Thread.sleep(3000); //睡眠 3 秒
        flag = false;//更改标记
        System.out.println("开始中断线程");
    }
}

(volatile の役割についてはここでは紹介しません。今後も更新していきます。)

結果:

ここでの割り込みはフラグ ビットの値を変更するためのものであり、ここには遅延があり、スレッドはスリープ スリープが終了するときにのみ終了できることがわかります。

したがって、Thread によって提供されるメソッド、つまり isInterrupted() または Interrupted() を使用できます。その利点は、スリープしているスレッドをすぐにウェイクアップできることです。次のケースを参照してください。

方法 説明する
パブリック void 割り込み() オブジェクトに関連付けられたスレッドを中断します。スレッドがブロックされている場合は異常な方法で通知され、そうでない場合はフラグ ビットが設定されます。
パブリック静的ブール値中断() 現在のスレッドの割り込みフラグ ビットが設定されているかどうかを確認し、呼び出し後にフラグ ビットをクリアします。
パブリックブール値 isInterrupted() オブジェクトに関連付けられたスレッドのフラグ ビットが設定されているかどうか、および呼び出し後にフラグ ビットがクリアされていないかどうかを確認します。

ここでのフラグの詳細については後述する。

public static void main(String[] args) throws InterruptedException {

        Thread thread = new Thread(){
            @Override
            public void run(){
                // flag 是中断标记
                while(!Thread.currentThread().isInterrupted()){
                    try {
                        Thread.sleep(500);//随眠
                    } catch (InterruptedException e) {
                        e.printStackTrace();//打印报错信息
                    }
                    System.out.println("Hi");
                }
            }
        };
        //开启线程
        thread.start();

        Thread.sleep(3000); //睡眠 3 秒
        thread.interrupt();//中断 thread 线程,把标记位置为true。
        System.out.println("开始中断线程");
    }

ここで説明します! Thread.currentThread().isInterrupted()、Thread.currentThread() は、次のような現在のスレッド オブジェクトの参照を返します。

方法 説明する
public static スレッド currentThread(); 現在のスレッドオブジェクトへの参照を返します。

isInterrupted(): 現在のスレッドが中断された場合は true を返し、そうでない場合は false を返します。

ここでの割り込みは、割り込みフラグビットで見る必要がありますが、割り込みフラグビット(割り込みフラグ)は、スレッドの内部状態の一つであり、実際にはスレッドの内部データ構造に存在します。**具体的には、割り込みフラグ ビットはスレッド オブジェクトのメンバー変数であり、スレッドの割り込みステータスを示すために使用されます。**このフラグは、スレッドの作成時に false に初期化されます。スレッドの中断() メソッドが呼び出されると、割り込みフラグは true に設定され、スレッドが中断されたことを示します。

Thread.currentThread().isInterrupted()、ここでは現在のスレッドが中断されているかどうかを示し、中断された場合は true を返し、そうでない場合は false を返します。最後に、while の条件として前に ! を追加します。つまり、中断された場合は false を返し、そうでない場合は true を返します。

最後に、結果を見てみましょう。上で thread.interrupt() を呼び出していることに注意してください。そのため、sleep が例外をスローし、ループを終了する (スレッドを終了する) ことが期待されます。

結果:

なぜプログラムはここで終わらないのでしょうか?ゆっくり分析してみましょう。while ループが終了していない場合、その条件が true であることは明らかです。その場合、Thread.currentThread().isInterrupted() の値は false となり、スレッドが中断状態ではないことを意味します。 。 何?すでにinterrupt()メソッドを呼び出していませんか?

その理由は、sleep()、join()、wait() メソッドの呼び出しなど、スレッドがブロックされているときに、割り込み要求を受信すると、これらのメソッドが InterruptedException をスローして割り込みステータスをクリアするためです、ここでのクリア割り込みステータスは、フラグの位置を false に設定し、スレッドが割り込まれていないことを示します。

この問題を解決するにはどうすればよいでしょうか? キャッチに直接ブレークを追加するだけです。

public static void main(String[] args) throws InterruptedException {

        Thread thread = new Thread(){
            @Override
            public void run(){
                // flag 是中断标记
                while(!Thread.currentThread().isInterrupted()){
                    try {
                        Thread.sleep(500);//随眠
                    } catch (InterruptedException e) {
                        //这里进行善后处理

                        break;
                    }
                    System.out.println("Hi");
                }
            }
        };
        //开启线程
        thread.start();

        //中断线程
        Thread.sleep(3000); //睡眠 3 秒
        thread.interrupt();//中断 thread 线程
        System.out.println("开始中断线程");
    }

このようなことをする利点は何ですか? スレッドは、ループの終了、リソースのクローズ、無視など、独自のロジックに従って割り込み要求を処理できます。

概要: 前提条件、interrupt() を呼び出します。

  1. wait/join/sleepなどのメソッドの呼び出しによりスレッドがブロックおよびサスペンドされた場合、InterruptedExceptionの形式で通知され、割り込みフラグがリセットされます。この時点でスレッドを終了するかどうかは、catch 内のコードの記述方法によって異なります。この例外を無視することも、ループから抜け出してスレッドを終了することもできます。
  2. 待機/参加/スリープなどの方法がない場合は、
  3. Thread.interrupted() 現在のスレッドの割り込みフラグがセットされているかどうかを判定し、判定後に割り込みフラグをリセットします。
  4. Thread.currentThread().isInterrupted() 現在のスレッドの中断フラグが設定されているかどうかを判断し、中断フラグをリセットしません。

3.6 スレッド join() の待機

場合によっては、次のステップに進む前に、スレッドの作業が完了するまで待つ必要があります。

方法 説明
加入() スレッドが完了するまで待ちます
結合(長いミリ秒) 指定されたミリ秒数まで待機します。この時間内にスレッドの実行が完了しない場合、現在のスレッドは実行を継続します。
join(long ミリス、int ナノ) 指定されたミリ秒およびナノ秒まで待機します。この時間内にスレッドの実行が完了しない場合、現在のスレッドは実行を継続します。
public class Main {
    public static void main(String[] args) {
        Thread thread = new Thread(){
            @Override
            public void run(){
                for (int i = 0; i < 9; i++) {
                    System.out.println("thread");
                }
            }
        };

        thread.start();//开启线程

        System.out.println("join()前");
        try {
            thread.join();//先等 thread 线程执行完
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("join()后");
    }
}

スレッド thread が join() より前に実行を終了している場合、join() はブロックされません。

これらの join() メソッドは、スレッド間で協調的な実行メカニズムを提供し、あるスレッドが別のスレッドの完了を待ってから実行を続行できるようにします。これは、スレッドの実行順序やスレッド間の依存関係が必要なシナリオに役立ちます。

おすすめ

転載: blog.csdn.net/mxt51220/article/details/131361893