第1章 JUCの概要

JUCとは

Java 5.0 では、java.util.concurrent (略して JUC) パッケージが提供されており、このパッケージでは、スレッド プール、非同期 IO、軽量タスク フレームワークなど、スレッドに似たカスタム サブシステムを定義するために、並行プログラミングで一般的に使用されるユーティリティ クラスが追加されています。 。調整可能で柔軟なスレッド プールを提供します。マルチスレッド コンテキストなどで使用するために設計された Collection 実装も提供します。

  • 関係するコンテンツ
    • java.util.concurrent
    • java.util.concurrent.atomic
    • java.util.concurrent.locks

複数のスレッドについて学ぶことが重要な理由

ハードウェア的には

  • ムーアの法則: 2003 年以降、CPU のメイン周波数は 2 倍にならなくなり、メイン周波数の高速化の代わりにマルチコアが採用されました。これはムーアの法則の失敗です。
  • メイン周波数が 2 倍にならなくなり、コア数が増加し続けると、プログラムを高速化するために並列テクノロジまたは同時実行テクノロジを使用する必要があります。

ソフトウェアに関しては

  • 面接には必須
  • マルチコアプロセッサを活用する
  • プログラムのパフォーマンスを向上させ、高い同時実行性のシステムを実現
  • プログラムのスループットを向上させ、コールバックやその他の運用上のニーズを非同期的に追加します

問題点

  • スレッドの安全性の問題
  • ネジロックの問題
  • スレッドのパフォーマンスの問題

Java マルチスレッド関連の概念

ロック

  • 同期については後ほど説明します

2つを組み合わせたもの

同時実行性と並列処理

レストランでの食事を例に挙げると、レストランに 3 人の客が来て、これらの料理の需要がありました。

画像

  • シリアル: このレストランにはシェフが 1 人しかいません。3 人のゲストの要求を満たしたい場合は、1 人ずつ料理することしかできません。あなたの 3 つのコースのニーズを満たして初めて、私の 3 つのコースのニーズを満たすことができます。この一つ一つを順番に満たしていく方式が逐次処理方式です
  • 並列: このレストランには 3 人のシェフがいて、各シェフが自分の顧客にサービスを提供するため、3 人の顧客のニーズが同時に満たされます。この処理モードは並列処理です。
  • 並行性: レストランにはまだシェフが 1 人しかいませんが、1 人の顧客のために料理を作り、その後別のユーザーの料理に移りました。タイム スライスの概念に従って処理されます。タイム スライスごとに処理が異なる場合があります。もの。の

シェフの料理がとても速く、1分で料理を作ることができ、私が料理を提供し、3分後に別の料理が出てくると、実際の生活では、このシェフは料理を専門にしていると感じるでしょう。この考えをコンピュータに当てはめると、CPU の処理速度は非常に速く、基本的には ns 単位の処理速度であることがわかっています。したがって、CPU がコンピュータのタスクを同時に実行すると、私たちの主観的な意識では、この CPU がタスクを処理しているように感じます。並行して

3つのプロセス

プロセス、スレッド、モニター

プロセスとプログラムの関係

プログラムとは何ですか

  • プログラムは、さまざまな効果を実現するためにオペレーティング システムのさまざまな API をカプセル化した一連の編成されたファイルです (プログラムは一連の命令であり、プログラムのコードはプログラム セグメントに配置され、出力データと入力データはプログラム セグメントに配置されます)。データセグメント)、QQ など、単なるプログラムです

プロセスとは何ですか

  • プロセスとは、プログラムの実行プロセスであり、システム内でリソースを割り当てるための独立した最小単位です (リソースとは、CPU やメモリなどの主要なシステム リソースを指します)。
  • たとえば、QQ に 2 回ログインすると、バックグラウンド タスク マネージャーに QQ プロセスが 2 回表示され、各プロセスは QQ によって実行されるプロセスになります。

画像

なぜプロセスを導入するのか

  • 現在のオペレーティング システムには、同時処理を実現するためのマルチプログラミング テクノロジが搭載されているため、メモリ上に複数のプログラムが配置され、複数のプログラム セグメントとデータ セグメントが存在します。オペレーティング システムは、対応するプログラムとデータ セグメントをどのようにして正しく見つけるのでしょうか。ということで導入 プロセスの概念を理解する(プログラムを並行して実行できるようにし、並行して実行されるプログラムを記述および制御するため)

