JAVA マルチスレッドの原理の詳細な分析

1. 糸の基本


1.1 スレッドとプロセス


1.1.1 プロセス


●プログラムは命令とデータで構成されますが、これらの命令を実行し、データを読み書きするには、命令をCPUにロードし、データをメモリにロードする必要があります。命令の実行時には、ディスクやネットワークなどのデバイスも必要です。プロセスは、命令のロード、メモリの管理、および IO の管理に使用されます。


●プログラムを実行すると、そのプログラムのコードがディスクからメモリにロードされ、この時点でプロセスが開かれます。 


● プロセスは、プログラムのインスタンスと見なすことができます。ほとんどのプログラムは同時に複数のインスタンス プロセスを実行できます (メモ帳、描画、ブラウザなど)。一部のプログラムは 1 つのインスタンス プロセスしか開始できません (NetEase Cloud Music、360 Security Guard など)。


●オペレーティングシステムは、システムリソース(CPUタイムスライス、メモリなどのリソース)をプロセス単位で割り当てますが、プロセスはリソース割り当ての最小単位です。


1.1.2 スレッド


●スレッドはプロセス内の実体であり、プロセスは複数のスレッドを持つことができ、スレッドには親プロセスが必要です。 


●スレッドは命令ストリームであり、命令ストリーム内の命令は一定の順序で CPU に渡されて実行されます。


●スレッドは、軽量プロセス (Lightweight Process、LWP) と呼ばれることもあり、オペレーティング システムのスケジューリング (CPU スケジューリング) 実行の最小単位です。


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


1 リソース占有
プロセスは、その内部スレッドが共有するメモリ空間などの共有リソースを持っています。


2 依存関係
プロセスは基本的に互いに独立していますが、スレッドはプロセス内に存在し、プロセスのサブセットです。


3 通信モード
プロセス間通信はより複雑です


ⅰ 同じコンピュータのプロセス通信は IPC (プロセス間通信) と呼ばれます
ⅱ 異なるコンピュータ間のプロセス通信は、ネットワークを経由し、HTTP などの共通のプロトコルに従う必要があります
b スレッド通信は、メモリを共有するため、比較的単純ですプロセスでは、例として、複数のスレッドが同じ共有変数にアクセスできる場合があります


4スレッドのコンテキスト切り替え
は軽く、スレッド コンテキスト切り替えのコストはプロセス コンテキスト切り替えよりも一般的に低い
b 2 つのスレッドが同じプロセスに属していない場合、切り替えプロセスはプロセス コンテキスト切り替えと同じ;
c 2 つのスレッドの場合同じプロセスの場合、仮想メモリが共有されているため、切り替え時に仮想メモリなどのリソースは変更されず、プライベート データやスレッドのレジスタなど、共有されていないデータのみを切り替える必要があります。


1.1.4 プロセス間通信の方法


詳細については、「オペレーティング システム - プロセス間通信」を参照してください

1 パイプライン (パイプ) と名前付きパイプ (名前付きパイプ): パイプラインは、親プロセスと子プロセス間の通信に使用できます. パイプラインの機能に加えて, 名前付きパイプは、パイプラインの機能に加えて、無関係なプロセス間の通信も可能にします.


2 シグナル (signal): シグナルは、ソフトウェアレベルでの割り込みメカニズムのシミュレーションです. 特定のイベントが発生したことをプロセスに通知するために使用される比較的複雑な通信方法です. プロセスはシグナルを受信し、プロセッサは割り込みを受信します. . リクエストの効果は一貫していると言えます。


3 メッセージ キュー (メッセージ キュー): メッセージ キューは、メッセージのリンクされたリストであり、上記の 2 つの通信方法の制限されたセマフォの欠点を克服します. 書き込み権限を持つプロセスは、特定の規則に従ってメッセージ キューに新しい情報を追加できます;メッセージ キューに対する読み取り権限を持つプロセスは、メッセージ キューから情報を読み取ることができます。


