Javaは長い間開発されてきましたが、これらのスレッドの基本をご存知ですか?

Java開発に精通している人は、Javaがマルチスレッドプログラミングを自然にサポートすることを知っています。この記事では、Javaスレッドの基本をより体系的に把握することを目的として、スレッドの起動から異なるスレッド間の通信方法まで、主にJavaスレッドの基本を学びます。

この記事の説明は、主に次の点から始まります。

  1. スレッドとは
  2. スレッドの状態は何ですか
  3. スレッドの開始と終了
  4. スレッド間の通信
  5. この記事で説明するスレッドの知識を使用して、単純なスレッドプールを実装します。

上記のポイントをマスターしている場合は、この記事が役に立たない可能性があります。選択的に見下ろします。

わかりました、今日のトピックにステップインし始めます

[外部リンク画像の転送に失敗しました。ソースサイトにアンチリーチリンクメカニズムがある可能性があります。画像を保存して直接アップロードすることをお勧めします(img-Zo0oaNHN-1597519114515)(en-resource:// database / 2540:1)]

スレッドとは

以下は、オペレーティングシステムのプロセスとスレッドの概念です。

最新のオペレーティングシステムがプログラムを実行すると、そのためのプロセスが作成されます。たとえば、Javaプログラムを起動すると、オペレーティングシステムによってJavaプロセスが作成されます。最新のオペレーティングシステムスケジューリングの最小単位はスレッドであり、軽量プロセスとも呼ばれます。1つのプロセスで複数のスレッドを作成できます。これらのスレッドは、独自のカウンター、スタック、ローカル変数を持ち、それらにアクセスできます。共有メモリ変数。プロセッサはこれらのスレッドを高速でオンに切り替えるので、ユーザーはこれらのスレッドが同時に実行されていると感じます。

では、Javaのスレッドとは何でしょうか。

Javaのスレッド

Javaでは、「スレッド」は2つの異なるものを指します。

  1. java.lang.Threadクラスのインスタンス。
  2. スレッド実行

java.lang.Threadクラスまたはjava.lang.Runnableインターフェースを使用して、新しいスレッドを定義、インスタンス化、および開始するコードを記述します。Threadクラスのインスタンスは、Javaの他のオブジェクトと同様に、ヒープ上で存続し死ぬ変数とメソッドを持つ単なるオブジェクトです。

Javaアプリケーションは常にmain()メソッドから実行され、mian()メソッドはメインスレッドと呼ばれるスレッドで実行されます。新しいスレッドが作成されると、新しい呼び出しスタックが生成されます。

Javaには特別なスレッド、つまりデーモンスレッドがあることに注意してください

デーモンスレッドは、主にプログラムのバックグラウンドスケジューリングとサポート作業に使用されるため、サポートスレッドです。つまり、Java仮想マシンに非デーモンスレッドがない場合、Java仮想マシンは終了します。Thread.setDaemon(true)を呼び出すことにより、スレッドをデーモンスレッドとして設定できます。Daemon属性は、スレッドの開始前に設定する必要があり、スレッドの開始後は設定できません。

スレッドのステータス

Javaスレッドは、ライフサイクル全体で6つの状態に分けられますが、どの時点でも、スレッドは1つの状態にしかなりません。

州名 説明
新着 名前が示すように、初期状態では、作成後にstartメソッドが呼び出されていません
実行可能 実行中の状態。Javaスレッドは、オペレーティングシステムで実行中であることをまとめて「実行中」と呼びます。
ブロックされた ブロックされた状態、つまり同期されたライフの相互に排他的なリソースなど、スレッドがロックでブロックされている
待機中 待機状態であり、この状態に入ると、他のスレッドが特定の操作(通知や割り込みなど)を実行する必要があることを意味します
TIME_WAITING タイムアウト待機状態は、指定された時間にそれ自体で戻ることができるという点で、WAITINGとは異なります。
終了した 終了ステータス。現在のスレッドが実行を完了したことを示します

興味のある学生は、jstackツールを使用して、実際に実行されている実行中のコードのスレッド情報を表示できます。これは、単に見ているだけでなく、スレッドのさまざまな状態をより深く理解できます。

ここに提供されているサンプルコードは次のとおりで、サンプルのコードはgithubにアップロードされています。これを必要とする学生は、このアドレスから取得できます
https://github.com/coderluojust/qige_blogs

ここに画像の説明を挿入

jstackを使用して表示する手順は次のとおりです。

  1. 上記の例を実行し、ターミナルまたはコマンドプロンプトを開きjps、と入力して、対応するプロセス番号を見つけます。
11024
13376 ThreadState
12644 Launcher
9652 KotlinCompileDaemon
5032 Jps
  1. 前のステップで取得した例に対応するプロセス番号は13376であり、次にinputと入力するjstack 13376と、現在のプログラムのスレッドスタックを取得してステータスを確認できます。
    出力の一部は次のとおりです。

// BlockedThread-2线程阻塞在获取Blocked.class示例的锁上
"BlockedThread-2" prio=5 tid=0x00007feacb05d000 nid=0x5d03 waiting for monitor
entry [0x000000010fd58000]
java.lang.Thread.State: BLOCKED (on object monitor)
// BlockedThread-1线程获取到了Blocked.class的锁
"BlockedThread-1" prio=5 tid=0x00007feacb05a000 nid=0x5b03 waiting on condition
[0x000000010fc55000]
java.lang.Thread.State: TIMED_WAITING (sleeping)
// WaitingThread线程在Waiting实例上等待
"WaitingThread" prio=5 tid=0x00007feacb059800 nid=0x5903 in Object.wait()
[0x000000010fb52000]
java.lang.Thread.State: WAITING (on object monitor)
// TimeWaitingThread线程处于超时等待
"TimeWaitingThread" prio=5 tid=0x00007feacb058800 nid=0x5703 waiting on condition
[0x000000010fa4f000]
java.lang.Thread.State: TIMED_WAITING (sleeping)

独自のライフサイクルでは、スレッドは特定の状態に固定されず、コードが実行されると異なる状態に切り替わります。Javaスレッドの状態遷移については、次の図を参照してください。

ここに画像の説明を挿入

スレッドの開始と終了

スレッドを実行する前に、まずスレッドオブジェクトを作成する必要があります。スレッドが属するスレッドグループ、スレッドの優先順位、デーモンスレッドであるかどうか、その他の情報など、プロセスオブジェクトは、スレッドの作成時にスレッドが必要とする属性を提供する必要があります。情報のこの部分については、Threadクラスのinit()メソッドを表示できます。以下java.lang.Threadは、初期化メソッドinitの一部インターセプトします。

ここに画像の説明を挿入

スレッドを開始

スレッドオブジェクトが初期化された後、start()メソッドを呼び出してスレッドを開始します。スレッドstart()メソッドの意味は次のとおりです。現在のスレッド(つまり、親スレッド)は、スレッドプランナーがアイドル状態である限り、start()メソッドを呼び出すスレッドをすぐに開始する必要があることをJava仮想マシンに同期的に通知します。

注意:启动一个线程前,最好为这个线程设置线程名称,因为这样在使用jstack分析程序或者进行问题排查时,就会给我们提供一些提示,自定义的线程最好能够起个名字。

スレッドを安全に終了する

スレッドが安全に終了したと言われるのは、最初の一時的なスレッド、再開スレッド、停止スレッドがスレッドAPIを使用しているためです。suspend()、resume()、stop()です。ただし、これらのAPIは有効期限が切れているため、推奨されません。使用が推奨されない具体的な理由は、主に次のとおり
です。一例として、suspend()メソッドを使用します。呼び出しの後、スレッドは占有されているリソース(ロックなど)を解放せず、リソースを占有してスリープ状態になり、デッドロックの問題が発生しやすくなります。同様に、stop()メソッドは、スレッドの終了時にスレッドのリソースの正常な解放を保証しません。通常、スレッドにはリソース解放作業を完了する機会が与えられないため、プログラムは不確実な状態で動作する可能性があります。

上記は、スレッドを使用して、関連するAPIを一時停止、再開、停止することをお勧めしていません。それから、スレッドを安全に終了し、復元待機する方法はどうですか?

  1. スレッド割り込みを使用します。つまり、スレッドのinterrupt()メソッドを呼び出し、割り込み状態を変更してスレッド間の相互作用を実現し、それによってタスクを停止します。
  2. ブール変数を使用して、スレッドを終了するためにタスクを停止する必要があるかどうかを制御します。

サンプルコードは次のとおりです。

ここに画像の説明を挿入

この例では、メインスレッドは、割り込み操作とcancel()メソッドを使用してCountThreadを停止できます。これらの2つのメソッドは、スレッドを任意に停止する代わりに、終了時にリソースをクリーンアップする機会をスレッドに与えることができるため、この種の終了スレッドアプローチはよりエレガントで安全です。

賢い質問かもしれませんが、スレッドの終了の問題は解決されました。待機と復元はどうですか。

ここに画像の説明を挿入

強力なJavaには確かに解決策があります。待機/通知メカニズムを使用して、特定のスレッドの待機と回復を行うことができます。次に、これをスレッド間通信メカニズムの説明で説明します。

スレッド間通信

スレッドは実行を開始し、スクリプトのように独自のスタックスペースを持ち、終了するまで確立されたコードに従って段階的に実行されます。ただし、実行中の各スレッドが分離して実行される場合、価値がないか、価値がほとんどないため、複数のスレッドが互いに連携して作業を完了することができる場合、これは大きな価値をもたらします。

スレッド同士が連携して作業を完了する必要があるため、スレッド間の通信が最初に解決する問題ですが、スレッド間の通信の方法を学びましょう。

揮発性および同期化されたキーワード

Javaメモリーモデルに関するこれまでの記事では、並行性のバグの3つの原因(可視性、秩序性、および原子性)を解決するためにjvmレベルで提供されるvolatileおよびsynchronizedキーワードを深く研究しました。

Javaは、オブジェクトまたはオブジェクトのメンバー変数に同時にアクセスするためにJavaが複数のスレッドをサポートしているため、スレッド間の通信をサポートしていることは誰もが知っています。ただし、最新のマルチコアプロセッサのため、CPUとメモリのパフォーマンスの違いを解決するために、各スレッドはCPUキャッシュに格納された変数のコピーを持っているため、プログラムの実行中に、スレッドに表示されるものが最新ではない場合がありますこの内容は、スレッド間の通信に影響します。

キーワードvolatileを使用してフィールドを変更できます。これは、変数へのアクセスを共有メモリから取得する必要があることをプログラムに通知し、変数への変更を同期的に共有メモリに更新する必要があることを実装します(実装の原則は、メモリバリアと同等のロックプレフィックス命令によるものです。その機能は、現在のプロセッサキャッシュラインのデータをすぐにメモリに書き戻すことです。ライトバック操作は、バスを介してデータを拡散します。他のプロセッサは、バス上のデータ拡散をスニッフィングし、メモリアドレスに対応するデータ変更がそれぞれのキャッシュを無効にすることを発見します。)変数アクセスに対するすべてのスレッドの可視性を確保するため。

同期キーワードは、メソッドを変更するために、または同期ブロックの形式で使用できます。これにより、複数のスレッドが同時にメソッドまたは同期ブロックにのみ存在できることが保証されます。これにより、変数へのスレッドアクセスの可視性と可視性が保証されます。独占性。

待機/通知メカニズム

簡単に言えば、待機/通知メカニズムはプロデューサーおよびコンシューマーモデルです。1つのスレッドがオブジェクトの値を変更し、もう1つのスレッドが変更を認識して、対応する操作を実行します。では、この機能をどのように実現するのでしょうか?

最も簡単な解決策は、コンシューマースレッドでエンドレスループを記述して、変数が期待を満たしているかどうかを継続的に確認することです。次の擬似コードを参照できます。

while (value != desire) {
    
    
Thread.sleep(1000);
}
doSomething();

この方法は必要な機能を実現しますが、次の問題があります。

  1. 十分にタイムリーではありません。基本的に、スリープ中にCPUリソースを消費しませんが、長時間スリープし、時間の変化を見つけることができません。
  2. オーバーヘッドを減らすことは困難です。適時性を確保したい場合は、スリープ時間を減らす必要があります。これにより、より多くのCPUリソースが消費されます。

上記の2つの質問は相互に排他的であることがわかります。この矛盾を解決するには、Javaの組み込みの待機通知メカニズムが必要です。

これらのメソッドはすべてoriginator java.lang.Object定義されているオブジェクトであるため、Javaオブジェクトに関連する待機/通知が含まれます。関連するメソッドは次のとおりです。

メソッド名 解説
notify() オブジェクトでwait()メソッドから戻るのを待機しているスレッドに通知します。戻りの前提は、スレッドがオブジェクトのロックを取得することです。
notifyAll() オブジェクトを待機しているすべてのスレッドに、WAITING状態からBLOCKED状態への変更を通知します
待つ() このメソッドを呼び出すスレッドは待機状態になり、別のスレッドによって中断または通知された場合にのみ戻ります。wait()メソッドを呼び出すと、オブジェクトのロックが解放されることに注意してください。
待つ(長い) 一定期間待機します。単位はミリ秒で、有効期限が切れると通知なしに戻ります。
wait(long、int) タイムアウト期間をより細かく制御するには、ナノ秒を実現できます

待機/通知メカニズムは、スレッドAがオブジェクトOのwait()メソッドを呼び出して待機状態に入り、別のスレッドBがオブジェクトOのnotify()またはnotifyAll()メソッドを呼び出し、スレッドAがオブジェクトから通知を受け取ることを意味しますOのwait()メソッドが戻り、その後の操作を実行します。上記の2つのスレッドはオブジェクトOを介して対話を完了し、オブジェクトのwait()とnotify / notifyAll()の関係は、待機中のパーティと通知側の間の対話を完了するスイッチ信号のようなものです。

スペース上の理由により、対応するサンプルコードもあり、コードを表示しないでください。実践的に理解を深めたい場合は、同じように、この記事の例がgithubにアップロードされて含まれています。テキストをクリックして元を読んで最後を確認し、WaitNotifyこのクラスを探します

wait()、notify()、notifyAll()を使用するときに習得すべき詳細がいくつかあります。

  1. wait()、notify()、notifyAll()を呼び出します。最初に呼び出しオブジェクトをロックする必要があります。
  2. wait()メソッドを呼び出した後、スレッドのステータスはRUNNINGからWAITINGに変わり、現在のオブジェクトロックを解放し、現在のスレッドをオブジェクトの待機キューに入れます。
  3. notify()メソッドとnotifyAll()メソッドが呼び出された後も、待機中のスレッドはwait()から戻りません。notify()とnotifyAll()を呼び出すスレッドがロックを解放すると、待機中のスレッドがロックを獲得し、ロックを取得できます。 wait()が戻ります。
  4. notify()メソッドは待機キュー内の待機スレッドを待機キューから同期キューに移動し、notifyAll()メソッドは待機キュー内のすべてのスレッドを同期キューに移動し、移動したスレッドの状態はWAITINGから変更されますブロックされています。
  5. wait()メソッドから戻ることの前提は、呼び出し元オブジェクトのロックを取得することです。

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

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

PipedOutputStream、PipedInputStream、PipedReader、PipedWriterの4つの主要な実装があります。最初の2つはバイト指向で、後の2つは文字指向です。

使用方法は次のとおりです。

PipedWriter out = new PipedWriter();
PipedReader in = new PipedReader();
// 将输出流和输入流进行连接,否则在使用时会抛出IOException
out.connect(in);

Thread.join()

スレッドAがスレッドBのjoin()メソッドを呼び出すと、現在のスレッドAはスレッドBの終了を待ってからB.join()から戻ります。join()メソッドに加えて、スレッドThreadは待機タイムアウトメソッドであるjoin(long)およびjoin(long、int)も提供します。

実際、ここでは待機/通知メカニズムも使用されます。つまり、AスレッドはBスレッドのjoin()メソッドを同期的に呼び出し、ループ待機に入り、Bスレッドが終了するまで待機し、通知を受信して​​B.join()メソッドから戻り、後続のロジックを実行します。

以下は、JDKのThread.join()メソッドのコアソースコードです。これは、上記で説明したものと同じです(部分的に調整した後)。


// 加锁当前线程对象
public final synchronized void join(long millis)throws InterruptedException {
    
        
    // 条件不满足,继续等待
    while (isAlive()) {
    
               
        wait(0);        
     }    
     // 条件满足,返回
} 

ThreadLocalの使用

現在のスレッドにバインドされてThreadLocal.ThreadLocalMapいる現在のスレッドプロパティを取得するためのパラメーターとして現在のスレッドに基づく、そのThreadLocalスレッド変数ThreadLocalMapこのThreadLocalMapも、キーとしてのThreadLocalオブジェクトと値としての任意のオブジェクトを持つマップデータ構造です。つまり、スレッドは、ThreadLocalオブジェクトに基づいて、このスレッドにバインドされた値を照会できます。

アプリケーションの実践

ことわざにあるように、考えずに学ぶことは無駄に学ぶことと同じです。次に、今日学習したスレッドの基本(スレッドのステータス、通知の待機、その他のスレッドベースのメソッドを含む)を組み合わせて、統合と改善のための単純なスレッドプールを実装します。

主な機能は、特定の数のスレッドを事前に作成することであり、ユーザーはスレッドの作成を直接制御する必要はありません。ユーザーは、実行する必要があるタスクをスレッドプールに送信するだけでよく、スレッドプールは固定数のスレッドを再利用してタスクを完了します。利点はスレッドの頻繁な作成と破棄のオーバーヘッドを減らし、1つのタスクと1つのスレッドがシステムの頻繁なコンテキスト切り替え引き起こさないようにすることです。これにより、システムの負荷が増加します。

スレッドプールインターフェイスは次のように定義されます。

ここに画像の説明を挿入

特定の実装はここに示されていません。興味がある場合は、私のgithubにアクセスして、対応する実装を見つけることができます。

総括する

この記事では主に、Javaでの並行プログラミングの基本的な知識を説明します。スレッドとは何か、スレッドのさまざまな状態、そして正常に安全に開く方法と終了する方法について説明します。スレッド間のさまざまな通信方法、およびスレッドプールによる最後の例である待機/通知メカニズムの古典的なパラダイムは、Javaマルチスレッドの基本を統合し、理解を深めます。

記事で説明されているように対応するコード例を実行できれば、Javaマルチスレッドの基本知識をより深く理解し、習得できると思います。

2020.04.12
ファイティング!

個人公開口座

ここに画像の説明を挿入

  • 彼らが上手に書いていると感じている友人は気に入ることができて、フォローすることができます
  • 記事が正しくない場合は、指摘してください。読んでいただきありがとうございます。
  • 私は公式アカウントに注意を払うことをお勧めします。私は定期的にオリジナルの乾物記事をプッシュし、あなたを高品質の学習コミュニティに引き込みます。
  • githubアドレス:github.com/coderluojust/qige_blogs

おすすめ

転載: blog.csdn.net/taurus_7c/article/details/105467897