プロセスとプログラムの違い

  • プロセスは動的、プログラムは静的

  • プロセスは同時に実行できますが、プログラムは同時に実行できません

  • 両者の間には 1 対 1 の対応関係はありません

  • プロセスとプログラムは密接に関係しており、プロセスを特定のプログラムから切り離すことはできず、プログラムは対応するプロセスによって実行されるアクションを規定します。

  • 構成が違います。このプロセスには、PCB、プログラム セグメント、およびデータ セグメントが含まれます。プログラムにはデータと命令コードが含まれています。

  • プログラムは、すべての命令とデータを含む静的なエンティティです。ディスクストレージスペースを占有するだけでなく、CPU やメモリなどのシステムオペレーティングリソースも占有しません。

  • このプロセスはプログラム セグメント、データ セグメント、および PCB で構成されており、CPU やメモリなどのシステム実行リソースを占有します。

  • プログラムは複数のプロセスを開始して、一緒にタスクを完了できます。

画像

进程调度算法

  • 先着順スケジューリング アルゴリズム (FCFS)
  • 短いジョブ(プロセス)優先スケジューリングアルゴリズム
  • 高優先優先スケジューリングアルゴリズム 1 ノンプリエンプティブ優先スケジューリングアルゴリズム 2 プリエンプティブ優先スケジューリングアルゴリズム 3 高応答率優先スケジューリングアルゴリズム
    • ハイレスポンススケジューリングアルゴリズムにおける優先度の計算

画像

スレッドとプロセス間の接続

什么是线程

  • 最新のオペレーティング システムはすべてマルチスレッド オペレーティング システムであり、スレッドはプロセスのサブタスクであり、プロセスは軽量プロセスです。
  • 私たちが開くブラウザーのプロセスや開く各 Web ページは独立したスレッドであり、各スレッドはサブタスクを実行できます。

画像

为什么引入线程

  • プロセスによっては、多くのことを「同時に」実行する必要がある場合がありますが、従来のプロセスは一連のプログラムを逐次的に実行することしかできません (たとえば、QQ を使用する場合、アカウントにログインします。これはプロセスを作成するのと同じです。次のように入力できます)。ビデオチャット中、これらは「同時に」実行されますが、従来のプロセスではそのような機能を実現できないため、同時実行を高速化するためにスレッドが導入されます)
  • OS にプロセスを導入する目的が、複数のプログラムを同時に実行できるようにしてリソース使用率とシステム スループットを向上させることである場合、OS にスレッドを導入する目的は、プログラムの同時実行に費やされる時間とスペースを削減することです。 、OS の同時実行性が向上します (プロセスが複数の機能を同時に完了できるようになります)。
  • スレッド導入後は、プリンタなどCPU以外のシステムリソースの割り当て単位がプロセスとなり、プロセスにメモリ空間が割り当てられます。
  • スレッドの導入後は、プログラムの同時実行だけでなく、プロセスの同時実行も可能となり、システムの同時実行性が向上します。

画像

线程和进程的比较

  • プロセスは OS リソース割り当ての基本単位であり、スレッドはスケジューリングの基本単位です。

  • プロセスの作成と使用のコストは、スレッドの作成と使用のコストよりもはるかに大きく、スレッドのスケジュール設定はプロセスよりもはるかに高速です。

  • プロセスにはスレッドが含まれており、各プロセスには少なくとも 1 つのスレッド (メイン スレッド) が含まれます。

  • プロセスは互いに独立しており、異なるプロセスはメモリ空間を共有しませんが、同じプロセスのスレッドはメモリ空間を共有し(プロセスのリソースを共有し)、スレッドはシステム リソースをほとんど所有しません。

  • マルチ CPU オペレーティング システムでは、異なるスレッドが異なる CPU を占有することができます。

  • 同じプロセスのスレッド切り替えはプロセス切り替えにつながりませんが、異なるプロセスのスレッド切り替えはプロセス切り替えにつながります。

线程的优点

  1. 新しいスレッドの作成は、新しいプロセスを作成するよりもはるかに低コストです

  2. プロセス間の切り替えと比較して、スレッド間の切り替えに必要なオペレーティング システムの作業ははるかに少なくなります。

  3. スレッドはプロセスよりもはるかに少ないリソースを消費します

  4. マルチプロセッサの並列数を最大限に活用でき、マルチコアCPUの利点を活かすことができます。

  5. 遅い I/O 操作が完了するのを待っている間、プログラムは他のコンピューティング タスクを実行できます。

  6. 計算負荷の高いアプリケーションの場合、マルチプロセッサ システムで実行するために、計算は複数のスレッドに分割されます。