4 共有メモリ (共有メモリ): プロセス間通信の最も有用な方法と言えます。これにより、複数のプロセスが同じメモリ空間にアクセスできるようになり、さまざまなプロセスが他のプロセスの共有メモリ内のデータの更新を時間内に確認できます。このアプローチは、ミューテックスやセマフォなど、ある種の同期操作に依存する必要があります。


5 Semaphore (セマフォ): 主に、プロセス間および同じプロセスの異なるスレッド間の同期および相互排除の手段として使用されます。


6 ソケット (socket): これは、より一般的なプロセス間通信メカニズムであり、ネットワーク内の異なるマシン間のプロセス間通信に使用でき、広く使用されています。


1.1.5 スレッド間の通信方法


相互排除とは、
個々のスレッドがアクセスするときの共有プロセス システム リソースの排他性を指します。複数のスレッドが共有リソースを使用したい場合、いつでも 1 つのスレッドだけがその共有リソースを使用でき、そのリソースを使用したい他のスレッドは、リソース占有者がリソースを解放するまで待機する必要があります。


同期と
は、スレッド間の制約関係を指し、たとえば、
あるスレッドの実行は別のスレッドのメッセージに依存する、別のスレッドのメッセージを取得できない場合は、メッセージが到着するまで待ってから、広範囲に起床する必要があります。センス

. 参照してください、相互排除も一種の同期です。

スレッド同期と排他制御の制御方法


●クリティカルセクション 高速でデータアクセスの制御に適したマルチスレッドのシリアル化により、公開リソースやコードにアクセスします。重要なリソースは、一度に 1 つのプロセスだけが使用できる共有リソースです。各プロセスで重要なリソースにアクセスするコードのセクションは、クリティカル セクションと呼ばれます。 Mutex
共有リソースへの個別のアクセスを調整するように
設計されています セマフォ 限られた数のユーザーでリソースを制御するように設計されています
イベント イベントが発生したことをスレッドに通知するために使用されます。これにより、後続のタスクの開始が開始されます。


1.2 コンテキストの切り替え


コンテキスト スイッチは、あるプロセスまたはスレッドから別のプロセスまたはスレッドへの CPU (中央処理装置) の切り替えです。
コンテキストは、任意の時点での CPU レジスタとプログラム カウンタの内容です。
レジスタは、頻繁に使用される値への高速アクセスを提供することによってコンピュータ プログラムの実行を高速化する (CPU の外側の低速 RAM メイン メモリとは対照的に) CPU 内の非常に高速なメモリの小さな部分です。
プログラム カウンタは、特定のシステムに応じて、命令シーケンス内の CPU の位置を示し、実行中の命令のアドレスまたは実行される次の命令のアドレスを保持する特殊なレジスタです。
プロセス コンテキストの切り替えの詳細については、「オペレーティング システム - プロセス コンテキストの切り替え」を参照してください。

スイッチング プロセスは、カーネル (つまり、オペレーティング システムのコア) が CPU 上のプロセス (スレッドを含む) で次のアクティビティを実行するものとして簡単に説明できます。


1 プロセスの処理を一時停止し、そのプロセスの CPU 状態 (コンテキスト) をメモリのどこかに保存します。


2 メモリから次のプロセスのコンテキストを取得し、CPU のレジスタに復元します


3 プログラム カウンタが示す位置に戻る (つまり、プロセスが中断されたコード行に戻る) と、プロセスが再開されます。
 



1.2.1 コンテキスト切り替え機能


●カーネルモードでのみ発生. 詳細については、カーネルモード VS ユーザーモード & オペレーティング システム-Linux メモリ管理
を参照してください. ●マルチタスク オペレーティング システムの機能. マルチタスク オペレーティング システムには、同時実行と並列の 2 つの状況があります。コンテキストの切り替えは、
        ○ プロセスが自発的に CPU の時間を放棄し
        たり、プロセスが CPU タイム スライスを使い果たしたときにスケジューラが切り替えられたりするために発生します。
●通常、計算負荷の高いタスクで発生します. IO 負荷の高いタスクは、CPU コンテキストの切り替えをほとんど必要とせず、外部デバイスで処理できます。コンテキストの切り替えは、CPU 時間の点でシステムに多大なコストがかかります。実際、これはオペレーティング システムで最もコストのかかる操作である可能性があります。そのため、オペレーティング システムの設計における主な焦点は、不要なコンテキスト スイッチをできるだけ回避することです。他のオペレーティング システム (他の Unix ライクなシステムを含む) に対する Linux の多くの利点の 1 つは、コンテキスト切り替えとモード切り替えのコストが非常に低いことです。


1.2.2 CPU コンテキストの切り替えを表示する


Linux システムは、オペレーティング システム全体の 1 秒あたりの CPU コンテキスト スイッチング
の統計をコマンド統計 CPU コンテキスト スイッチング データ 1 で表示 できます。

vmstat 1 

cs 列は、CPU コンテキスト スイッチングの統計です。もちろん、CPU コンテキストの切り替えはスレッドの切り替えと同等ではなく、多くの操作で CPU コンテキストの切り替えが発生します:
        ○スレッド、プロセスの切り替え
        ○システム コール
        ○割り込み



2 pidstat コマンドを使用して、プロセス/スレッド コンテキスト スイッチを表示します。

常用的参数:
-u 默认参数,显示各个进程的 CPU 统计信息
-r 显示各个进程的内存使用情况
-d 显示各个进程的 IO 使用
-w 显示各个进程的上下文切换
-p PID 指定 PID

# 显示进程5598每一秒的切换情况
pidstat -w -p 5598 1


cswch はアクティブ スイッチング、nvcswch はパッシブ スイッチングを意味します。統計から、プロセスが 1 秒あたり約 500 回アクティブに切り替わることがわかります。そのため、コードには多数のスリープ/ウェイク操作があります。 


b プロセスのステータス情報から見る 

猫 /proc/5598/status 


1.3 オペレーティング システム レベルのスレッドのライフ サイクル


オペレーティング システム レベルでのスレッドのライフ サイクルは、基本的に、次の図に示す「5 状態モデル」で記述できます。


●初期状態 スレッドは作成されていますが、CPU は実行を許可されていません。ここでの作成とは、オペレーティング システムがまだ作成されていない、プログラミング言語レベル、実際のスレッドで作成されることを指します。


●準備完了状態 スレッドは、実行のために CPU タイム スライスを割り当てることができます。オペレーティング システムがスレッドを作成します。


●実行状態 CPUタイムスライスに割り当てられたスレッド。


●ドーマント状態 CPU の使用権を解放します。実行中の状態のスレッドが、ブロッキング方式でファイルを読み込んだり、条件変数などのイベントを待ったりするなど、ブロッキング API を呼び出すと、スレッドの状態はこの状態になります。待機時間が発生すると、スレッドはスリープ状態から準備完了状態に切り替わります。


●終了状態 スレッドの実行が終了、または例外が発生して終了状態になります。スレッドの存続期間が終了します。

これらの 5 つの状態は、さまざまなプログラミング言語で単純化するために組み合わされています。例えば:


●C言語のPOSIX Threads仕様は、初期状態と実行可能状態を兼ね備えている


●Java 言語では、runnable 状態と running 状態が組み合わされています. これら 2 つの状態は、オペレーティング システムのスケジューリング レベルでは有用ですが、JVM はスレッド スケジューリングをオペレーティング システムに任せているため、JVM レベルではこれら 2 つの状態は関係ありません。 .


1.4 プロセススレッドの表示方法


Windows
●プロセスとスレッドの数を表示するタスク マネージャー、およびプロセスを強制終了することもできます tasklist プロセスを
表示する
taskkill スレッドを強制終了する


linux
ps -ef すべてのプロセスを表示する
top 大文字の H を押して明示的にスレッド化するかどうかを切り替える
ps -fT -p <PID> プロセスのすべてのスレッドを表示する
top -H -p <PID> プロセスのすべてのスレッドを表示する
● killプロセスを強制終了します