管理

  • それがMonitor(モニター)、別名ロックです
  • モニターは実際には同期メカニズムであり、その義務は (保護されたデータとコードに同時にアクセスできるのは 1 つのスレッドのみであること) を保証することです。

なぜモニターを導入するのか?

ここに画像の説明を挿入します

  • 私たちのレコード型セマフォは、クリティカル セクションの 4 つの条件 ** (アイドル状態、ビジー状態、制限された待機状態、右に待機状態)** を実際に実現できますが、上記の例では、コンシューマーとプロデューサーでそれがわかっています。問題点として、P の順序が逆の場合 (相互に排他的な P 操作を同期 P 操作の後に実装する必要がある)、デッドロックの問題が発生し、PV 操作の作成が難しくなり、エラーが発生しやすくなります。
    • バッファ相互排他を実現するためのmutex
    • empty はキャッシュ領域数分の同期動作を実現します。
  • そこで、高度な同期メカニズムであるモニターの概念が提案されています。

パイプラインの構成と基本特性

構成

  • モニターにローカルな共有データ構造
  • データ構造を操作する一連のプロシージャ (プロシージャは関数モニターとして扱われ、クラス共有データ構造として扱われ、クラス内の属性として扱われます)
  • モニターローカルの共有データの初期値を設定するステートメント
  • モニターには名前がある

基本的な機能

  • モニターに対してローカルなデータには、モニターに対してローカルなプロセスによってのみアクセスできます (カプセル化のアイデア)
  • プロセスは、モニター内のプロシージャを呼び出すことによってのみモニターに入り、共有データにアクセスできます。
  • 每次仅允许一个进程在管程内执行某个内部过程(实现了互斥访问)

パイププロセスは生産者と消費者の問題を実装します

ここに画像の説明を挿入します

  • リソースに対する相互排他操作は、チューブ内での各プロセスの相互排他を実現する役割を担うコンパイラーによって実装されます。
  • ただし、プロデューサー モデルとコンシューマー モデルは依然として同期関係を実装する必要があります。同期を達成するには wait と signal を使用する必要があります。full と empty には対応するブロッキング キューがあります。

Javaのモニターに似た仕組み

1

public class startTest {
    
    
    public static void main(String[] args) {
    
    
        Object o=new Object();
        new Thread(()->{
    
    
            synchronized (o){
    
    

            }
        },"t2").start();
    }
}
  • JVM での同期は基于进入和退出监视器对象(Monitor,管程对象)来实现的,每个对象实例都会有一个Monitor对象
  • Monitor オブジェクトは Java オブジェクトと一緒に作成および破棄され、その基礎となる実装は C++ 言語によって実装されます。
  • 実行スレッドは、メソッドを実行する前に、まずモニターを正常に保持する必要があります。最後に、メソッドが完了すると、モニターは解放されます。メソッドの実行中、スレッドはモニターを所有し、他のスレッドは同じモニターを取得できなくなりますモニター。

非同期と同期

発信者の観点から見ると、

  • 実行を続行するには、結果が返されるまで待つ必要があります。これが同期です。
  • 結果が返されるのを待つ必要はなく、非同期で実行を続けることができます。

デザイン

  • マルチスレッドでは、メソッドの実行を非同期にすることができます (つまり、待機しません)。たとえば、ディスク ファイルを読み取るとき、読み取り操作に 5 秒かかると仮定します。スレッド スケジューリング メカニズムがない場合、CPU は実行できません。この 5 秒以内に何かをしてください。他のコードは一時停止する必要があります...

結論は

  • たとえば、プロジェクトでは、ビデオ ファイルをフォーマットに変換するなど、時間がかかる操作が必要ですが、このとき、メイン スレッドのブロックを避けるために、ビデオ変換を処理する新しいスレッドが開かれます。
  • Tomcat の非同期サーブレットにも同様の目的があり、ユーザー スレッドが長時間実行操作を処理できるようにし、Tomcat の作業スレッドのブロックを回避します。
  • ui プログラムで、ui スレッドのブロックを避けるために、スレッドを開いて他の操作を実行します。

マルチスレッドの意味

什么叫多线程

  • マルチスレッド: マルチスレッドとは、プログラムに複数の実行ストリームが含まれること、つまり、プログラム内で複数の異なるスレッドを同時に実行して、異なるタスクを実行できることを意味します。

マルチコアCPUのメリットを最大限に活かして、作業効率を向上させます。次のシナリオを想像し、3 つの計算を実行し、最後に結果をまとめます。

计算 1 花费 10 ms
计算 2 花费 11 ms
计算 3 花费 9 ms
汇总需要 1 ms
  • シリアルに実行した場合、合計所要時間は 10 + 11 + 9 + 1 = 31ms となります。
  • ただし、4 コア CPU の場合、各コアはスレッド 1 を使用して計算 1 を実行し、スレッド 2 で計算 2 を実行し、スレッド 3 で計算 3 を実行し、次に計算 3 を実行します。
  • スレッドは並列であり、かかる時間は最も長いスレッドの実行時間 (11 ミリ秒) のみに依存し、最後に要約時間を追加すると、所要時間はわずか 12 ミリ秒になります。

効率を向上させるにはマルチコア CPU が必要であることに注意してください。シングル コアは引き続き順番に実行されます。

package Lambda;
 
public class ThreadNB {
    private static final long count=10_0000_0000;
    public static void main(String[] args) throws InterruptedException {
        serial();
        concurrent();
    }
    public static void serial(){
        long start=System.nanoTime();
        long a=0;
        for (int i = 0; i < count; i++) {
            a++;
        }
        long b=0;
        for (int i = 0; i < count; i++) {
            b++;
        }
        long end=System.nanoTime();
        double allTime=(end-start)*1.0/1000/1000;
        System.out.println("串行执行所用的时间"+allTime+"ms");
    }
    public static void concurrent() throws InterruptedException {
        //并行实现20亿的累加
        long start=System.nanoTime();
        Thread thread1=new Thread(()->{
            long a=0;
            for (int i = 0; i < count; i++) {
                a++;
            }
        });
        thread1.start();//子线程进行十亿次累加
        //主线程也进行10亿次累加
        long b=0;
        for (int i = 0; i < count; i++) {
            b++;
        }
        // 等待子线程执行结束,主线程和子线程的加法操作都完成
        // 等待子线程thread执行结束才能执行下面代码
        thread1.join();//限制子线程执行完毕,才能运行下面的代码
        long end=System.nanoTime();
        double allTime=(end-start)*1.0/1000/1000;
        System.out.println("并行耗费的时间为"+allTime+"ms");
    }
}
//串行执行所用的时间955.9495ms
//并行耗费的时间为747.6655ms
  • 理論的には、この場合の同時実行の速度は順次実行の 2 倍になるはずです。
  • マルチスレッドの最大の応用シナリオは、大きなタスクを複数のサブタスク (サブスレッドに引き渡す) に分割することです。複数のサブスレッドを同時に実行して、システムの処理効率を向上させることができます。たとえば、次のようになります。 12306 システムはマルチスレッド プログラムです。実際、私たち一人ひとりがスレッドです。複数の人がシステムにログインして同時にチケットを購入できます。支払い操作は非常に時間のかかる操作です。マルチスレッドではなかった場合、全員が順番にチケットを購入するために列に並ばなければなりませんが、これは非常に遅く、マルチスレッドです (たとえば、他の人のチケットを処理するために支払いページを調整する時間を利用できます)購入操作。「IO の待機」を必要とする一部のタスク シナリオと同様です。IO の待機時間を他の作業に使用できるようにするには、同時プログラミングも使用する必要があります。)

マルチスレッド (同時プログラミング) を使用する理由

  • マルチコア CPU の使用率を向上させる: 一般的に、ホスト上には複数の CPU コアが存在します。複数のスレッドを作成できます。理論的には、オペレーティング システムは、実行のために複数のスレッドを異なる CPU に割り当てることができます。各 CPU 1 つのスレッドを実行すると、CPU が向上します。シングルスレッドを使用する場合、使用できる CPU コアは 1 つだけです。

  • たとえば、オンライン ショッピングでは、応答速度を向上させるために、在庫の削減、注文の生成などを分割する必要があります。これらの操作は、マルチスレッド技術を使用して分割して完了できます。複雑なビジネス モデルに直面すると、並列プログラムはシリアル プログラムよりもビジネス ニーズに適応しやすく、このビジネス分割には並行プログラミングの方が適しています。

  • 簡単に言うと次のとおりです。

    • マルチコア CPU のコンピューティング能力を最大限に活用します。

    • 事業分割を促進し、アプリケーションのパフォーマンスを向上させる