システム スレッドの実装方法
LinuxThreads linux/glibc パッケージは 2.3.2 より前の LinuxThreads のみを実装
NPTL (ネイティブ POSIX スレッド ライブラリ) は、

次のコマンド getconf GNU_LIBPTHREAD_VERSION を使用して、システムが使用するスレッドの実装を確認できます 


Java
jps すべての Java プロセスを表示する
jstack <PID> Java プロセスのすべてのスレッド状態を表示する
jconsole GUI ビュー


2. Javaスレッドの詳細説明


2.1 Javaスレッドの実装


スレッドを使用するか、スレッド クラスを継承する

public void test() {
    Thread newThread = new Thread() {
        @Override
            public void run() {
            System.out.println("new Thread");
        }
    };

    MyThread myThread = new MyThread();

    newThread.start();
    myThread.start();
}

class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("extend Thread");
    }
}


Runnable インターフェースを実装する

public void test2() {
    Runnable runnable = new Runnable() {

        @Override
            public void run() {
            System.out.println("implement runnable");
        }
    };

    Thread thread = new Thread(runnable);
    thread.start();
}


戻り値を持つ Callable インターフェイスを使用する
方法 1

public void test3() {
    ExecutorService executorService = Executors.newFixedThreadPool(10);
    Future<Integer> submit = executorService.submit(new MyCallableTask());
    try {
        System.out.println(submit.get());
    } catch (InterruptedException e) {
        e.printStackTrace();
    } catch (ExecutionException e) {
        e.printStackTrace();
    }
    executorService.shutdown();
}
class MyCallableTask implements Callable<Integer> {

    @Override
    public Integer call() throws Exception {
        return new Random().nextInt();
    }
}


方法 2

public void test4() {
    FutureTask<String> task = new FutureTask<>(new Callable<String>() {
        @Override
        public String call() throws Exception {
            return "callable";
        }
    });

    new Thread(task).start();

    try {
        String s = task.get();
        System.out.println(s);
    } catch (InterruptedException e) {
        e.printStackTrace();
    } catch (ExecutionException e) {
        e.printStackTrace();
    }
}


ラムダを使用する

new Thread(() -> System.out.println(Thread.currentThread().getName())).start();

要約
本質的に、Java でスレッドを実装する唯一の方法は、new Thread() を介してスレッドを作成することです.スレッドを開始するために Thread#start を呼び出すと、最終的に Thread#run メソッドが呼び出されます。


なぜそう言うのですか?
最初の 2 つについては何も言う必要はありません。これらはすべて直接 Thread#start メソッドを呼び出しています。重要なのは、3 番目のスレッド プールの呼び出しメソッドにあります。
メソッド java.util.concurrent.Executors#newFixedThreadPool が呼び出されると、ThreadPoolExecutor クラスが作成されます。

public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}

ThreadPoolExecutor クラスのコンストラクターは Executors.defaultThreadFactory() メソッドを使用して DefaultThreadFactory を作成します。これには newThread メソッドが含まれており、最終的にスレッドで呼び出されて初期化とスレッドの呼び出しが完了します。

public Thread newThread(Runnable r) {
    Thread t = new Thread(group, r,
                          namePrefix + threadNumber.getAndIncrement(),
                          0);
    if (t.isDaemon())
        t.setDaemon(false);
    if (t.getPriority() != Thread.NORM_PRIORITY)
        t.setPriority(Thread.NORM_PRIORITY);
    return t;
}

新しい Thread に移動し、最終的に Thread#start メソッドが呼び出されてスレッドが開始されることがわかります。


Java スレッドの実行で run メソッドではなく start メソッドが呼び出されるのはなぜですか?


start メソッドは Java スレッド → JVM スレッド → OS スレッドの生成過程に対応するローカルメソッドであり、run メソッドは Thread オブジェクトの単なるメソッドであり、実行しても OS スレッドは生成されません。


2.2 Javaスレッドの実装原理


以下は、Java スレッドが Java コード レベルからオペレーティング システム レベルまで Thread#start メソッドを呼び出すプロセス全体です。

 