同時プログラミングの欠点は何ですか

  • 並行プログラミングの目的は、プログラムの実行効率を向上させ、プログラムの実行速度を上げることです。ただし、並行プログラミングによって常にプログラムの実行速度が向上するとは限らず、並行プログラミングでは次のような多くの問題が発生する可能性があります内存泄漏上下文切换、、、线程安全など死锁_

結論は

  • シングルコア CPU では、マルチスレッドによって実際にプログラムの実行効率が向上するわけではありません。これは、異なるタスク間を切り替えることができるだけです。異なるスレッドが順番に CPU を使用するため、1 つのスレッドが常に CPU を占有するわけではありません。そして他のスレッドは動作できません。

  • マルチコアCPUは複数のスレッドを並列実行できますが、プログラムの実行効率が向上するかどうかは状況によって異なりますが、慎重に設計した上でタスクを分割して並列実行することで、プログラムの実行効率は確実に向上します。しかし、すべてのコンピューティングタスクを分割できるわけではありませんし(後述の[アムダールの法則]を参照)、すべてのタスクを分割する必要があるわけではありません。タスクの目的が異なる場合、分割や効率化について語るのは無意味です。

  • IO処理はCPUを占有しませんが、一般的にファイルのコピーには[ブロッキングIO]を使用します、このときCPUを使用していないスレッドと同等ですが、IOの終了を待つ必要があり、スレッドは実行できません。十分に活用されること。そのため、後続の [ノンブロッキング IO] と [非同期 IO] の最適化が行われます。

プロセススレッドを表示する方法

ウィンドウズ

  • タスク マネージャーはプロセスとスレッドの数を表示でき、プロセスを強制終了するためにも使用できます。
  • タスクリスト表示プロセス
  • taskkill はプロセスを強制終了します

リナックス

  • ps -fe すべてのプロセスを表示します
    • ps -fT -p <PID> プロセス (PID) のすべてのスレッドを表示する
  • キルキルプロセス
  • top 大文字のHを押してスレッドを表示するかどうかを切り替えます
    • top -H -p <PID> プロセス (PID) のすべてのスレッドを表示する

ジャワ

  • すべての Java プロセスを表示する jps コマンド
  • jstack <PID> Java プロセス (PID) のすべてのスレッド ステータスを表示する
  • jconsole: Java プロセス内のスレッドの実行ステータスを表示します (グラフィカル インターフェイス)

スレッドコンテキストの切り替え

次の理由により、CPU は現在のスレッドを実行せず、代わりに別のスレッドのコードを実行します。

  • スレッドの CPU タイム スライスが使い果たされると、JVM は一時的に CPU 操作を放棄します (タイム スライス ラウンドロビン スケジューリングに基づく JVM オペレーティング システムでは、スレッドが永久に CPU を放棄したり、実行権を放棄したりすることはありません)このタイムスライスの)
  • 現在実行中のスレッドが、I/O のブロックなどの何らかの理由でブロック状態になります。
  • ガベージコレクション
  • 実行する必要がある優先度の高いスレッドがあります
  • 現在実行中のスレッドが終了します。つまり、run() メソッド内のタスクが完了します。
  • スレッド自体は、sleep、yield、wait、join、park、synchronized、lock などのメソッドを呼び出します。

コンテキストとは、ある時点での CPU レジスタとプログラム カウンターの内容、つまりスレッド (プロセス) が実行される環境を指します。コンテキスト スイッチが発生すると、オペレーティング システムは現在のスレッドの状態を保存する必要があります。別のスレッドの状態を復元します。Java の対応する概念

  • これはプログラム カウンター レジスタであり、その機能は次の JVM 命令の実行アドレスを記憶することであり、スレッドに対してプライベートです。
  • ステータスには、プログラム カウンターと、ローカル変数、オペランド スタック、リターン アドレスなど、仮想マシン スタック内の各スタック フレームに関する情報が含まれます。

ねじA~Bの例

  • 1. まずスレッド A を一時停止し、その状態を CPU のメモリに保存します。
  • 2. メモリ内の次のスレッド B のコンテキストを取得し、それを CPU のレジスタに復元してスレッド B を実行します。
  • 3. B の実行が完了すると、プログラム カウンタが指す位置に従ってスレッド A を再開します。