Coroutines
, English Coroutines, is a kind of exist based on threads, but light than threads. コルーチンは、オペレーティング システム カーネルによって管理されませんが、プログラムによって完全に制御され (つまり、ユーザー モードで実行されます)、目に見えないプロパティがあります。カーネル。
これの利点は、パフォーマンスが大幅に向上し、スレッド切り替えなどのリソースを消費しないことです。



サブルーチンまたは関数は、すべての言語で階層的に呼び出されます。たとえば、A が B を呼び出し、B が実行中に C を呼び出し、C が実行後に戻り、B が実行後に戻り、最後に A が実行を完了します。コルーチンの呼び出しは、サブルーチンとは異なります。コルーチンはサブルーチン内で割り込み可能であり、次に別のサブルーチンを実行するようになり、その後、適切なタイミングで実行を継続するために戻ります。

ジャワ

def A():
    print '1'
    print '2'
    print '3'
def B():
    print 'x'
    print 'y'
    print 'z'

 

コルーチンによって実行されると仮定すると、A の実行中にいつでも中断して B を実行でき、B も実行プロセス中に中断してから A を実行できます。結果は次のようになります。 1 2 xy 3 z。
コルーチンの特徴は、スレッドによって実行されることです。
コルーチンのメリット
        ●スレッドの切り替えはOSがスケジューリングし、コルーチンはユーザー自身がスケジューリングするため、コンテキストの切り替えが減り、効率が上がります。
        ●スレッドのデフォルトのスタック サイズは 1M ですが、コルーチンは軽量で 1k に近くなっています。そのため、同じメモリ内でより多くのコルーチンを開くことができます。
        ●マルチスレッドのロック機構が不要:スレッドが1つしかないため、変数の同時書き込み競合が発生しない コルーチンでは共有リソースをロックせずに制御し、状態の判断のみでよいため、実行効率は、マルチスレッドよりもはるかに高くなります。
コルーチンは、ブロックされ、多くの同時実行が必要なシナリオ (ネットワーク io) に適しています。負荷の高いコンピューティング シナリオには適していません。


Java
kilim quasarのコルーチン フレームワーク


2.3 Java スレッドのスケジューリングメカニズム


スレッド スケジューリングとは、システムがスレッドにプロセッサの使用権を割り当てるプロセスを指します。主なスケジューリング方法には、協調スレッド スケジューリングとプリエンプティブ スレッド スケジューリングの 2 つがあります。


協調スレッド スケジューリング


スレッドの実行時間は、スレッドによって決まります。スレッドがタスクの実行を終了した後、別のスレッドに切り替えるようにシステムに積極的に通知する必要があります。
利点:
        1 単純な実装、コンテキスト切り替えがスレッドに表示される
        2 スレッド同期の問題がない
欠点:
        1 実行時間が制御できない。スレッドがブロックされると、CPU がブロックされたままになることがあります。
プリエンプティブ スレッド スケジューリング
オペレーティング システムは各スレッドに実行タイム スライスを割り当て、スレッドの切り替えは OS によって決定されます。利点:
1. スレッドの実行時間は制御可能であり、1 つのスレッドがブロックされるため、CPU 全体がブロックされることはありません。


Java スレッドのプリエンプティブ スレッド スケジューリングの実施形態


システムが一部のスレッドにより多くの時間を割り当て、一部のスレッドにより少ない時間を割り当てられることが望まれます。これは、スレッドの優先順位を設定することで実行できます。Java言語には合計10段階のスレッド優先度(Thread.MIN_PRIORITY~Thread.MAX_PRIORITY)があり、2つのスレッドが同時に実行可能状態になると、優先度の高いスレッドが選択されて実行される可能性が高くなります。システム。ただし、Java スレッドはシステムのネイティブ スレッドへのマッピングによって実装されるため、優先順位はあまり信頼できません。そのため、スレッドのスケジューリングは最終的にオペレーティング システムに依存します。


2.4 Java スレッドのライフサイクル


Java 言語のスレッドの状態には、次の 6 つがあります。
1NEW (初期化状態)
2RUNNABLE (実行可能状態 + 実行中状態)
3BLOCKED (ブロック状態)
4WAITING (無制限の待機)
5TIMED_WAITING (時間指定待機)
6TERMINATED (終了状態)
動作中システム レベル、BLOCKED、WAITING、および Java スレッドの TIMED_WAITING は状態、つまり、前述の休止状態です。つまり、Java スレッドがこれら 3 つの状態のいずれかにある限り、そのスレッドは CPU を使用する権利を持ちません。


yield は、park、wait、join などの操作とは異なり、コンテキスト切り替えなどの重い操作を伴い、すぐに再度実行できます。


JavaThread の観点から、JVM は Java Thread オブジェクト (jvm.h) のいくつかの状態を定義します。

*/


OSThread の観点から、JVM は、jstack によって出力されるスレッド スタック情報 (osThread.hpp) 内のスレッドの状態など、外部で使用するためのいくつかのスレッド状態も定義します。


2.5 スレッド共通メソッド

寝る

  • sleep を呼び出すと、現在のスレッドが Running から TIMED_WAITING 状態になり、オブジェクト ロックが解放されません。
  • 他のスレッドは、割り込みメソッドを使用してスリープ状態のスレッドに割り込むことができます。このとき、スリープ メソッドは InterruptedException をスローし、割り込みフラグをクリアします。
  • スリープ終了後のスレッドがすぐに実行されない場合がある
  • 入力パラメータが 0 の場合、sleep は yield と同じです

収率

  • Yield は CPU リソースを解放し、現在のスレッドを Running から Runnable 状態に移行させ、より優先度の高い (少なくとも同じ) スレッドに実行機会を与え、オブジェクト ロックを解放しません。
  • 現在のプロセスにメイン スレッドしかない場合、yield を呼び出した後、メイン スレッドは引き続き実行されます。これは、メイン スレッドより優先度の高いスレッドがないためです。
  • 具体的な実装は、オペレーティング システムのタスク スケジューラに依存します。

加入

After Waiting for the thread Calling the join method to end, the program continue to execute. 一般に、非同期スレッドが実行を続行する前に結果の実行を終了するシナリオで使用されます。

public class ThreadJoinDemo {
    public static void main(String[] sure) throws InterruptedException {

        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("t begin");
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("t finished");
            }
        });
        long start = System.currentTimeMillis();
        t.start();
        //主线程等待线程t执行完成
        t.join();

        System.out.println("执行时间:" + (System.currentTimeMillis() - start));
        System.out.println("Main finished");
    }
}

ストップ

stop() メソッドは jdk によって放棄されました。なぜなら、stop() メソッドは、途中で実行されたスレッドを強制的に終了させる、あまりにも暴力的だからです。

このメソッドはオブジェクトのロックを解除するため、スレッドが実行の途中で強制的に終了され、データの不整合が発生する可能性があります。

public class ThreadStopDemo {
    private static Object lock = new Object();

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

        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (lock) {
                    System.out.println(Thread.currentThread().getName() + "获取锁");
                    try {
                        Thread.sleep(60000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println(Thread.currentThread().getName() + "执行完成");
            }
        });
        thread.start();
        Thread.sleep(2000);
        // 停止thread,并释放锁
        thread.stop();

        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + "等待获取锁");
                synchronized (lock) {
                    System.out.println(Thread.currentThread().getName() + "获取锁");
                }
            }
        }).start();

    }
}

スレッドを適切に停止するには?

Java は、スレッドを正常に停止するための割り込みメカニズムを提供します。以下を参照してください。

2.6 Java スレッドの割り込みメカニズム

Java は、スレッドを停止するための安全で直接的な方法を提供しませんが、割り込みメカニズムを提供します。割り込みメカニズムは協調メカニズムです。つまり、割り込みによって別のスレッドを直接終了することはできませんが、割り込みを受けたスレッドはそれを自分で処理する必要があります。中断されたスレッドには完全な自律性があり、すぐに停止するか、一定時間後に停止するか、まったく停止しないかを選択できます。