コンテキストスイッチが頻繁に発生するとパフォーマンスに影響を与える

ユーザースレッドとデーモンスレッド

Java スレッドはユーザー スレッドとデーモン スレッドに分けられます

  • 通常の状況では、特別な構成は必要なく、デフォルトはユーザー スレッドです。

  • ユーザー (ユーザー) スレッド: フォアグラウンドで実行され、特定のタスクを実行します。たとえば、プログラムのメイン スレッドとネットワークに接続されているサブスレッドはすべてユーザー スレッドです。

  • デーモン スレッド: バックグラウンドで実行され、他のフォアグラウンド スレッドに対応しますデーモン スレッドは、JVM における非デーモン スレッドの「召使い」であるとも言えます。

    • GC ガベージ コレクション スレッドは古典的なデーモン スレッドです
    • すべてのユーザー スレッドの実行が完了すると、デーモン スレッドは JVM とともに作業を終了します。
  • スレッドのデーモン属性は

    • true はデーモン スレッドであることを意味します
    • false は、ユーザー スレッドであることを意味します。
public class DaemonDemo {
    
    
    public static void main(String[] args) {
    
    
        Thread t1 = new Thread(() -> {
    
    
            System.out.println(Thread.currentThread().getName()+"\t 开始运行,"
                    +(Thread.currentThread().isDaemon() ? "守护线程":"用户线程"));
            while (true){
    
    

            }
        },"t1");
        //线程的daemon属性为true表示是守护线程,false表示是用户线程
        //---------------------------------------------
        t1.setDaemon(true);
        t1.start();
        //3秒后主线程再运行
        try {
    
    
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
        System.out.println("----------main线程运行完毕");
    }
}

2 つの状況

  1. 追加されませんt1.setDaemon(true);。デフォルトはユーザースレッドです。t1 は実行し続けるため、ライトが点灯します。
  2. デーモンスレッドが追加されt1.setDaemon(true);、ユーザースレッドのメインメソッドが終了すると、t1は自動的に終了します。

サービス スレッドとして、デーモン スレッドはサービス オブジェクトなしで実行を続ける必要はありません。すべてのユーザー スレッドが終了すると、プログラムが完了する必要があるビジネス操作が終了したことになり、システムは終了できます。システムにデーモン スレッドのみが残っている場合、Java 仮想マシンは自動的に終了します。

setDaemon(true) メソッドは start() の前に設定する必要があります。そうしないと、IIIegalThreadStateException 例外が報告されます。

JAVAスレッド

Java メイン クラス名は
、このクラスのメイン スレッドである JAVA プロセス クラスの main メソッドを開始します。スレッドはオペレーティング システムの概念です。オペレーティング システムのカーネルは、スレッドなどのメカニズムを実装し、いくつかの API をユーザーに提供しますユーザーが使用するレイヤー (Linux の pthread ライブラリなど) Java 標準ライブラリの Thread クラスは、オペレーティング システムが提供する API をさらに抽象化し、カプセル化したものとみなすことができます。

画像

Javaスレッドの作成方法

  • Thread クラスを継承し、run メソッドをオーバーライドします。
  • Runnable インターフェイスをオーバーライドし、run メソッドを上書きします。
  • Callable インターフェースをオーバーライドし、call メソッドをオーバーライドします。
  • スレッドプールを使用してスレッドを作成する

Threadクラスを継承する

  • サブクラスは Thread クラスを継承します
  • 実行メソッドをオーバーライドする
class MyThread extends Thread{
    
    
    @Override
    public void run() {
    
    
        System.out.println("线程运行");
    }
}
public class ThreadTest {
    
    
    public static void main(String[] args) {
    
    
        MyThread myThread = new MyThread();
        myThread.start();
    }
}
// 匿名内部类写法 创建线程对象
Thread t = new Thread() {
    
    
	public void run() {
    
    
		// 要执行的任务
	}
};
// 构造方法的参数是给线程指定名字,推荐
Thread t1 = new Thread("t1") {
    
    
	@Override
	// run 方法内实现了要执行的任务
	public void run() {
    
    
		// 要执行的任务
	}
};
// 启动线程
t.start();

Thread で Runnable インターフェイスを使用する

[スレッド]と[タスク](実行するコード)を分ける