簡単に言うと、スレッド A が実行されているときに、スレッド B に割り込みたい場合、スレッド B にフラグが設定されます。スレッド B のコード実装では、フラグのチェックポイントが存在する可能性があります。フラグ ビットが割り込みを示していることが判明した場合、スレッド B は独自のコード ロジックを呼び出して割り込み要求を処理します。たとえば、スレッド B 自体を停止したり、無視したりできます。

API の使用

  • interrupt(): スレッドを停止せずに、スレッドの割り込みフラグ ビットを true に設定します
  • isInterrupted(): 現在のスレッドの割り込みフラグ ビットが true であるかどうかを判断し、割り込みフラグ ビットをクリアしません。
  • Thread.interrupted(): 現在のスレッドの割り込みフラグ ビットが true かどうかを判断し、割り込みフラグ ビットをクリアして、fasle にリセットします

割り込みメカニズムを使用する場合は、割り込みフラグ ビットがクリアされる状況があるかどうかに注意する必要があります。つまり、次の 2 つの状況を注意深く区別する必要があります。

テストisInterrupted()

public class ThreadInterruptTest {
    static int i = 0;

    public static void main(String[] args) {
        System.out.println("begin");
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    i++;
                    System.out.println(i);
                    //Thread.interrupted()  清除中断标志位
                    //Thread.currentThread().isInterrupted() 不会清除中断标志位
                    if (Thread.currentThread().isInterrupted()) {
                        System.out.println("=========");
                    }
                    if (i == 10) {
                        break;
                    }

                }
            }
        });

        t1.start();
        //不会停止线程t1,只会设置一个中断标志位 flag=true
        t1.interrupt();
    }
}

テストThread.interrupted()

public class ThreadInterruptTest {
    static int i = 0;

    public static void main(String[] args) {
        System.out.println("begin");
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    i++;
                    System.out.println(i);
                    //Thread.interrupted()  清除中断标志位
                    //Thread.currentThread().isInterrupted() 不会清除中断标志位
                    if (Thread.interrupted()) {
                        System.out.println("=========");
                    }
                    if (i == 10) {
                        break;
                    }

                }
            }
        });

        t1.start();
        //不会停止线程t1,只会设置一个中断标志位 flag=true
        t1.interrupt();
    }
}

割り込みメカニズムを使用してスレッドを正常に停止する

while (!Thread.currentThread().isInterrupted() && more work to do) {
    do more work
}

public class StopThread implements Runnable {

    @Override
    public void run() {
        int count = 0;
        while (!Thread.currentThread().isInterrupted() && count < 1000) {
            System.out.println("count = " + count++);
        }
        System.out.println("线程停止: stop thread");
    }

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new StopThread());
        thread.start();
        Thread.sleep(5);
        thread.interrupt();
    }
}

睡眠中に邪魔を感じますか

@Override
public void run() {
    int count = 0;
    while (!Thread.currentThread().isInterrupted() && count < 1000) {
        System.out.println("count = " + count++);

        try {
            Thread.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    System.out.println("线程停止: stop thread");
}

休眠中のスレッドは中断され、スレッドは割り込み信号を感知でき、InterruptedException 例外をスローし、同時に割り込み信号をクリアし、割り込みフラグ ビットを false に設定します。これにより、while 条件 Thread.currentThread().isInterrupted() が false になり、条件カウント < 1000 が満たされない場合、プログラムは終了します。手動で catch に割り込みシグナルを追加せず、何も処理を行わないと、割り込み要求がブロックされ、スレッドが正常に停止しない可能性があります。

try {
    Thread.sleep(1);
} catch (InterruptedException e) {
    e.printStackTrace();
    //重新设置线程中断状态为true
    Thread.currentThread().interrupt();
}

要約する

スリープが中断される可能性があり、割り込み例外がスローされます: スリープが中断され、割り込みフラグ ビットがクリアされます。

割り込み例外をスローすることにより、待機を中断できます: InterruptedException、割り込みフラグをクリアします。

2.7 Java スレッド間の通信

揮発性

Volatile には 2 つの主要な機能があります。1 つは可視性、もう 1 つは命令の並べ替えを禁止する順序であり、可視性はスレッド間の通信を許可することです。

public class VolatileTest {
    private static volatile boolean flag = true;

    public static void main(String[] args) {

        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    if (flag) {
                        System.out.println("trun on");
                        flag = false;
                    }
                }
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    if (!flag) {
                        System.out.println("trun off");
                        flag = true;
                    }
                }
            }
        }).start();
    }
}