  • スレッドはスレッドを表します
  • Runnable 実行可能タスク (スレッドによって実行されるコード)
Runnable runnable = new Runnable() {
    
    
	public void run(){
    
    
		// 要执行的任务
	}
};
// 创建线程对象
Thread t = new Thread( runnable );
// 启动线程
t.start();
// 创建任务对象
Runnable task2 = new Runnable() {
    
    
	@Override
	public void run() {
    
    
		log.debug("hello");
	}
};
// 参数1 是任务对象; 参数2 是线程名字,推荐
Thread t2 = new Thread(task2, "t2");
t2.start();
//匿名内部类写法
 Thread thread = new Thread(new Runnable() {
    
    
    @Override
    public void run() {
    
    
        System.out.println("线程运行");
     }
}, "t1");
//Lambda写法
Thread thread = new Thread(() -> System.out.println("线程运行"), "t1");
thread.start();
  • Lambda は関数型インターフェイスの実装にのみ使用でき、抽象メソッド インターフェイスは 1 つだけです。詳細については、http://t.csdn.cn/gI076 を参照してください。
  • Runnable を使用すると、スレッド プールなどの高度な API との連携が容易になります。
  • Runnable を使用してタスク クラスをスレッド継承システムから分離し、より柔軟にします。
    • 合成は継承より優先されます

FutureTask 配合 Thread

  • まず Callable インターフェースを実装します
  • コアメソッド呼び出しメソッドをオーバーライドする
  • Callable の戻り値を受け取るために、対応する FutureTask クラスを作成します。
  • FutureTask オブジェクトを Thread クラス オブジェクトに渡します。
FutureTask<Integer> FutureTask = new FutureTask<>(new Callable<Integer>() {
    
    
     @Override
     public Integer call() throws Exception {
    
    
         int sum = 0;
         for (int i = 0; i < 100; i++) {
    
    
            sum += i;
         }
        return sum;
     }
});
Thread thread1 = new Thread(FutureTask);
thread1.start();
System.out.println(FutureTask.get());
  • Callable インターフェイスを実装するスレッドには戻り値があり、戻り値は FutureTask クラスのオブジェクトを使用して受け取る必要があります。

  • Callable と Runnable は相対的なもので、どちらも「タスク」を表します。Callable は戻り値のあるタスクを表し、Runnable は戻り値のないタスクを表します。

  • Callable は通常、FutureTask と併用する必要があります。FutureTask は Callable の戻り結果を保存するために使用されます。Callable は別のスレッドで実行されることが多いため、いつ実行されるかは不確かです。

実行可能と呼び出し可能の違いは何ですか
? 類似点:

  • どちらのインターフェイスもマルチスレッド プログラムの作成に使用でき、いずれも Thread.start() を使用してスレッドを開始します。

主な違い:

  • Runnable インターフェイスの run メソッドには戻り値がありません (この Runnable インターフェイスのオブジェクトを受け取るには、Thread オブジェクトを直接使用します)。

  • Callable インターフェイスの call メソッドは戻り値を持ち、ジェネリック型です。非同期実行の結果を取得するには、FutureTask オブジェクトとともに使用する必要があります (FutureTask オブジェクトを使用して Callable インターフェイスを受け取り、次に Thread オブジェクトを使用します) FutureTask オブジェクトを受信します)。

  • Runnable インターフェイスの run メソッドは、実行時例外をスローすることのみが可能であり、キャッチして処理することはできません。Callable インターフェイスの call メソッドでは、例外をスローすることができ、例外情報を取得できます。 注: Callalbe インターフェイスは、実行結果を返すことをサポートしています。これは、FutureTask.get() を呼び出して取得する必要があります。メイン プロセスの実行継続がブロックされます。呼び出されない場合、ブロックされません。

フューチャータスクとは

FutureTask は、非同期操作タスクを表します。FutureTask は、Callable の特定の実装クラスのオブジェクトを渡すことができ、この非同期操作タスクの結果を待ったり、完了したかどうかを判断したり、タスクをキャンセルしたりするなどの操作を行うことができます。操作が完了した場合のみ結果を取得できます。操作が完了していない場合、get メソッドはブロックされます。

  • 詳細は後ほど説明します

スレッドクラスのコンストラクターメソッドと静的ブロックを呼び出しているスレッドはどれですか?

  • 覚えておいてください。スレッド クラスの構築メソッドと静的ブロックは、新しいスレッド クラスが配置されているスレッドによって呼び出され、run メソッドのコードはスレッド自体によって呼び出されます。
  • 上記のステートメントが混乱している場合は、例を示します。Thread2 は main 関数の新しい関数です。
    • (1) Thread2 の構築メソッドと静的ブロックはメインスレッドによって呼び出され、Thread2 の run() メソッドは Thread2 自身によって呼び出されます。