ウェイクアップメカニズムの待機/通知メカニズムの待機

待機ウェイクアップメカニズムは、wait メソッドと notify メソッドに基づいて実装できます.スレッド内のスレッドロックオブジェクトの wait メソッドを呼び出すと、スレッドは待機キューに入り、ウェイクアップされるまで待機します.

public class WaitDemo {
    private static Object lock = new Object();
    private static boolean flag = true;

    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (lock) {
                    while (flag) {
                        try {
                            System.out.println("wait start .......");
                            lock.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }

                    System.out.println("wait end ....... ");
                }
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                if (flag) {
                    synchronized (lock) {
                        if (flag) {
                            lock.notify();
                            System.out.println("notify .......");
                            flag = false;
                        }

                    }
                }
            }
        }).start();
    }
}

LockSupport は JDK でスレッドのブロックとウェイクアップを実現するツールで、以下の特徴があります。

  • スレッドは park を呼び出して「許可」を待ち、unpark を呼び出して指定されたスレッドに「許可」を提供します。
  • 任意のスレッドをブロックするために使用し、起床するスレッドを任意に指定でき、ブロックと起床操作の順序を気にする必要はありませんが、複数起床の影響に注意する必要があります連続して1回起きます。
public class LockSupportTest {

    public static void main(String[] args) {
        Thread parkThread = new Thread(new ParkThread());
        parkThread.start();

        System.out.println("唤醒parkThread");
        LockSupport.unpark(parkThread);
    }

    static class ParkThread implements Runnable{

        @Override
        public void run() {
            System.out.println("ParkThread开始执行");
            LockSupport.park();
            System.out.println("ParkThread执行完成");
        }
    }
}

パイプラインの入力ストリームと出力ストリーム

パイプライン入出力ストリームと通常のファイル入出力ストリームまたはネットワーク入出力ストリームの違いは、主にスレッド間のデータ伝送に使用され、伝送媒体がメモリであるということです。パイプラインの入出力ストリームには、主に次の 4 つの特定の実装が含まれます。

  • バイト指向の
    PipedOutputStream、PipedInputStream
  • 文字指向の
    PipedReader、PipedWriter
public class Piped {
    public static void main(String[] args) throws Exception {
        PipedWriter out = new PipedWriter();
        PipedReader in = new PipedReader();
        // 将输出流和输入流进行连接,否则在使用时会抛出IOException
        out.connect(in);

        Thread printThread = new Thread(new Print(in), "PrintThread");

        printThread.start();
        int receive = 0;
        try {
            while ((receive = System.in.read()) != -1) {
                out.write(receive);
            }
        } finally {
            out.close();
        }
    }

    static class Print implements Runnable {
        private PipedReader in;

        public Print(PipedReader in) {
            this.in = in;
        }

        @Override
        public void run() {
            int receive = 0;
            try {
                while ((receive = in.read()) != -1) {
                    System.out.print((char) receive);
                }
            } catch (IOException ex) {
            }
        }
    }
}

スレッド#結合()

ジョインはスレッドのマージとして理解できます. スレッドが別のスレッドのジョイン メソッドを呼び出すと, 現在のスレッドはブロックされ, ジョイン メソッドと呼ばれるスレッドの実行が終了するのを待ってから続行します. したがって, ジョインの利点は、スレッドですが、呼び出しスレッドの結合メソッドは、実際には並列処理の意味を失っています. 複数のスレッドがありますが、それらは本質的にシリアルです. 最終的な結合の実装は、実際には待機通知メカニズムに基づいています.

おすすめ

転載: blog.csdn.net/peterjava123/article/details/130217747