スレッドプールを使用する

public class ThreadDemo1 {
    
    
    public static void main(String[] args) {
    
    
        ExecutorService executorService = Executors.newFixedThreadPool(3);
        executorService.submit(new Runnable() {
    
    
            @Override
            public void run() {
    
    
                System.out.println("线程运行");
            }
        });
    }
}
  • スレッドプールの知識ポイントについては、後で詳しく説明します。

Java 中用到的线程调度算法(Java はプリエンプションを実装し、Java は JVM 内のスレッド カウンターを使用してスレッド スケジューリングを実装します)

タイムシェアリング スケジューリング モデルとプリエンプティブ スケジューリング モデル。

  • タイムシェアリング スケジューリング モデルは、すべてのスレッドが順番に CPU の使用権を取得し、各スレッドが占有する CPU のタイム スライスが均等に分散されることを意味し、これも理解しやすいです。
  • Java で使用されるスレッド スケジューリングは、プリエンプティブ スケジューリングを使用します。Java のスレッドは、優先度に応じて実行する CPU タイム スライスを割り当てます。優先度が高いほど、優先度も高くなります。ただし、優先度が高いからといって、実行タイム スライスを占有できるわけではありません優先順位が高いほど、より多くの実行時間スライスが取得されます。逆に、優先順位が低いほど、割り当てられる実行時間は少なくなりますが、実行時間は不足しません。

実行と開始の違い

start() は新しいスレッドを開始し、在新的线程run メソッドでコードを実行します。

  • start メソッドはスレッドを準備するだけであり、内部のコードはすぐには実行されない可能性があります (CPU タイム スライスがまだ割り当てられていません)。各スレッドオブジェクトのstartメソッドは1回のみ呼び出すことができ、複数回呼び出すとIllegalThreadStateExceptionが発生します。
  • new スレッド。スレッドは新しい状態に入ります。start()メソッドを呼び出すとスレッドが起動して準備完了状態となり、CPUリソースが割り当てられると実行を開始できます(実行状態)。start() は対応するスレッドの準備作業を実行し、その後 run() メソッドの内容を自動的に実行することで、真のマルチスレッド操作を実現します。start() メソッドを呼び出すときに run メソッド本体のコードが実行されるのを待つ必要はなく、他のコードの実行を直接続行できます。
public class ThreadDemo2 {
    
    
    public static void main(String[] args) {
    
    
        new Thread(()->{
    
    
            System.out.println(Thread.currentThread().getName()+"开始运行");
        },"t1").start();
    }
}
//t1开始运行
public class ThreadDemo2 {
    
    
    public static void main(String[] args) {
    
    
        Thread thread = new Thread(() -> {
    
    
            System.out.println(Thread.currentThread().getName() + "开始运行");
        }, "t1");
        thread.start();
        thread.start();
    }
}
t1开始运行
Exception in thread "main" java.lang.IllegalThreadStateException
	at java.lang.Thread.start(Thread.java:708)
	at com.lsc.day10.ThreadDemo2.main(ThreadDemo2.java:17)

新しいスレッドの開始後に呼び出される run() メソッド

  • Thread オブジェクトの構築時に Runnable パラメータが渡された場合、スレッドの開始後に Runnable の run メソッドが呼び出されます。それ以外の場合、デフォルトでは操作は実行されません。ただし、Thread のサブクラス オブジェクトを作成して、デフォルトの動作をオーバーライドすることができます。
  • run() メソッドはこのスレッド内にありますが、これはスレッド内の単なる関数であり、マルチスレッドではありません。run() を直接呼び出すと、実際には通常の関数を呼び出すのと同じになります。run() メソッドを直接使用する場合は、次のコードを実行する前に run() メソッドが完了するまで待つ必要があるため、まだ実行パスは 1 つであり、マルチスレッドはまったくありません。
public class ThreadDemo2 {
    
    
    public static void main(String[] args) {
    
    
        Thread thread = new Thread(() -> {
    
    
            System.out.println(Thread.currentThread().getName() + "开始运行");
        }, "t1");
        thread.run();
    }
}
//main开始运行

おすすめ

転載: blog.csdn.net/qq_50985215/article/details/131178847