2023 年同時並行ステレオタイプ エッセイ - 面接の質問

基本知識

並行プログラミングの長所と短所 並行プログラミングを使用する理由 (並行プログラミングの利点)

  • マルチコアCPUの計算能力を最大限に活用: 同時プログラミングにより、マルチコアCPUの計算能力を最大化し、パフォーマンスを向上させることができます。
  • ビジネス分割を促進し、システムの同時実行性とパフォーマンスを向上させる: 特殊なビジネス シナリオでは、本質的に同時プログラミングに適しています。現在のシステムでは、あらゆる時点で数百万、さらには数千万の同時実行が必要であり、マルチスレッド同時プログラミングは、高同時実行システムを開発するための基礎です。マルチスレッド メカニズムをうまく利用すると、全体的な同時実行性とパフォーマンスを大幅に向上させることができます。システムの。複雑なビジネス モデルに直面すると、シリアル プログラムよりも並列プログラムの方がビジネス ニーズに適しており、同時プログラミングの方がこのビジネス分割に適しています。

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

並行プログラミングの目的は、プログラムの実行効率を向上させ、プログラムの実行速度を上げることですが、並行プログラミングによって必ずしもプログラムの実行速度が向上するとは限らず、並行プログラミングでは次のような多くの問題が発生する可能性があります。

: メモリ リーク、コンテキストの切り替え、スレッド セーフ、デッドロックなど。

同時プログラミングの 3 つの要素とは何ですか? Java プログラムでのマルチスレッド操作の安全性を確保するにはどうすればよいですか?

同時プログラミングの 3 つの要素 (スレッドの安全性の問題が反映されています):

原子性: 原子はそれ以上分割できない粒子です。原子性とは、1 つ以上の操作がすべて成功するかすべて失敗することを意味します。

可視性: 1 つのスレッドが共有変数を変更すると、別のスレッドはそれをすぐに見ることができます。(同期、揮発性)

順序性: プログラムが実行される順序は、コードが実行された順序で実行されます。(プロセッサは命令を並べ替えることができます)

スレッドの安全性の問題の理由:

  • スレッド切り替えによって引き起こされるアトミック性の問題
  • キャッシュによって引き起こされる可視性の問題
  • コンパイルの最適化によって引き起こされる順序の問題

解決:

  • アトミッククラス、同期化、JDK Atomic の最初の LOCK でアトミックな問題を解決できる
  • 同期、揮発性、LOCK、可視性の問題を解決できる
  • Happens-Before ルールは順序の問題を解決できる

並列と同時の違いは何ですか?

  • 同時実行性: 複数のタスクが同じ CPU コア上で細分化されたタイム スライスに従って順番に (交互に) 実行され、論理的な観点からは、これらのタスクは同時に実行されます。
  • 並列処理: 単位時間内に、複数のプロセッサまたはマルチコア プロセッサが複数のタスクを同時に処理します。これが本当の意味で「同時」です。
  • シリアル: n 個のタスクがあり、1 つのスレッドによって順次実行されます。タスクとメソッドは 1 つのスレッドで実行されるため、スレッドのセキュリティが確保されず、クリティカル セクションの問題も発生しません。

画像のメタファーを作成するには:

同時実行 = 2 つのキューとコーヒー マシン。
並列 = 2 つのキューと 2 台のコーヒーマシン。
シリアル = 1 つのキューと 1 つのコーヒーマシン。

マルチスレッドとは何ですか?マルチスレッドのメリットとデメリットは何ですか?

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

マルチスレッドの利点: CPU 使用率を向上させることができます。マルチスレッド プログラムでは、スレッドが待機する必要がある場合、CPU は待機する代わりに他のスレッドを実行できるため、プログラムの効率が大幅に向上します。つまり、1 つのプログラムで複数の並列実行スレッドを作成し、それぞれのタスクを完了することができます。

マルチスレッドの欠点:

  • スレッドもプログラムであるため、スレッドはメモリを占有する必要があり、スレッドが増えると占有するメモリも増えます。
  • マルチスレッドには調整と管理が必要なため、スレッドを追跡するために CPU 時間が必要になります。
  • スレッド間の共有リソースへのアクセスは相互に影響を与えるため、共有リソースの競合の問題を解決する必要があります。

スレッドとプロセスの違いは何ですか?

**プロセス**

メモリ内で実行されるアプリケーション。各プロセスには独自の独立したメモリ空間があり、プロセスは複数のスレッドを持つことができます。たとえば、Windows システムでは、実行中の xx.exe がプロセスです。

現在のプロセス内のプログラムの実行を担当するプロセス内の実行タスク (制御ユニット)。プロセスには少なくとも 1 つのスレッドがあり、プロセスは複数のスレッドを実行でき、複数のスレッドがデータを共有できます。

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

スレッドは従来のプロセスの多くの特徴を備えているため、軽量プロセスまたはプロセス要素とも呼ばれます。従来のプロセスは重量プロセスと呼ばれ、スレッドが 1 つだけのタスクに相当します。スレッドを導入するオペレーティング システムでは、通常、プロセスには少なくとも 1 つのスレッドを含む複数のスレッドがあります。

**基本的な違い:** プロセスはオペレーティング システムのリソース割り当ての基本単位であるのに対し、スレッドはプロセッサ タスクのスケジューリングと実行の基本単位です。

**リソース オーバーヘッド:**各プロセスには独自のコードとデータ スペース (プログラム コンテキスト) があり、プログラム間の切り替えには大きなオーバーヘッドが発生します。スレッドは軽量プロセスとみなすことができ、同じタイプのスレッドはコードとデータ スペースを共有します。各スレッドには独自の独立した実行スタックとプログラム カウンター (PC) があり、スレッド間の切り替えのオーバーヘッドは小さいです。

**包含関係:** プロセス内に複数のスレッドがある場合、実行プロセスは 1 行ではなく、複数の行 (スレッド) がまとめて完了します。スレッドはプロセスの一部であるため、スレッドは軽量プロセスまたは軽量とも呼ばれます。プロセス。

**メモリ割り当て:** 同じプロセスのスレッドはプロセスのアドレス空間とリソースを共有しますが、プロセス間のアドレス空間とリソースは互いに独立しています。

**影響関係:** プロセスがクラッシュしても、保護モードの他のプロセスには影響しませんが、スレッドがクラッシュするとプロセス全体が停止します。したがって、マルチプロセッシングはマルチスレッドよりも堅牢です。

**実行プロセス:**それぞれの独立したプロセスには、プログラム実行エントリ、順次実行シーケンス、およびプログラム終了があります。ただし、スレッドは独立して実行することはできず、アプリケーションプログラムに依存する必要があり、アプリケーションプログラムは複数のスレッドの実行制御を提供しており、両方を同時に実行することができます。

コンテキストスイッチングとは何ですか?

マルチスレッド プログラミングでは、一般にスレッドの数が CPU コアの数よりも多く、1 つの CPU コアは常に 1 つのスレッドによってのみ使用されます。これらのスレッドを効率的に実行できるようにするために、 CPUは各スレッドにタイムスライスを割り当ててローテーションする形式を提供します。スレッドのタイム スライスが使い果たされると、他のスレッドが再度使用できるようになります。このプロセスはコンテキスト スイッチです。

簡単に言うと、現在のタスクは、CPU タイム スライスの実行後に別のタスクに切り替える前にその状態を保存します。これにより、次回このタスクに切り替えるときに、このタスクの状態を再度読み込むことができます。タスク
の保存から再ロードまでのプロセスはコンテキスト スイッチです。

コンテキストの切り替えは多くの場合、大量の計算を要します。つまり、かなりのプロセッサ時間を必要とし、1 秒あたり数十、数百回のスイッチング回数のうち、各スイッチングにはナノ秒オーダーの時間がかかります。したがって、コンテキストの切り替えはシステムで多くの CPU 時間を消費することを意味し、実際、これはオペレーティング システムで最も時間のかかる操作である可能性があります。

Linux が他のオペレーティング システム (他の Unix 系システムを含む) に比べて持つ多くの利点の 1 つは、コンテキストの切り替えとモードの切り替えに非常に時間がかかることです。

デーモンスレッドとユーザースレッドの違いは何ですか?

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

  • ユーザー (ユーザー) スレッド: フォアグラウンドで実行され、プログラムのメインスレッド、ネットワークに接続されたサブスレッドなどの特定のタスクを実行するスレッドはすべてユーザー スレッドです。
  • デーモン スレッド: バックグラウンドで実行され、他のフォアグラウンド スレッドに対応します。デーモン スレッドは、JVM における非デーモン スレッドの「従者」であるとも言えます。すべてのユーザー スレッドの実行が完了すると、デーモン スレッドは JVM との連携を終了します。

main 関数が配置されているスレッドはユーザー スレッドであり、main 関数が開始されると、ガベージ コレクション スレッドなどの多くのデーモン スレッドも JVM 内で開始されます。より明らかな違いの 1 つは、ユーザー スレッドが終了すると、その時点でデーモン スレッドが実行されているかどうかに関係なく、JVM が終了することです。デーモン スレッドは JVM の終了には影響しません。

予防:

  1. setDaemon(true) は start() メソッドの前に実行する必要があります。そうしないと、IllegalThreadStateException がスローされます。
  2. デーモン スレッド内で生成された新しいスレッドもデーモン スレッドです
  3. 読み取りおよび書き込み操作や計算ロジックなど、すべてのタスクをデーモン スレッドに割り当てて実行できるわけではありません。
  4. デーモン スレッドでは、リソースを閉じたりクリーンアップするロジックが確実に実行されるようにするために、finally ブロックの内容に依存することはできません。すべてのユーザー スレッドの実行が終了すると、デーモン スレッドは JVM との連携を終了することも上で述べたので、デーモン スレッドのfinally ステートメント ブロックは実行されない可能性があります。

Windows と Linux で CPU 使用率が最も高いスレッドを見つけるにはどうすればよいですか?

Windows ではタスク マネージャーを使用して表示し、Linux では上部のツールを使用して表示します。

  1. 最も多くの CPU を消費するプロセスの pid を見つけて、ターミナルで top コマンドを実行し、shift+p を押して最も多くの CPU を消費するプロセスの pid 番号を見つけます。
  2. 上記の最初のステップで取得した pid 番号に従って、top -H -p pid となります。次に、shift+p を押して、CPU 使用率が高いスレッド番号を見つけます (top -H -p 1328 など)。
  3. 取得したスレッド番号を 16 進数に変換し、Baidu にアクセスして変換するだけです
  4. jstack ツールを使用してプロセス情報を出力します。jstack pid 番号 > /tmp/t.dat (例: jstack 31365 > /tmp/t.dat)
  5. /tmp/t.dat ファイルを編集して、スレッド番号に対応する情報を見つけます。

スレッドデッドロックとは何ですか

百度百科: デッドロックとは、複数のプロセス (スレッド) が実行中に、リソースの競合や相互の通信により発生するブロック現象を指し、外部からの力がなければ先に進むことができなくなります。このとき、システムがデッドロック状態にあること、あるいはシステムがデッドロックに陥っていることを言い、このように常に待ち続けているプロセス(スレッド)をデッドロックプロセス(スレッド)と呼びます。

複数のスレッドが同時にブロックされ、そのうちの 1 つまたはすべてがリソースが解放されるのを待っています。スレッドは無期限にブロックされるため、プログラムを正常に終了することはできません。

次の図に示すように、スレッド A はリソース 2 を保持し、スレッド B はリソース 1 を保持します。両方とも互いのリソースを同時に適用したいため、2 つのスレッドはお互いを待機し、デッドロック状態になります。

[外部リンク画像の転送に失敗しました。ソース サイトには盗難防止リンク メカニズムがある可能性があります。画像を保存して直接アップロードすることをお勧めします (img-gWfnTdZi-1692509038138) (04-同時プログラミング面接の質問 (2020 最新版)-) focus.assets/スレッドデッドロック.png)]

スレッド デッドロック 以下は、スレッド デッドロックを説明するための例です。コードは、上の図のデッドロック状況をシミュレートします。

1 public class DeadLockDemo { 
2  private static Object resource1 = new Object();//资源 1 
3  private static Object resource2 = new Object();//资源 2
4 
5  public static void main(String[] args) {
6  new Thread(() ‐> { 
7  synchronized (resource1) { 
8  System.out.println(Thread.currentThread() + "get resource1"); 
9  try { 
10  Thread.sleep(1000); 
11  } catch (InterruptedException e) { 
12  e.printStackTrace(); 
13  } 
14  System.out.println(Thread.currentThread() + "waiting get resource2"); 
15  synchronized (resource2) { 
16  System.out.println(Thread.currentThread() + "get resource2"); 
17  } 
18  } 
19  }, "线程 1").start();
20 
21  new Thread(() ‐> { 
22  synchronized (resource2) { 
23  System.out.println(Thread.currentThread() + "get resource2"); 
24  try { 
25  Thread.sleep(1000); 
26  } catch (InterruptedException e) { 
27  e.printStackTrace(); 
28  } 
29  System.out.println(Thread.currentThread() + "waiting get resource1"); 
30  synchronized (resource1) { 
31  System.out.println(Thread.currentThread() + "get resource1"); 
32  } 
33  } 
34  }, "线程 2").start(); 
35  } 
36 } 

出力結果

1 Thread[线程 1,5,main]get resource1 
2 Thread[线程 2,5,main]get resource2 
3 Thread[线程 1,5,main]waiting get resource2 
4 Thread[线程 2,5,main]waiting get resource1 

スレッド A は、同期 (resource1) を通じてリソース 1 のモニター ロックを取得し、次に Thread.sleep(1000) を渡します。スレッド B に CPU 実行権を取得させるために、スレッド A を 1 秒間スリープさせてから、リソース 2 のモニター ロックを取得します。 。スレッド A とスレッド B のスリープが終了すると、両方とも相手のリソースを要求し始めるため、2 つのスレッドは互いに待ち状態になり、デッドロックが発生します。上記の例は、デッドロックが発生するために必要な 4 つの条件を満たしています。

デッドロックが形成されるために必要な4つの条件は何ですか?

  1. 相互排他条件: スレッド (プロセス) は、割り当てられたリソースに対して排他的です。つまり、リソースは、スレッド (プロセス) によって解放されるまで、1 つのスレッド (プロセス) によってのみ占有されます。
  2. 要求と保持条件:占有リソースの要求によりスレッド(プロセス)がブロックされた場合、獲得したリソースは手放しません。
  3. 非剥奪条件: スレッド(プロセス)が獲得したリソースは、使い果たされる前に他のスレッドに強制的に剥奪されることができず、リソースが使い果たされて初めて解放されます。
  4. 循環待機条件: デッドロックが発生すると、待機中のスレッド (プロセス) がループ (無限ループに似た) を形成する必要があり、その結果永続的なブロックが発生します。

スレッドのデッドロックを回避する方法

デッドロックを引き起こす 4 つの条件のうち 1 つを破棄するだけで済みます。

ミューテックス条件を破る

ロックを使用して相互排他的になるため、この状態を破壊する方法はありません (重要なリソースには相互排他的アクセスが必要です)。

破棄要求と保留条件

すべてのリソースを一度に申請します。

**非剥奪条件を破る**

一部のリソースを占有しているスレッドがさらに他のリソースを申請するときに、アプリケーションを取得できない場合は、スレッドが占有しているリソースを積極的に解放できます。

**ブレークループ待機条件**

リソースを順番に申請することで防止します。特定の順序でリソースを申請し、逆の順序でリソースを解放します。ループ待ち状態を解除します。

デッドロックが発生しないように、スレッド 2 のコードを次のように変更します。

1 new Thread(() ‐> { 
2  synchronized (resource1) { 
3  System.out.println(Thread.currentThread() + "get resource1"); 
4  try { 
5  Thread.sleep(1000); 
6  } catch (InterruptedException e) { 
7  e.printStackTrace();
8  } 
9  System.out.println(Thread.currentThread() + "waiting get resource2"); 
10  synchronized (resource2) { 
11  System.out.println(Thread.currentThread() + "get resource2"); 
12  } 
13  } 
14 }, "线程 2").start(); 

出力結果

1 Thread[线程 1,5,main]get resource1 
2 Thread[线程 1,5,main]waiting get resource2 
3 Thread[线程 1,5,main]get resource2 
4 Thread[线程 2,5,main]get resource1 
5 Thread[线程 2,5,main]waiting get resource2 
6 Thread[线程 2,5,main]get resource2 

上記のコードがデッドロックを回避できる理由を分析してみましょう。

スレッド 1 が最初にリソース 1 のモニター ロックを取得しますが、この時点ではスレッド 2 はそれを取得できません。次に、スレッド 1 がリソース 2 のモニター ロックを取得し始めます。これは取得可能です。次に、スレッド 1 がリソース 1 とリソース 2 のモニター ロックの占有を解放し、スレッド 2 がそれらを取得した後に実行できるようになります。これによりブレーク ループの待機状態が解除され、デッドロックが回避されます。

スレッドを作成する 4 つの方法

スレッドを作成するにはどのような方法がありますか?

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

  • Thread クラスを継承します。
  • Runnable インターフェイスを実装します。
  • Callable インターフェイスを実装します。
  • Executors ツール クラスを使用してスレッド プールを作成し、Thread クラスを継承します。

ステップ

  1. Thread クラスのサブクラスを定義し、run メソッドを書き直し、関連するロジックである run() メソッドを実装します。

スレッドによって実行されるビジネス ロジック メソッドです

  1. カスタム スレッド サブクラス オブジェクトを作成する

  2. サブクラス インスタンスの star() メソッドを呼び出してスレッドを開始します。

1 public class MyThread extends Thread {
2 
3	@Override
4	public void run() {
5	System.out.println(Thread.currentThread().getName() + " run()方法正在执行...");
6	}
7
8 }
1 public class TheadTest {
2
3	public static void main(String[] args) {
4	MyThread myThread = new MyThread();
5	myThread.start();
6	System.out.println(Thread.currentThread().getName() + " main()方法执行结束");
7	}
8
9 }
10

演算結果

1 main main()方法执行结束 
2 Thread‐0 run()方法正在执行... 

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

ステップ

  1. Runnable インターフェースを定義してクラス MyRunnable を実装し、run() メソッドをオーバーライドします。

  2. MyRunnable インスタンス myRunnable を作成し、myRunnable をターゲットとして Thead オブジェクトを作成します。Thread オブジェクトは実際のスレッド オブジェクトです。

  3. スレッドオブジェクトのstart()メソッドを呼び出します。

1 public class MyRunnable implements Runnable {
2
3	@Override
4	public void run() {
5	System.out.println(Thread.currentThread().getName() + " run()方法执行中...");
6	}
7
8 }
1 public class RunnableTest {
2
3	public static void main(String[] args) {
4	MyRunnable myRunnable = new MyRunnable();
5	Thread thread = new Thread(myRunnable);
6	thread.start();
7	System.out.println(Thread.currentThread().getName() + " main()方法执行完成");
8	}
9
10 }

の結果

1	main main()方法执行完成
2	Thread‐0 run()方法执行中...

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

ステップ

  1. Callable インターフェースを実装するクラス myCallable を作成します。

  2. myCallableをパラメータとしてFutureTaskオブジェクトを作成します。

  3. FutureTask をパラメータとして使用して Thread オブジェクトを作成する

  4. スレッドオブジェクトのstart()メソッドを呼び出します。

1 public class MyCallable implements Callable<Integer> {
2
3	@Override
4	public Integer call() {
5	System.out.println(Thread.currentThread().getName() + " call()方法执行中...");
6	return 1;
7	}
8
9 }
1 public class CallableTest {
2
3	public static void main(String[] args) {
4	FutureTask<Integer> futureTask = new FutureTask<Integer>(new MyCallable());
5	Thread thread = new Thread(futureTask);
6	thread.start();
7
8	try {
9	Thread.sleep(1000);
10	System.out.println("返回结果 " + futureTask.get());
11	} catch (InterruptedException e) {
12	e.printStackTrace();
13	} catch (ExecutionException e) {
14	e.printStackTrace();
15	}
16	System.out.println(Thread.currentThread().getName() + " main()方法执行完成");
17	}
18
19 }

の結果

1	Thread‐0 call()方法执行中...
2	返回结果 1
3	main main()方法执行完成

Executors ツール クラスを使用してスレッド プールを作成する

エグゼキュータは、スレッド プールを作成するための一連のファクトリ メソッドを提供し、返されるスレッド プールはすべて ExecutorService インターフェイスを実装します。主に newFixedThreadPool、newCachedThreadPool、newSingleThreadExecutor、newScheduledThreadPool があり、これら 4 つのスレッド プールについては後ほど詳しく紹介します。

1 public class MyRunnable implements Runnable {
2
3	@Override
4	public void run() {
5	System.out.println(Thread.currentThread().getName() + " run()方法执行中...");
6	}
7
8 }
1 public class SingleThreadExecutorTest {
2 
3  public static void main(String[] args) { 
4  ExecutorService executorService = Executors.newSingleThreadExecutor(); 
5  MyRunnable runnableTest = new MyRunnable(); 
6	for (int i = 0; i < 5; i++) {
7	executorService.execute(runnableTest);
8	}
9
10	System.out.println("线程任务开始执行");
11	executorService.shutdown();
12	}
13
14 }

の結果

1	线程任务开始执行
2	pool‐1‐thread‐1 is running...
3	pool‐1‐thread‐1 is running...
4	pool‐1‐thread‐1 is running...
5	pool‐1‐thread‐1 is running...
6	pool‐1‐thread‐1 is running...

スレッドの run() と start() の違いは何ですか?

各スレッドは、特定の Thread オブジェクトに対応するメソッド run() を通じてその操作を完了します。run() メソッドはスレッド本体と呼ばれます。Thread クラスの start() メソッドを呼び出してスレッドを開始します。start() メソッドはスレッドの開始に使用され、run() メソッドはスレッドのランタイム コードの実行に使用されます。run() は繰り返し呼び出すことができますが、start() は 1 回だけ呼び出すことができます。start()メソッドを使用してスレッドを起動することで、まさにマルチスレッド動作を実現します。start() メソッドの呼び出しでは、run メソッド本体のコードの実行が完了するまで待つ必要はなく、他のコードの実行を直接続行できます。この時点では、スレッドは準備完了状態にあり、実行されていません。次に、この Thread クラスを通じて run () メソッドを呼び出して実行状態を完了すると、 run () メソッドが終了し、スレッドが終了します。次に、CPU は他のスレッドをスケジュールします。

run() メソッドはこのスレッド内にあり、スレッド内の単なる関数であり、マルチスレッドではありません。run() を直接呼び出す場合、実際には通常の関数を呼び出すのと同じです。run() メソッドは、次のコードを実行する前に run() メソッドが実行されるのを待つ必要があるため、実行パスはまだ 1 つだけです。スレッド機能がまったくないため、マルチスレッド実行時には run() メソッドの代わりに start() メソッドを使用します。

start() メソッドを呼び出すと run() メソッドが実行されるのはなぜですか? run() メソッドを直接呼び出すことができないのはなぜですか?

これも非常に古典的な Java マルチスレッド面接の質問であり、面接でよく聞かれます。とても簡単ですが、答えられない人も多いのではないでしょうか?

new スレッド。スレッドは新しい状態に入ります。start() メソッドを呼び出すとスレッドが開始され、スレッドが準備完了状態になり、タイム スライスが割り当てられた後に実行を開始できます。start() は、スレッドの対応する準備作業を実行し、その後、実際のマルチスレッド作業である run() メソッドの内容を自動的に実行します。

run() メソッドを直接実行すると、run メソッドはメインスレッドで通常のメソッドとして実行され、特定のスレッドで実行されるわけではないため、これはマルチスレッド作業ではありません。

概要: start メソッドを呼び出すとスレッドを開始して準備完了状態にすることができますが、run メソッドはスレッドの通常のメソッド呼び出しにすぎず、メイン スレッドで実行され続けます。

Callable と Future とは何ですか?

名前からわかるように、Callable インターフェイスは Runnable に似ていますが、Runnable は結果を返さず、結果を返す例外をスローできませんが、Callable の方が強力です。スレッドによって実行された後、次の結果を返すことができます。この戻り値は、Future によって取得されます。つまり、Future は非同期実行されたタスクの戻り値を取得できます。

Future インターフェイスは、非同期タスク、つまりまだ完了していない可能性のある非同期タスクの結果を表します。それで

Callable は結果を生成するために使用され、Future は結果を取得するために使用されます。

フューチャータスクとは

FutureTask は、非同期で動作するタスクを表します。FutureTask を渡すことができます

この非同期操作タスクの結果の取得と判定を待機できる Callable の特定の実装クラス

完了したかどうかを確認し、タスクやその他の操作をキャンセルします。結果は操作が完了した場合にのみ取得でき、操作が完了していない場合、get メソッドはブロックされます。FutureTask オブジェクトは呼び出すことができます

Callable オブジェクトと Runnable オブジェクトがパッケージ化されており、FutureTask も Runnable インターフェイスの実装クラスであるため、FutureTask もスレッド プールに配置できます。

スレッドの状態と基本的な操作 スレッドのライフサイクルと 5 つの基本的な状態について話しますか?

[外部リンク画像の転送に失敗しました。ソース サイトにはリーチ防止メカニズムがある可能性があります。画像を保存して直接アップロードすることをお勧めします (img-H96OwJqP-1692509038139) (04-同時プログラミング面接の質問 (2020 最新版)-focus .assets/スレッドステータス.png)]

  1. 新規(new): スレッドオブジェクトが新規に作成されます。

  2. 実行可能: スレッド オブジェクトが作成された後、スレッド オブジェクトの start() メソッドが呼び出されるとき、スレッドは準備完了状態になり、CPU を使用する権利を取得するためにスレッド スケジューリングによって選択されるのを待機します。

  3. 実行中 (running): 実行可能状態 (runnable) のスレッドが CPU タイム スライスを取得しました

(タイムスライス)、プログラム コードを実行します。注: 準備完了状態は、実行状態に入る唯一のエントリです。つまり、スレッドが実行のために実行状態に入りたい場合は、まず準備完了状態でなければなりません。

  1. ブロッキング (ブロック): 実行状態のスレッドが何らかの理由で一時的に CPU の使用権を放棄し、実行を停止します。このとき、実行可能状態になるまでブロック状態に入ります。 CPU によって再度呼び出され、動作状態になります。

ブロックには 3 つのタイプがあります。

(1). ブロック待ち: 実行状態のスレッドが wait() メソッドを実行し、JVM がスレッドを待機キュー (待機キュー) に入れて、スレッドがブロック待機状態に入ります。

(2). 同期ブロッキング: スレッドが同期された同期ロックの取得に失敗した場合 (ロックが他のスレッドによって占有されているため)、JVM はスレッドをロック プール (ロック プール) に入れ、スレッドは同期ロックに入ります。ブロッキング状態。

(3). その他のブロック: スレッドの sleep() または join() を呼び出すか、I/O リクエストが発行されると、スレッドは

ブロックされた状態になります。sleep () 状態がタイムアウトになると、join () はスレッドが終了するかタイムアウトになるまで待機します。あるいは、I/O 処理が完了すると、スレッドは再び準備完了状態に移行します。

  1. Dead: スレッドの run()、main() メソッドの実行が終了するか、例外により run() メソッドが終了すると、スレッドはライフサイクルを終了します。死んだスレッドを再び復活させることはできません。

Java で使用されるスレッド スケジューリング アルゴリズムとは何ですか?

通常、コンピュータには CPU が 1 つだけあり、一度に 1 つの機械命令のみを実行できます。また、各スレッドは CPU の使用権を取得した場合にのみ命令を実行できます。マルチスレッドのいわゆる同時動作は、実際には、マクロの観点から見ると、各スレッドが順番に CPU を使用する権利を取得し、それぞれ独自のタスクを実行することを意味します。実行中のプールでは、CPU を待機している準備完了状態の複数のスレッドが存在します。JAVA 仮想マシンのタスクの 1 つは、スレッドのスケジューリングを担当します。スレッドのスケジューリングとは、CPU 使用権を複数のスレッドに割り当てることを指します。特定のメカニズム。

スケジューリング モデルには、タイムシェアリング スケジューリング モデルとプリエンプティブ スケジューリング モデルの 2 つがあります。

タイムシェアリング スケジューリング モデルは、すべてのスレッドが順番に CPU を使用する権利を取得し、各スレッドが占有する CPU のタイム スライスが均等に割り当てられることを意味します。これも比較的理解しやすいものです。

ava 仮想マシンはプリエンプティブ スケジューリング モデルを採用しています。これは、実行可能なプール内で優先度の高いスレッドが優先的に占有されることを意味します。

CPU。実行可能なプール内のスレッドの優先順位が同じ場合、スレッドがランダムに選択されて占有されます。

CPU。実行状態のスレッドは、CPU を解放する必要があるまで実行を続けます。

スレッドスケジューリングポリシー

スレッド スケジューラは、実行する優先度の高いスレッドを選択しますが、次の状況が発生した場合、スレッドは終了します。

(1) スレッド本体内で yield メソッドを呼び出し、CPU の占有権を放棄します。

(2) スレッド本体で sleep メソッドを呼び出し、スレッドをスリープ状態にします。

(3) IO 操作によりスレッドがブロックされている

(4) 別の優先度の高いスレッドが表示される

(5) タイムスライスをサポートするシステムでは、スレッドのタイムスライスが不足します。

スレッドスケジューラとタイムスライスとは何ですか?

スレッド スケジューラは、実行可能状態のスレッドに CPU 時間を割り当てる役割を担うオペレーティング システム サービスです。スレッドを作成して開始すると、その実行はスレッド スケジューラの実装に依存します。タイム スライシングは、利用可能な CPU 時間を利用可能な実行可能スレッドに割り当てるプロセスです。配布する

CPU 時間は、スレッドの優先順位またはスレッドの待機時間に基づくことができます。

スレッドのスケジューリングは Java 仮想マシンによって制御されないため、アプリケーションで制御する方が良いでしょう。

(つまり、プログラムをスレッドの優先順位に依存させないでください)。

スレッドの同期とスレッドのスケジューリングに関連するメソッドの名前を教えてください。

(1)wait():スレッドを待機(ブロッキング)状態にし、保持していたオブジェクトのロックを解放する。

(2) sleep(): 実行中のスレッドをスリープ状態にします。これは静的メソッドであり、このメソッドを呼び出して InterruptedException 例外を処理します。

(3) Notice(): 待機状態のスレッドをウェイクアップ もちろん、このメソッドを呼び出すときに、待機状態の特定のスレッドをウェイクアップすることはできませんが、どのスレッドをウェイクアップするかは JVM が決定し、それは優先順位とは何の関係もありません。

(4) notityAll(): 待機中のすべてのスレッドを起動します。このメソッドはオブジェクトのロックを与えません。

すべてのスレッドが競合しますが、ロックを取得したスレッドだけが準備完了状態に入ることができます。

sleep() と wait() の違いは何ですか?

どちらもスレッドの実行を一時停止できます

  • クラス間の違い: sleep() は Thread スレッド クラスの静的メソッドであり、wait() は Object クラスのメソッドです。
  • ロックを解放するかどうか: sleep() はロックを解放しませんが、wait() はロックを解放します。
  • さまざまな目的: 待機は通常、スレッド間の対話/通信に使用され、スリープは通常、実行を一時停止するために使用されます。
  • 使用法は異なります。wait() メソッドが呼び出された後、スレッドは自動的にウェイクアップせず、他のスレッドは同じオブジェクトに対して Notice() メソッドまたは NotifyAll() メソッドを呼び出す必要があります。sleep() メソッドが実行されると、スレッドは自動的に起動します。または、wait(long timeout) を使用して、タイムアウト後にスレッドを自動的に起動することもできます。

wait() メソッドをどのように呼び出していますか? if ブロックまたはループを使用しますか? なぜ?待機状態にあるスレッドは誤ったアラームや偽のウェイクアップを受信する可能性があり、ループ内で待機条件がチェックされない場合、プログラムは終了条件が満たされずに終了します。

wait() メソッドはループ内で呼び出す必要があります。スレッドが CPU の実行を開始するときに他の条件が満たされていない可能性があるため、処理前に条件が満たされているかどうかを確認することをお勧めします。wait メソッドとnotify メソッドを使用した標準的なコードを次に示します。

1	synchronized (monitor) {
2	// 判断条件谓词是否得到满足
3	while(!locked) {
4	// 等待唤醒
5	monitor.wait();
6	}
7	// 处理其他的业务逻辑
8	}

スレッド通信メソッド wait()、notify()、notifyAll() が Object クラスで定義されているのはなぜですか?

Java では、任意のオブジェクトをロックとして使用でき、wait()、notify()、およびその他のメソッドを使用して、オブジェクトのロックを待機したり、スレッドをウェイクアップしたりします。Java スレッド内のオブジェクトに使用できるロックはありません。したがって、オブジェクトはメソッドを呼び出します。このメソッドは Object クラスで定義されている必要があります。

wait()、notify()、およびnotifyAll()メソッドは同期コードブロックで呼び出されます。

スレッドがオブジェクトのロックを放棄するので、Thread クラスに wait() を定義することもできると言う人もいますが、新しく定義されたスレッドは Thread クラスを継承するため、wait() の実装を再定義する必要はありません。方法。しかし、これには非常に大きな問題があり、スレッドは多くのロックを保持できるのですが、ロックを放棄する場合、どのロックを放棄すべきでしょうか? もちろん、この設計は不可能ではありませんが、管理がより複雑になります。

要約すると、wait()、notify()、notifyAll() メソッドは Object クラスで定義する必要があります。

wait()、notify()、notifyAll() を同期メソッドまたはブロック内で呼び出す必要があるのはなぜですか?

スレッドがオブジェクトの wait() メソッドを呼び出す必要がある場合、スレッドはオブジェクトのロックを所有している必要があります。

その後、オブジェクトのロックを解放し、他のスレッドがこのオブジェクトのメソッドを呼び出すまで待機状態になります。

通知()メソッド。同様に、スレッドがオブジェクトのnotify()メソッドを呼び出す必要がある場合、スレッドは

このオブジェクト ロックにより、待機中の他のスレッドがこのオブジェクト ロックを取得できるようになります。これらのメソッドはすべて、スレッドがオブジェクトのロックを保持する必要があるため、同期を通じてのみ実装でき、同期メソッドまたは同期ブロック内でのみ呼び出すことができます。

Thread クラスの yield メソッドは何をするのでしょうか?

現在のスレッドを実行状態 (実行状態) から実行可能状態 (準備完了状態) に変更します。

現在のスレッドは準備完了状態に達しました。次にどのスレッドが準備完了状態から実行状態に遷移するでしょうか? システムの割り当てに応じて、現在のスレッドまたは他のスレッドになる可能性があります。

Thread クラスの sleep() メソッドと yield() メソッドが静的なのはなぜですか?

Thread クラスの sleep() メソッドと yield() メソッドは、現在実行中のスレッド上で実行されます。それでその中で

待機状態にあるスレッドでこれらのメソッドを呼び出しても意味がありません。このため、これらのメソッドは静的です。これらは現在実行中のスレッドで動作し、プログラマが他の非実行スレッドから呼び出すことができると誤って考えることを防ぎます。

スレッドの sleep() メソッドと yield() メソッドの違いは何ですか?

(1) sleep() メソッドは、他のスレッドに実行の機会を与えるときにスレッドの優先順位を考慮しないため、優先順位の低いスレッドに実行の機会を与えます。yield() メソッドは、同じ優先順位以上のスレッドにのみ実行の機会を与えます。機会を実行することを優先します。

(2) スレッドは、sleep() メソッドの実行後にブロック状態になり、yield() メソッドの実行後に準備完了状態になります。

(3) sleep() メソッドは InterruptedException をスローすることを宣言しますが、yield() メソッドは例外を宣言しません。

(4) sleep() メソッドは、yield() メソッドより移植性が高く (オペレーティング システムの CPU スケジューリングに関連する)、同時スレッドの実行を制御するために yield() メソッドを使用することは通常推奨されません。

実行中のスレッドを停止するにはどうすればよいですか?

Javaで実行中のスレッドを終了するには、次の3つの方法があります。

\1. スレッドを正常に終了するには、終了フラグを使用します。つまり、run メソッドが完了するとスレッドが終了します。

\2. stop メソッドを使用して強制終了しますが、stop はサスペンドやレジュームと同様に有効期限が切れており無効なメソッドであるため、この方法は推奨されません。

\3. スレッドを中断するには、interrupt メソッドを使用します。

JavaのinterruptedメソッドとisInterruptedメソッドの違いは何ですか?

割り込み: スレッドを中断するために使用されます。このメソッドを呼び出したスレッドの状態は「中断」状態に設定されます。注: スレッドの割り込みは、スレッドの割り込みステータス ビットを設定するだけであり、スレッドは停止しません。ユーザーは監視する必要がある

スレッドの状態に応じて処理を行います。スレッドの中断をサポートするメソッド (つまり、スレッドが中断された後、

interruptedException メソッド)は、スレッドの中断状態を監視するもので、スレッドの中断状態が「中断状態」に設定されると、中断例外がスローされます。

interrupted: これは静的メソッドであり、現在の割り込み信号が true か false かを確認し、割り込み信号をクリアします。スレッドが中断された場合、interrupted の最初の呼び出しは true を返し、2 回目以降の呼び出しは false を返します。

isInterrupted: 現在の割り込み信号が true か false かを確認します。

ブロッキングメソッドとは、プログラムが他のことをせずにメソッドの完了を待つことを意味し、ServerSocket の accept() メソッドはクライアントの接続を待ちます。ここでのブロックとは、呼び出しの結果が返される前に現在のスレッドが一時停止され、結果が取得されるまで戻らないことを意味します。さらに、タスクが完了する前に戻る非同期および非ブロッキング メソッドもあります。

Java でブロックされたスレッドを起動するにはどうすればよいですか?

まず、wait()メソッドとnotify()メソッドはオブジェクト用であり、任意のオブジェクトのwait()メソッドを呼び出すとスレッドがブロックされ、同時にオブジェクトのロックが解放されます。オブジェクトの wait() メソッドを使用すると、スレッドがブロックされます。

Notice() メソッドは、オブジェクトによってブロックされているスレッドのブロックをランダムに解除しますが、続行する前に、取得が成功するまでオブジェクトのロックを再取得する必要があります。

次に、wait メソッドと Notice メソッドは同期されたブロックまたはメソッドで呼び出される必要があり、同期されたブロックまたはメソッドのロック オブジェクトは、wait メソッドと Notify メソッドを呼び出すオブジェクトと同じである必要があります。そのため、現在のスレッドは呼び出す前に成功している必要があります。 wait オブジェクトのロックを取得し、wait to block を実行した後、現在のスレッドは以前に取得したオブジェクト ロックを解放します。

Notice() と NotifyAll() の違いは何ですか?

スレッドがオブジェクトの wait() メソッドを呼び出した場合、スレッドはオブジェクトの待機プール内に存在し、待機プール内のスレッドはオブジェクトのロックをめぐって競合しません。

NoticeAll() はすべてのスレッドをウェイクアップしますが、notify() は 1 つのスレッドのみをウェイクアップします。

NoticeAll() が呼び出された後、すべてのスレッドは待機プールからロック プールに移動され、ロック競合に参加します。

成功した場合は実行を継続しますが、失敗した場合はロック プールに留まり、ロックが解放されるのを待ってから再度競合に参加します。また、notify() は 1 つのスレッドのみをウェイクアップし、どのスレッドをウェイクアップするかは仮想マシンによって制御されます。2 つのスレッド間でデータを共有するにはどうすればよいですか? 共有は、2 つのスレッド間で変数を共有することによって実現されます。

一般に、共有変数は変数自体がスレッド セーフである必要があり、スレッドで使用する場合、共有変数に複合操作がある場合は、複合操作のスレッド セーフも保証する必要があります。

Java は複数のスレッド間の通信とコラボレーションをどのように実装しますか?

スレッド間の通信とコラボレーションは、変数を中断して共有することで実現できます。たとえば、古典的な生産者/消費者モデル: キューがいっぱいの場合、生産者はキューに商品を投入し続ける前に、キューにスペースができるまで待つ必要があります。待機中 この期間中、プロデューサーは重要なリソース (つまり、キュー) を占有する権利を解放する必要があります。生産者が重要なリソースを占有する権利を解放しない場合、消費者はキュー内の商品を消費できず、キューにスペースがないため、生産者は無限に待機することになります。したがって、通常の状況では、キューがいっぱいになると、プロデューサーは重要なリソースを占有する権利を放棄し、一時停止状態に入ります。次に、消費者が製品を消費するのを待ち、消費者はキューに空きがあることを生産者に通知します。同様に、キューが空の場合、コンシューマは、キューに項目があることをプロデューサから通知されるまで待機する必要があります。この相互に通信するプロセスは、スレッド間のコラボレーションです。

Java では、スレッドの通信と連携には 2 つの一般的な方法があります。

1.同期ロックスレッドのオブジェクトクラスのwait()/notify()/notifyAll()

2. ReentrantLock クラスによってロックされたスレッドの Condition クラスの await()/signal()/signalAll() は、スレッド間で直接データ交換を行います。

3. パイプラインを介したスレッド間通信: 1) バイト ストリーム、2) 文字ストリームの同期方法と同期ブロック、どちらがより良い選択ですか?

同期ブロックはオブジェクト全体をロックしないため、より良い選択です (もちろん、オブジェクト全体をロックすることもできます)。同期メソッドは、このクラス内に無関係な同期ブロックが複数ある場合でも、オブジェクト全体をロックします。これにより、通常、メソッドの実行が停止し、このオブジェクトのロックを取得するまで待機する必要があります。

同期ブロックはオープン呼び出しの原則に準拠し、ロックする必要があるコード ブロック内の対応するオブジェクトのみをロックする必要があるため、デッドロックも側面から回避できます。

原則を知っておいてください。同期の範囲は小さいほど良いということです。

スレッド同期とスレッド相互排他とは何ですか?またその実装方法は何ですか?

スレッドが共有データを操作するときは、「アトミック操作」にする必要があります。つまり、関連する操作が完了するまで他のスレッドがスレッドを中断することはできません。そうしないと、データの整合性が破壊され、間違った処理結果、これがスレッド同期です。

マルチスレッド アプリケーションでは、異なるスレッド間のデータ同期を考慮してデッドロックを防ぎます。スレッド間のデッドロックは、2 つ以上のスレッドが互いのリソースを同時に解放するのを待っている場合に形成されます。デッドロックを防ぐには、同期を通じてスレッドの安全性を実現する必要があります。

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

スレッド間の同期方法は、ユーザーモードとカーネルモードの2つに大別できます。カーネルモードとは、その名のとおり、システムカーネルオブジェクトの単一性を利用して同期を行うことを指し、使用する際にはカーネル状態とユーザー状態を切り替える必要がありますが、ユーザーモードは切り替える必要がありません。カーネル状態に移行し、ユーザー状態でのみ操作を完了します。

ユーザー モードのメソッドは次のとおりです: アトミック操作 (単一のグローバル変数など)、クリティカル セクション。カーネル モードのメソッドは、イベント、セマフォ、およびミューテックスです。

スレッド同期を実現する方法

  • 同期コードメソッド: 同期キーワード修正メソッド同期コードブロック:
  • synchronized キーワードで修飾されたコード ブロック
  • 特殊な変数ドメイン volatile を使用したスレッド同期: volatile キーワードは、ドメイン変数アクセスにロックフリーのメカニズムを提供します。
  • リエントラント ロックを使用してスレッド同期を実現します: reentrantlock クラスは、フラッシュ可能で相互に排他的で、ロック インターフェイスを実装するロックであり、synchronized メソッドと同じ基本的な動作とセマンティクスを持ちます。

モニター(Monitor)内でスレッド同期を行うにはどうすればよいですか?プログラムはどのレベルの同期を実行する必要がありますか?

Java 仮想マシンでは、各オブジェクト (オブジェクトとクラス) が何らかのロジックを通じてモニターに関連付けられ、各モニターはオブジェクト参照に関連付けられ、モニターの相互排他機能を実現するために、各オブジェクトがオブジェクト参照に関連付けられます。ロック。

メソッドまたはコード ブロックが同期によって変更されると、この部分はモニターの

領域を監視して、一度に 1 つのスレッドのみがコードのこの部分を実行できること、およびロックを取得する前にスレッドがコードのこの部分を実行できないことを確認します。

さらに、Java は、明示的モニター (ロック) と暗黙的モニター (同期) という 2 つのロック スキームも提供します。

タスクを送信したときにスレッド プール キューがいっぱいになった場合はどうなりますか

ここでの違いは次のとおりです。

(1) 無制限のキュー LinkedBlockingQueue、つまり無制限のキューを使用している場合、

問題はありません。実行を待つためにブロッキング キューにタスクを追加し続けます。LinkedBlockingQueue はタスクを無限に保存できる無限キューとほぼみなすことができるためです。

(2) ArrayBlockingQueue などのバインドされたキューを使用している場合、タスクは最初に追加されます。

ArrayBlockingQueue では、ArrayBlockingQueue がいっぱいの場合、

MaximumPoolSize の値によりスレッド数が増加します。スレッド数が増加しても ArrayBlockingQueue が引き続き満杯の場合は、拒否戦略が使用されます。

RejectedExecutionHandler は完全なタスクを処理します。デフォルトは AbortPolicy です。 スレッド セーフとは何ですか? サーブレットはスレッドセーフですか?

スレッド セーフとはプログラミングの用語で、マルチスレッド環境でメソッドが呼び出されたときに、複数のスレッド間で共有変数を正しく処理できるため、プログラム関数が正しく完了できることを意味します。

サーブレットはスレッド セーフではありません。サーブレットは単一インスタンスのマルチスレッドです。複数のスレッドが同時に同じメソッドにアクセスする場合、共有変数のスレッド セーフは保証できません。

Struts2のアクションはマルチインスタンス、マルチスレッドでスレッドセーフであり、リクエストが来るたびにそのリクエストに新しいアクションが割り当てられ、リクエストが完了すると破棄されます。

SpringMVC のコントローラーはスレッドセーフですか? いいえ、処理の流れはサーブレットと似ています。

Struts2 の利点は、スレッドの安全性の問題を考慮する必要がないことです。サーブレットと SpringMVC はスレッドを考慮する必要があります。

セキュリティの問題はありますが、過度の gc に対処することなくパフォーマンスを向上させることができます。ThreadLocal を使用してマルチスレッドの問題に対処できます。

Java プログラムでのマルチスレッド操作の安全性を確保するにはどうすればよいですか?

方法 1: java.util.concurrent の下のクラスなどのセキュリティ クラスを使用し、アトミック クラス AtomicInteger を使用する

方法 2: 自動ロック同期を使用します。

方法 3: 手動ロックを使用する ロックします。

手動ロックの Java サンプル コードは次のとおりです。

1 Lock lock = new ReentrantLock(); 
2 lock. lock(); 
3 try {
4	System. out. println("获得锁");
5	} catch (Exception e) {
6	// TODO: handle exception
7	} finally {
8	System. out. println("释放锁");
9	lock. unlock();
10	}

スレッドの優先順位についてどのように理解していますか?

各スレッドには優先順位があり、一般に実行時には優先順位の高いスレッドが優先されますが、これはオペレーティング システム (OS に依存) に関連するスレッド スケジューリングの実装によって異なります。スレッドの優先順位を定義できますが、これは、優先順位の高いスレッドが優先順位の低いスレッドよりも前に実行されることを保証するものではありません。スレッドの優先順位は int 変数 (1 ~ 10) で、1 は低い優先順位、10 は高い優先順位を示します。Java のスレッド優先度のスケジューリングはオペレーティング システムに委ねられるため、特定のオペレーティング システムの優先度に関連付けられますが、特別な必要がない場合、通常はスレッド優先度を設定する必要はありません。

Java のスレッド優先度のスケジューリングはオペレーティング システムに委ねられるため、特定のオペレーティング システムの優先度に関連付けられますが、特別な必要がない場合、通常はスレッド優先度を設定する必要はありません。

スレッド クラスの構築メソッドと静的ブロックが呼び出されるスレッド

これは非常にトリッキーで狡猾な質問です。覚えておいてください: スレッド クラスのコンストラクター、静的ブロックは

New はスレッド クラスが配置されているスレッドによって呼び出され、run メソッドのコードはスレッド自体によって呼び出されます。

上記の説明が混乱する場合は、Thread2 が新しいものとして例を示します。

Thread1、Thread2 が main 関数の新しいものである場合、次のようになります。

(1) Thread2 の構築メソッドと静的ブロックはメインスレッドによって呼び出され、Thread2 の run() メソッドは Thread2 自身によって呼び出されます。

(2) Thread1 の構築メソッドと静的ブロックは Thread2 によって呼び出され、Thread1 の run() メソッドは Thread1 自身によって呼び出されます。

Javaでスレッドダンプファイルを取得するにはどうすればよいですか? Javaでスレッドスタックを取得するにはどうすればよいですか?

ダンプ ファイルはプロセスのメモリ イメージです。プログラムの実行状態はデバッガを通じてダンプファイルに保存できます。

Linux では、コマンド kill -3 PID (Java プロセスのプロセス ID) によって Java アプリケーションのダンプ ファイルを取得できます。

Windows では、Ctrl + Break を押して取得できます。このようにして、JVM はスレッドのダンプ ファイルを標準出力またはエラー ファイルに出力します。コンソールまたはログ ファイルに出力される場合もあり、具体的な場所はアプリケーションの構成によって異なります。

スレッドの実行中に例外が発生するとどうなりますか? 例外がキャッチされない場合、スレッドは実行を停止します。

Thread.UncaughtExceptionHandler は、キャッチされなかった例外によるスレッドの突然の中断を処理するために使用される組み込みインターフェイスです。キャッチされなかった例外によってスレッドの中断が発生する場合、JVM は

Thread.getUncaughtExceptionHandler() スレッドをクエリする

UncaughtExceptionHandler は、スレッドと例外をパラメータとしてハンドラーの uncaughtException() メソッドに渡して処理します。

Java スレッドが多すぎるとどのような例外が発生しますか?

  • スレッドの存続期間中のオーバーヘッドが非常に高く、消費量が多すぎる

  • CPU リソース 実行可能なスレッドの数が使用可能なプロセッサの数よりも多い場合、一部のスレッドがアイドル状態になります。アイドル状態のスレッドが多数あると、大量のメモリが占​​有されてガベージ コレクターに負荷がかかります。また、スレッドが多数あると、CPU リソースを競合する際に他のパフォーマンスのオーバーヘッドも発生します。

  • 安定性の低下 JVM には作成できるスレッドの数に制限があります。この制限値はプラットフォームによって異なり、JVM 起動パラメーター、スレッド コンストラクターのリクエスト スタックのサイズ、制限などの複数の要因の影響を受けます。スレッドなどの基礎となるオペレーティング システムの これらの制限に違反すると、OutOfMemoryError 例外がスローされる可能性があります。

同時実行理論

Javaメモリモデル

Java におけるガベージ コレクションの目的は何ですか? ガベージコレクションはいつ行われますか? ガベージ コレクションは、メモリ内に参照されていないオブジェクト、またはスコープ外のオブジェクトがある場合に発生します。ガベージ コレクションの目的は、アプリケーションによって使用されなくなったオブジェクトを特定して破棄し、リソースを解放して再利用することです。

オブジェクトの参照が null に設定されている場合、ガベージ コレクターはオブジェクトが占有しているメモリをすぐに解放しますか?

いいえ、オブジェクトは次のガベージ コールバック サイクルでリサイクル可能になります。

つまり、ガベージ コレクターによってすぐには再利用されませんが、占有しているメモリは次のガベージ コレクションで解放されます。

Finalize() メソッドはいつ呼び出されますか? デストラクター (ファイナライゼーション) の目的は何ですか?

1) ガベージ コレクター (ガベージ コレクター) は、オブジェクトをリサイクルすることを決定すると、オブジェクトの Finalize() メソッドを実行します。

Finalize は Object クラスのメソッドであり、Object クラスでのこのメソッドの宣言 protected void Finalize() throws Throwable { }

ガベージ コレクターが実行されると、再利用されたオブジェクトの Finalize() メソッドが呼び出され、このメソッドをオーバーライドしてリソースの回復を実現できます。注: ガベージ コレクターは、オブジェクトによって占有されているメモリを解放する準備ができると、最初にオブジェクトの Finalize() メソッドを呼び出します。オブジェクトによって占有されているメモリ領域は、次のガベージ コレクション アクションが発生するまで実際には回復されません。

2) GC は本来メモリを再利用するためのものですが、アプリケーションはファイナライゼーションで他に何をする必要がありますか? 答えは、ほとんどの場合、何もする必要はありません (つまり、オーバーロードする必要はありません)。いくつかの非常に特殊な場合にのみ、たとえば、いくつかのネイティブ メソッド (通常は C で記述されている) を呼び出す場合、ファイナライゼーションで C の release 関数を呼び出すことができます。

データの依存関係の並べ替え コードの並べ替えはなぜ行われるのでしょうか?

[外部リンク画像の転送に失敗しました。ソース サイトにはリーチ防止メカニズムがある可能性があります。画像を保存して直接アップロードすることをお勧めします (img-fsRmIju2-1692509038139) (04-同時プログラミング面接の質問 (2020 最新版)-focus .assets/clip_image001.gif )] プログラムを実行する際、プロセッサやコンパイラはパフォーマンスを向上させるために命令の順序を変更することがよくありますが、それらは自由に並べ替えることはできません。条件: シングルスレッド内 プログラムの実行結果を環境内で変更できない; データの依存関係がある場合、並べ替えは許可されない シングルスレッド環境の実行結果には並べ替えは影響しませんが、影響を受けることに注意してください。マルチスレッドの実行セマンティクスを破壊します。

as-if-srialルールとhappens-beforeルールの違い

[外部リンク画像の転送に失敗しました。ソース サイトにはリーチ防止メカニズムがある可能性があります。画像を保存して直接アップロードすることをお勧めします (img-QlxYssLh-1692509038140) (04-同時プログラミング面接の質問 (2020 最新版)-focus .assets/clip_image002.gif )] as-if-serial セマンティクスは、単一スレッドでのプログラムの実行結果が変更されないことを保証し、happensbefore 関係により、正しく同期されたマルチスレッド プログラムの実行結果が変更されないことを保証します。 。[外部リンク画像の転送に失敗しました。ソース サイトには盗難防止リンク メカニズムがある可能性があります。画像を保存して直接アップロードすることをお勧めします (img-MUYsFGNW-1692509038140) (04-同時プログラミング面接の質問 (2020 最新版)-) focus.assets/clip_image003.gif )] as-if-serial セマンティクスは、シングルスレッド プログラムを作成するプログラマーに、シングルスレッド プログラムがプログラムの順序で実行されるという錯覚を引き起こします。前発生関係は、正しく同期されたマルチスレッド プログラムを作成するプログラマに錯覚を引き起こします。正しく同期されたマルチスレッド プログラムは、前発生で指定された順序で実行されます。

[外部リンク画像の転送に失敗しました。ソース サイトには盗難防止リンク メカニズムがある可能性があります。画像を保存して直接アップロードすることをお勧めします (img-k6LqCuxd-1692509038140) (04-同時プログラミング面接の質問 (2020 最新版)-) focus.assets/clip_image004.gif )] as-if-serial セマンティクスと happens-before の目的は、プログラムの実行結果を変更せずに、プログラム実行の並列性を可能な限り向上させることです。

同時キーワード

同期した

同期って何をするの?

Java では、synchronized キーワードを使用してスレッドの同期を制御します。つまり、マルチスレッド環境で、同期されたコード セグメントが複数のスレッドによって同時に実行されないように制御します。synchronized はクラス、メソッド、変数を変更できます。

さらに、Java の初期バージョンでは、同期は重いロックであり、監視が行われないため非効率的です。

モニター ロック (モニター) は、基礎となるオペレーティング システム (Java の) のミューテックス ロックに依存して実装されます。

スレッドはオペレーティング システムのネイティブ スレッドにマップされます。スレッドを一時停止またはウェイクアップする場合は、それを完了するためにオペレーティング システムの助けが必要であり、スレッド間で切り替えるときにオペレーティング システムはユーザー モードからカーネル モードに切り替える必要があります。これらの状態間の移行には比較的長い時間がかかります。時間と時間コストが比較的高いため、早期同期は非効率的でした。幸いなことに、Java 6 以降、Java 公式

JVM レベルからは、同期が大幅に最適化されているため、現在の同期ロックの効率も非常に最適化されています。JDK1.6 では、スピン ロック、適応スピン ロック、ロックの削除、ロック粗密化、バイアス ロック、軽量ロック、およびロック操作のオーバーヘッドを削減するその他のテクノロジなど、ロックの実装に多数の最適化が導入されています。

synchronized キーワードの使用方法を教えてください。プロジェクト内で使用されましたか?

synchronized キーワードを使用するには、主に 3 つの方法があります。

[外部リンク画像の転送に失敗しました。ソース サイトにはリーチ防止メカニズムがある可能性があります。画像を保存して直接アップロードすることをお勧めします (img-0qHcBdL6-1692509038141) (04-同時プログラミング面接の質問 (2020 最新版)-key) Points.assets/clip_image005.gif )] 変更されたインスタンス メソッド: 現在のオブジェクト インスタンスをロックするように動作し、同期コードを入力する前に現在のオブジェクト インスタンスのロックを取得します。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TQ3tUhed-1692509038141)(04-并发编程面试题(2020最新版)-重点.assets/clip_image006.gif)] 修饰静态方法: 也就是给当前类加锁,会作用于类的所有对象实例,因为静态成员不属于任何一个实例对象,是类成员( static 表明这是该类的一个静态资源,不管new了多少个对象,只有一份)。所以如果一个线程A调用一个实例对象的非静态 synchronized 方法,而线程B需要调用这个实例对象所属类的静态 synchronized 方法,是允许的,不会发生互斥现象,因为访问静态 synchronized 方法占用的锁是当前类的锁,而访问非静态 synchronized 方法占用的锁是当前实例对象锁。 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-H1QgANbK-1692509038141)(04-并发编程面试题(2020最新版)-重点.assets/clip_image007.gif)] 修饰代码块: 指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定

对象的锁。

总结: synchronized 关键字加到 static 静态方法和 synchronized(class)代码块上都是是给 Class 类上锁。synchronized 关键字加到实例方法上是给对象实

例上锁。尽量不要使用 synchronized(String a) 因为JVM中,字符串常量池具

有缓存功能!下面我以一个常见的面试题为例讲解一下 synchronized 关键字的具体使用。面试中面试官经常会说:“单例模式了解吗?来给我手写一下!给我解释一下双重检验锁方式实现单例模式的原理呗!” 双重校验锁实现对象单例(线程安全)

1 public class Singleton {
2
3 private volatile static Singleton uniqueInstance;
4
5	private Singleton() {
6	}
7
8	public static Singleton getUniqueInstance() {
9	//先判断对象是否已经实例过,没有实例化过才进入加锁代码
10	if (uniqueInstance == null) {
11	//类对象加锁
12	synchronized (Singleton.class) {
13	if (uniqueInstance == null) {
14	uniqueInstance = new Singleton();
15	}
16	}
17	}
18	return uniqueInstance;
19	}
20	}

また、 uniqueInstance は volatile キーワードで変更されていることに注意する必要があります。また、volatile キーワード uniqueInstance を使用して uniqueInstance を変更する必要があります。

= new Singleton(); このコードは実際には 3 つのステップで実行されます。

  1. uniqueInstance にメモリ領域を割り当てる

  2. uniqueInstance を初期化する

  3. uniqueInstance が割り当てられたメモリ アドレスを指すようにします。

ただし、JVMには命令の並び替えという機能があるため、実行順序が1→3→2となる場合があります。命令の再配置は、シングルスレッド環境では問題を引き起こしませんが、マルチスレッド環境では、スレッドがまだ初期化されていないインスタンスを取得する原因となります。たとえば、スレッド T1 は 1 と 3 を実行し、この時点で T2 が呼び出します。

getUniqueInstance() は、 uniqueInstance が空ではないことを検出すると、 uniqueInstance を返しますが、 uniqueInstance はまだ初期化されていません。volatile を使用すると、JVM の命令の再配置を禁止して、マルチスレッド環境でも正常に実行できるようにすることができます。同期の基本的な実装原理について教えてください。

同期は Java のキーワードであり、表示されるロックおよびロック解除のプロセスは使用中には表示されません。したがって、javap コマンドを使用して、対応するバイトコード ファイルを表示する必要があります。同期された同期ステートメントブロックの場合

1	public class SynchronizedDemo {
2	public void method() {
3	synchronized (this) {
4	System.out.println("synchronized 代码块");
5	}
6	}
7	}

JDK 逆アセンブリ命令 javap -c -v SynchronizedDemo を介して

[外部リンク画像の転送に失敗しました。ソース サイトにはリーチ防止メカニズムがある可能性があります。画像を保存して直接アップロードすることをお勧めします (img-3R8ToFF1-1692509038141) (04-コンカレント プログラミング面接の質問 (2020 最新版)-focus .assets/image-20201109183510197 .png)]

同期コードブロックの実行前後にモニターワードがあり、先頭が

後者は、monitorexit から離れることです。スレッドが同期コード ブロックも実行して、最初にロックを取得し、ロックを取得するプロセスは、monitorenter であり、コード ブロックの実行後、ロックを解放することは想像に難くありません。ロック、ロックの解除はmonitorexit命令を実行します。

モニター出口が 2 つあるのはなぜですか?

これは主に、同期コード ブロックの例外によってスレッドが終了するのを防ぐためですが、ロックは解放されず、必然的にデッドロックが発生します (待機中のスレッドがロックを取得することはありません)。したがって、後者のmonitorexitは、デッドロックを回避するために、異常な状況でもロックを解除できるようにするためのものです。

ACC_SYNCHRONIZED などのフラグしかありません。これは、スレッドがこのメソッドに入るときは、monitorenter が必要であり、このメソッドを出るときは、monitorexit が必要であることを示します。

同期リエントラントの原理

リエントラント ロックとは、スレッドがロックを取得した後も、引き続きロックを取得できることを意味します。基本的な原理はカウンタを維持します。スレッドがロックを取得すると、カウンタは 1 つ増加します。ロックが再び取得されると、カウンタは 1 つ増加します。ロックが解放されると、カウンタは 1 つ減少します。カウンタ値が 0 の場合、ロックがどのスレッドにも保持されていないことを示します。はい、他のスレッドがロックを取得するために競合する可能性があります。

スピンとは何ですか

多くの同期コードは非常に単純なコードであり、実行時間は非常に高速ですが、スレッドのブロックにはユーザー モードとカーネル モードの切り替えの問題が含まれるため、現時点では、待機中のすべてのスレッドをロックすることは価値のない操作である可能性があります。synchronized のコードは非常に高速に実行されるため、ロックを待機しているスレッドをブロックせず、synchronized の境界でビジー ループ (スピン) を実行することをお勧めします。複数のループを実行し、ロックを取得していないことが判明した場合は、ブロックする方が良い戦略である可能性があります。

マルチスレッドにおける同期ロックアップグレードの原理は何ですか?

同期ロック アップグレードの原則: ロック オブジェクトのオブジェクト ヘッダーに threadid フィールドがあります。初めて threadid にアクセスするとき、threadid は空です。jvm はバイアスされたロックを保持することを許可し、threadid を次のように設定します。スレッド ID がスレッド ID と一致しているかどうかを最初に判断します。

一貫性がある場合、オブジェクトを直接使用できます。一貫性がない場合、バイアスされたロックは軽量ロックにアップグレードされ、一定回数のスピン サイクルを通じてロックが取得されます。一定回数の実行後、使用するオブジェクトが正常に取得されなかった場合、ロックは軽量ロックから重量ロックにアップグレードされ、このプロセスは同期ロックのアップグレードを構成します。

ロックのアップグレードの目的: ロックのアップグレードは、ロックによるパフォーマンスの消費を削減することです。Java 6 以降では、synchronized の実装が最適化され、バイアスされたロックが軽量ロックにアップグレードされてから重量ロックにアップグレードされるため、ロックによるパフォーマンスの消費が削減されます。

スレッド B はスレッド A が変数を変更したことをどのようにして知るのでしょうか

(1) volatile 修飾子変数

(2) 変数の修正・変更の同期方式

(3) 待つ/通知する

(4) ポーリング中

スレッドがオブジェクトの同期メソッド A に入った後、他のスレッドがこのオブジェクトの同期メソッド B に入ることができますか?

できません。他のスレッドはオブジェクトの非同期メソッドにのみアクセスでき、同期メソッドはアクセスできません。非静的メソッドの同期修飾子は、メソッドの実行時にオブジェクトのロックを取得する必要があるため、オブジェクト ロックがすでに A メソッドに入っている場合、B メソッドに入ろうとしているスレッドは待機することしかできません。ロックプール用

(プールを待っているわけではないことに注意してください) オブジェクトのロックを待ちます。

同期、揮発性、CAS 比較

(1) synchronized は悲観的なロックであり、プリエンプティブであり、他のスレッドをブロックします。(2) volatile は、マルチスレッドの共有変数の可視性を提供し、命令の並べ替えの最適化を禁止します。

(3) CAS は競合検出に基づく楽観的ロック (ノンブロッキング)

同期とロックの違いは何ですか?

  • まず第一に、synchronized は Java の組み込みキーワードであり、JVM レベルでは、Lock は Java クラスです。
  • synchronized はクラス、メソッド、およびコード ブロックをロックできますが、lock はコード ブロックのみをロックできます。
  • Synchronized は手動でロックを取得、解放する必要がなく、簡単に使用できます。例外が発生すると自動的にロックを解放し、デッドロックが発生しません。一方、ロックは自分でロックと解放する必要があります。使用しない場合適切にロックを解除するための unLock() がない場合、ロックのデッドロックが発生します。
  • Lock を通じて、ロックが正常に取得されたかどうかを知ることができますが、同期ではわかりません。

同期ロックと ReentrantLock の違いは何ですか?

Synchronized は if、else、for、while と同じキーワードであり、ReentrantLock はクラスであり、これが 2 つの本質的な違いです。ReentrantLock はクラスであるため、次のものを提供します。

synchronized には、ますます柔軟な機能があり、継承可能、メソッドを持つことができ、さまざまなクラス変数を持つことができます。

synchronized の初期の実装は比較的効率が悪く、ReentrantLock と比較すると、ほとんどのシナリオのパフォーマンスが低くなります。

違いは非常に大きいですが、Java 6 では同期が大幅に改善されました。

同じ点: どちらもリエントラント ロックであり、両方ともリエントラント ロックです。「リエントラント ロック」の概念は、独自の内部ロックを再度取得できることです。たとえば、スレッドがオブジェクトのロックを取得しますが、この時点ではオブジェクトのロックは解放されていません。オブジェクトのロックを再度取得したい場合は、引き続き取得できます。ロックがリエントラントでない場合、スレッドはオブジェクトのロックを取得します。デッドロックが発生します。

同じスレッドがロックを取得するたびに、ロック カウンタは 1 ずつインクリメントされるため、ロック カウンタが 0 になるまでロックは解放できません。主な違いは次のとおりです。

  • ReentrantLock はより柔軟に使用できますが、ロックを解放するには協調的なアクションが必要です。
  • ReentrantLock は手動でロックを取得して解放する必要がありますが、synchronized では手動でロックを解放して開く必要はありません。
  • ReentrantLock はコード ブロック ロックにのみ適していますが、synchronized はクラス、メソッド、変数などを変更できます。
  • 実は両者のロック機構は異なります。ReentrantLock の最下層は Unsafe の park メソッドを呼び出してロックします。同期操作はオブジェクト ヘッダーのマーク ワードである必要があります。
  • Java のすべてのオブジェクトは、同期の基礎となるロックとして使用できます。共通の同期メソッド、ロックは現在のインスタンス オブジェクトです。
  • 静的同期メソッド。ロックは現在のクラスのクラス オブジェクトです。
  • 同期メソッド ブロック、ロックは括弧内のオブジェクトです

揮発性の

volatile キーワードの役割

可視性を確保するために、Java は可視性を保証し、命令の再配置を禁止する volatile キーワードを提供します。

volatile は事前発生保証を提供し、あるスレッドによって行われた変更が他のスレッドに確実に表示されるようにします。共有変数が volatile によって変更されると、変更された値が直ちにメイン メモリに更新され、他のスレッドがその値を読み取る必要がある場合には、メモリに移動して新しい値を読み取ります。

実用的な観点から見ると、volatile の重要な役割は、CAS と組み合わせてアトミック性を確保することです。詳細については、java.util.concurrent.atomic パッケージのクラスを参照してください。

アトミック整数。

Volatile は、マルチスレッド環境における単一の操作 (単一の読み取りまたは単一の書き込み) によく使用されます。

Java で揮発性配列を作成できますか?

はい、Java で揮発性配列を作成できますが、それは配列への参照のみであり、配列全体ではありません。つまり、参照が指す配列を変更すると、その配列は volatile によって保護されますが、複数のスレッドが同時に配列の要素を変更すると、volatile 識別子は以前の保護を実行できなくなります。

揮発性変数とアトミック変数の違いは何ですか?

volatile 変数は優先関係、つまり書き込み操作が後続の読み取り操作の前に発生することを保証できますが、アトミック性は保証されません。たとえば、count 変数が volatile で変更された場合、count++ 操作はアトミックではありません。

AtomicInteger クラスによって提供されるアトミック メソッドを使用すると、次のようにこの操作をアトミックにすることができます。

getAndIncrement() メソッドは現在の値をアトミックに 1 ずつインクリメントします。また、他のデータ型や参照変数も同様の操作を実行できます。

volatile は非アトミック操作をアトミックにできますか?

キーワード volatile の主な機能は、複数のスレッド間で変数を可視にすることですが、原子性は保証できません。複数のスレッドが同じインスタンス変数にアクセスするには、同期のためにロックが必要です。

volatile は可視性のみを保証できますが、アトミック性は保証できませんが、volatile を使用して long と double を変更すると、操作のアトミック性を保証できます。

Oracle Java 仕様から次のことがわかります。

  • 64 ビットの long および double の場合、それらが volatile によって変更されない場合、それらの操作はアトミックではない可能性があります。動作させる場合は2段階に分けて、それぞれ32ビットで動作します。
  • volatile を使用して long と double を変更すると、それらの読み取りと書き込みはアトミックな操作になり、64 ビット参照アドレスの読み取りと書き込みはアトミックな操作になります。
  • JVM実装時にlongやdoubleをアトミック操作として読み書きするかどうかを自由に選択できます
  • JVM がアトミック操作を実装することをお勧めします。

volatile 修飾子はどのように使用されますか?

シングルトンパターン

遅延初期化かどうか: はい マルチスレッドの安全性かどうか: はい

実装の難易度: より複雑 説明: ダブルチェックなどの考えられる問題 (もちろん、この可能性は非常に低いですが、結局のところまだいくつかあります~) の解決策は次のとおりです: インスタンス宣言に volatile を追加するだけです。

volatile キーワードの機能の 1 つは、命令の再配置を禁止することです。インスタンスが volatile として宣言された後、その書き込み操作にはメモリ バリア (メモリ バリアとは何ですか?) が存在します。そのため、割り当てが完了する前に、場合は、読み取り操作を呼び出す必要はありません。注: volatile が防止するのは、文 singleton = newSingleton() 内の [1-2-3] の命令の再配置ではなく、書き込み操作 ([1-

1 public class Singleton7 {
2 
3  private static volatile Singleton7 instance = null;
4 
5  private Singleton7() {}
6
7  public static Singleton7 getInstance() {
8  if (instance == null) {
9  synchronized (Singleton7.class) {
10  if (instance == null) {
11  instance = new Singleton7(); 
12  } 
13  } 
14  }
15 
16  return instance;
17  }
18 
19 }

同期と揮発性の違いは何ですか?

同期とは、1 つのスレッドだけがオブジェクトのロックを取得し、コードを実行し、他のスレッドをブロックできることを意味します。

volatile は、変数が CPU のレジスタ内で不定であり、メイン メモリから読み取る必要があることを意味します。マルチスレッド環境での変数の可視性を確保し、命令の並べ替えを禁止します。

違い

  • volatile は変数修飾子であり、synchronized はクラス、メソッド、変数を変更できます。
  • Volatile は変数の変更の可視性のみを実現できますが、アトミック性は保証できませんが、synchronized は変数の変更の可視性とアトミック性を保証できます。
  • Volatile ではスレッド ブロックが発生しませんが、synchronized ではスレッド ブロックが発生する可能性があります。
  • volatile とマークされた変数はコンパイラによって最適化されませんが、synchronized とマークされた変数はコンパイラによって最適化できます。
  • volatile キーワードはスレッド同期の軽量実装であるため、volatile のパフォーマンスは synchronized キーワードよりも明らかに優れています。ただし、volatile キーワードは変数にのみ使用でき、変数には使用できません。

synchronized キーワードはメソッドとコード ブロックを変更できます。同期されたキーワードは次のとおりです

JavaSE1.6以降、偏ったロックや軽量ロックの導入、ロックの取得や解放によるパフォーマンスの消費を削減するためのさまざまな最適化により、実行効率が大幅に向上しました。言葉で。

最後の

不変オブジェクトとは何ですか?また、同時アプリケーションの作成にどのように役立ちますか?

不変オブジェクト (Immutable Objects) とは、オブジェクトが作成されるとその状態 (オブジェクトのデータ、つまりオブジェクト プロパティの値) を変更することができず、それ以外の場合は変更可能なオブジェクト (Mutable Objects) であることを意味します。

不変オブジェクトのクラスを不変クラス(Immutable Class)と呼びます。Java プラットフォーム クラス ライブラリには、さまざまな種類のものが含まれています。

String などの可変クラス、プリミティブ型のラッパー クラス、BigInteger、BigDecimal など。

オブジェクトが不変であるのは、作成後にその状態を変更できないこと、すべてのフィールドが最終的なものであること、およびオブジェクトが正しく作成されたこと (作成中に this 参照のエスケープが発生しなかった場合) である場合に限られます。

不変オブジェクトはオブジェクトのメモリ可視性を保証し、不変オブジェクトの読み取りには追加の同期手段が必要ないため、コードの実行効率が向上します。

ロックシステム

ロックと AQS の概要

Java Concurrency API の Lock インターフェースとは何ですか? 同期と比較した場合の利点は何ですか?

Lock インターフェイスは、同期メソッドや同期ブロックよりもスケーラブルなロック操作を提供します。これらにより、より柔軟な構造が可能になり、まったく異なるプロパティを持つことができ、関連するクラスの複数の条件オブジェクトをサポートできます。

その利点は次のとおりです。

(1) ロックをより公平にできる

(2) ロックを待機している間にスレッドが割り込みに応答できるようにします。

(3) スレッドはロックの取得を試み、ロックを取得できない場合はすぐに戻るか、一定期間待機することができます。

(4) ロックは、異なるスコープの異なる順序で取得および解放できます。

全体として、Lock は synchronized の拡張バージョンであり、無条件、ポーリング可能 (tryLock メソッド)、タイミング (パラメーター付きの tryLock メソッド)、割り込み可能 (lockInterruptibly)、および複数条件キュー (newCondition メソッド) のロック操作を提供します。また、Lock の実装クラスは基本的に不公平なロック (デフォルト) と公平なロックをサポートし、synchronized は不公平なロックのみをサポートしますが、もちろん、ほとんどの場合、不公平なロックが効率的な選択です。

楽観的ロックと悲観的ロックの理解と実装方法、実装方法は何ですか?

悲観的ロック: 常に悪い状況を想定します。データを取得しに行くたびに、他の人がそのデータを変更すると考えます。そのため、データを取得するたびにデータをロックし、他の人がロックを取得するまでブロックするようにします。彼らはデータを取得したいと考えています。このようなロック メカニズムの多くは、行ロック、テーブル ロック、読み取りロック、書き込みロックなど、従来のリレーショナル データベースで使用されており、これらはすべて操作が実行される前にロックされます。もう 1 つの例は、Java での synchronized キーワードの実装です。これも悲観的ロックです。

楽観的ロック: 名前が示すように、非常に楽観的です。データを取得するたびに、他の人がそのデータを変更しないと考えてロックされません。ただし、更新するときは、他の人が更新したかどうかを判断します。この期間中のデータは、バージョン番号などのメカニズムを使用できます。オプティミスティック ロックは、スループットを向上させることができるマルチ読み取りアプリケーション タイプに適しています。データベースによって提供される write_condition メカニズムと同様、実際にはオプティミスティック ロックが提供されます。存在する

Java の java.util.concurrent.atomic パッケージにあるアトミック変数クラスは、楽観的ロックの実装方法である CAS を使用して実装されます。

楽観的ロックの実装:

1. バージョン識別子を使用して、読み取られたデータが送信されたデータと一致しているかどうかを判断します。送信後、バージョン識別を変更し、矛盾する場合は破棄して再試行する戦略を採用できます。

2. Java の比較と交換は CAS です。複数のスレッドが CAS を使用して同じ変数を同時に更新しようとすると、そのうちの 1 つのスレッドだけが変数の値を更新でき、他のスレッドは失敗し、失敗したスレッドは出場停止にはならないが、この競争に負けてもう一度挑戦するように言われた。CAS 操作には 3 つのオペランドがあります - 読み取りおよび書き込みが必要なメモリ位置 (V)、および比較のために期待される元の値です。

(A) と書き込まれる新しい値 (B)。メモリ位置 V の値が予期された古い値 A と一致する場合、プロセッサは位置の値を新しい値 B で自動的に更新します。それ以外の場合、プロセッサは何も行いません。

CASとは

CASとはcompare and swapの略で、いわゆる比較と交換のことです。

cas はロックベースの操作であり、楽観的ロックです。Java のロックは、楽観的ロックと悲観的ロックに分類されます。

悲観的ロックはリソースをロックすることであり、次のスレッドは、以前にロックを取得したスレッドがロックを解放した後にのみアクセスできます。楽観的ロックは幅広い考え方を採用し、データを取得するためにレコードにバージョンを追加するなど、特定の方法でロックせずにリソースを処理するため、悲観的ロックと比較してパフォーマンスが大幅に向上します。

CAS 操作には、メモリ位置 (V)、予期される古い値 (A)、および新しい値 (B) の 3 つのオペランドがあります。メモリアドレスの値が A の値と同じ場合、メモリの値を B に更新します。

CAS は無限ループを通じてデータを取得します。ループの最初のラウンドにある場合、スレッドは内部のアドレスを取得します。

の値が b スレッドによって変更されると、a スレッドがスピンする必要があり、次のサイクルまで実行できなくなります。

java.util.concurrent.atomic パッケージ内のクラスのほとんどは、CAS 操作を使用して実装されています。

(AtomicInteger、AtomicBoolean、AtomicLong)。

CASの何が問題なのでしょうか?

1. ABA 問題: たとえば、スレッド 1 がメモリ位置 V から A をフェッチし、この時点で別のスレッド 2 もメモリから A をフェッチし、2 が何らかの操作を実行してそれを B に変更し、2 がデータを次の位置に返します。 V の位置は A になります。この時点で、スレッド 1 が CAS 操作を実行し、A がまだメモリ内にあることがわかり、1 つの操作が成功します。スレッド 1 の CAS 操作は成功しましたが、根本的な問題が存在する可能性があります。Java1.5 以降、JDK のアトミック パッケージには、ABA 問題を解決するクラス AtomicStampedReference が提供されています。

2. サイクルタイムが長く、コストが高い:

深刻なリソース競合 (深刻なスレッド競合) の場合、CAS がスピンする可能性が比較的高くなり、より多くの CPU リソースが浪費され、効率は同期の場合よりも低くなります。

3. 保証できるのは 1 つの共有変数のアトミック操作のみです。共有変数で操作を実行する場合、循環 CAS メソッドを使用してアトミック操作を保証できますが、複数の共有変数を操作する場合、循環 CAS はアトミック操作を保証できません。アトミック操作 セックス、現時点ではロックを使用できます。デッドロックとは何ですか?

スレッド A が排他ロック a を保持し、排他ロック b を取得しようとすると、スレッド B が排他ロックを保持します。

b で排他ロック a を取得しようとすると、AB の 2 つのスレッド間でブロッキング現象が発生します。これは、AB の 2 つのスレッドが互いに必要なロックを保持しているためであり、これをデッドロックと呼びます。

デッドロックの条件は何ですか? デッドロックを防ぐにはどうすればよいでしょうか?

デッドロックが発生するために必要な条件は次のとおりです。

1. 相互排他条件: いわゆる相互排他とは、プロセスが一定期間内にリソースを独占することを意味します。

2. 要求と保持の条件: リソースの要求によりプロセスがブロックされた場合、プロセスは取得したリソースを手放しません。

3. 非剥奪条件: プロセスはリソースを取得しているため、リソースが使い果たされる前に強制的に剥奪することはできません。

4. 循環待機条件: 複数のプロセス間で先頭から末尾までの循環待機リソース関係が形成されます。

この 4 つの条件はデッドロックの必要条件であり、システムがデッドロック状態にある限り、これらの条件が成立する必要があり、いずれか 1 つでも満たされない限り、デッドロックは発生しません。

デッドロックの原因、特にデッドロックに必要な 4 つの条件を理解することで、デッドロックを可能な限り回避、防止、解決できます。

デッドロックを防ぐには、次の方法を使用できます。

  • tryLock(ロングタイムアウト、TimeUnit単位)メソッド(ReentrantLock、
  • ReentrantReadWriteLock)、タイムアウト期間を設定します。デッドロックを防ぐためにタイムアウトを終了できます。
  • 独自の手書きロックの代わりに java.util.concurrent 同時クラスを使用してみてください。ロック使用の粒度を減らし、複数の機能に同じロックを使用しないようにしてください。
  • 同期されたコード ブロックを最小限に抑えます。

デッドロックとライブロックの違い、デッドロックとスターベーションの違いは何ですか?

デッドロック:複数のプロセス(スレッド)が実行処理中にリソースの奪い合いにより待ち合い、外部からの力がないと先に進めなくなる現象を指します。

ライブロック: いくつかの条件が満たされていないため、タスクまたは実行者はブロックされず、試行、失敗、試行、失敗が繰り返されます。

ライブロックとデッドロックの違いは、ライブロック内のエンティティは「ライブ」と呼ばれる状態を常に変更しているのに対し、デッドロック内のエンティティは待機していることです。ライブロックは自動的に解決される可能性がありますが、デッドロックは解決されません。 。

飢餓: 1 つ以上のスレッドがさまざまな理由で必要なリソースを取得できず、実行できない状態になります。

Java での飢餓の原因:

1. 高優先度のスレッドは、低優先度のスレッドのすべての CPU 時間を消費します。

2. 他のスレッドは常にその前の同期ブロックにアクセスし続けることができるため、スレッドは同期ブロックに入るのを待っている状態で永続的にブロックされます。

3. 他のスレッドは常に継続的に起動されるため、スレッドは、それ自体が完了を永続的に待機しているオブジェクト (このオブジェクトの wait メソッドの呼び出しなど) を待機しています。

マルチスレッド ロックのアップグレード原理は何ですか?

Java には、ロックの 4 つの状態があります。レベルは、低レベルから高レベルまで、ステートレス ロック、バイアスされたロック、軽量ロック、および重量ロックの状態です。これらの状態は、競合によって徐々にエスカレートします。ロックはアップグレードできますが、ダウングレードはできません

AQ(レベル.)S(AbstractQueuedSynchronizer)の詳細説明とソースコード解析

AQS の概要

AQS (AbstractQueuedSynchronizer) の完全名。このクラスは java.util.concurrent.locks パッケージの下にあります。

[外部リンク画像の転送に失敗しました。ソース サイトには盗難防止リンク メカニズムがある可能性があります。画像を保存して直接アップロードすることをお勧めします (img-bMVDUy0x-1692509038142) (04-同時プログラミング面接の質問 (2020 最新版)-) focus.assets/image-20201109202104977 .png)]

AQS は、ロックとシンクロナイザーを構築するためのフレームワークです。AQS を使用すると、前述の ReentrantLock や Semaphore などの広く使用されている多数のシンクロナイザーや、ReentrantReadWriteLock、SynchronousQueue、FutureTask などのその他のシンクロナイザーを簡単かつ効率的に構築できます。 AQSに基づいています。もちろん、AQS を使用して、独自のニーズを満たすシンクロナイザーを簡単に構築することもできます。

AQS原理分析

以下の内容のほとんどは実際に AQS の授業ノートに記載されていますが、英語で読むのは少し難しいので、興味のある方はソースコードをご覧ください。

AQS原則の概要

AQS の基本的な考え方は、要求された共有リソースがアイドル状態の場合、そのリソースを現在要求しているスレッドが有効なワーカー スレッドとして設定され、共有リソースがロック状態に設定されるというものです。要求された共有リソースが占有されている場合、スレッドをブロックして待機し、目覚めたときにロックを割り当てるメカニズムが必要です。

AQS は CLH キュー ロックを使用して実装されます。つまり、一時的にロックを取得できないスレッドがキューに追加されます。

CLH (Craig、Landin、および Hagersten) キューは、仮想双方向キューです (仮想双方向キューとは、キュー インスタンスがなく、ノード間の関連付け関係だけが存在することを意味します)。AQS は、共有リソースを要求する各スレッドを CLH ロック キューのノード (Node) にカプセル化し、ロック割り当てを実装します。

AQS (AbstractQueuedSynchronizer) の回路図を見てください。

[外部リンク画像の転送に失敗しました。ソース サイトにはリーチ防止メカニズムがある可能性があります。画像を保存して直接アップロードすることをお勧めします (img-ObkxhaOx-1692509038142) (04-同時プログラミング面接の質問 (2020 最新版)-focus .assets/image-20201109202131975 .png)]

AQS は、int メンバー変数を使用して同期状態を表し、組み込み FIFO キューを通じてリソース スレッドのキューイング作業を完了します。AQS は、CAS を使用して同期状態に対してアトミック操作を実行し、その値を変更します。

private volatile int state;//共享变量,使用volatile修饰保证线程可见性

状態情報は、保護された型 getState、setState、compareAndSetState を通じて操作されます。

1	//返回同步状态的当前值
2	protected final int getState() {
3	return state;
4	}
5	// 设置同步状态的值
6	protected final void setState(int newState) {
7	state = newState;
8	}
9	//原子地(CAS操作)将同步状态值设置为给定值update如果当前同步状态的值等于expect (期望值)
10	protected final boolean compareAndSetState(int expect, int update) { 
11 return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
12 }

AQS がリソースを共有する方法

AQS は 2 つのリソース共有方法を定義します

  • xclusive (排他的): ReentrantLock など、1 つのスレッドのみを実行できます。公平なロックと不公平なロックに分けることができます。

    公平なロック: キュー内のスレッドのキュー順序に従って、最初に来たスレッドが最初にロックを取得します。

    不公平なロック: スレッドがロックを取得したい場合、キューの順序を無視して直接ロックを取得します。取得した人がそのロックを所有することになります。

  • Share(共有):Semaphore/CountDownLatchなど、複数のスレッドを同時に実行できます。Semaphore、CountDownLatch、CyclicBarrier、ReadWriteLock については後ほど説明します。ReentrantReadWriteLock は、複数のスレッドが同時にリソースを読み取ることを可能にする読み取り/書き込みロックでもあるため、ReentrantReadWriteLock は組み合わせとみなすことができます。さまざまなカスタム シンクロナイザーがさまざまな方法で共有リソースを競合します。カスタムシンクロナイザーを実装する場合、共有リソース状態の取得と解放メソッドを実装するだけでよく、特定スレッドの待ちキューのメンテナンス(リソース取得失敗後のキューへの投入/復帰など)についても実装が必要です。 .)、AQS はすでにトップレベルで実装されています。

AQS の最下層はテンプレート メソッド パターンを使用します。シンクロナイザーの設計はテンプレート メソッド パターンに基づいています。シンクロナイザーをカスタマイズする必要がある場合、一般的な方法は次のとおりです。

(テンプレート メソッド パターンの古典的なアプリケーション):

  1. ユーザーは AbstractQueuedSynchronizer を継承し、指定されたメソッドをオーバーライドします。(これらの書き換え方法は非常に単純で、共有リソースの状態の取得と解放にすぎません)

  2. カスタム同期コンポーネントの実装に AQS を結合し、そのテンプレート メソッドを呼び出します。これにより、ユーザーによってオーバーライドされたメソッドが呼び出されます。

これは、以前にインターフェイスを実装するために使用していた方法とは大きく異なり、テンプレート メソッド パターンの古典的なアプリケーションです。

AQS はテンプレート メソッド パターンを使用します。シンクロナイザーをカスタマイズするときは、AQS が提供する次のテンプレート メソッドを書き直す必要があります。

1	isHeldExclusively()//该线程是否正在独占资源。只有用到condition才需要去实现它。
2	tryAcquire(int)//独占方式。尝试获取资源,成功则返回true,失败则返回false。
3	tryRelease(int)//独占方式。尝试释放资源,成功则返回true,失败则返回false。
4	tryAcquireShared(int)//共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。
5	tryReleaseShared(int)//共享方式。尝试释放资源,成功则返回true,失败则返回fals e。
6

すべてのメソッドはデフォルトで UnsupportedOperationException をスローします。これらのメソッドの実装は内部的にスレッドセーフである必要があり、通常はブロックするのではなく短くする必要があります。AQS クラスの他のメソッドは Final であるため、他のクラスでは使用できません。他のクラスで使用できるのは、これらのメソッドのみです。ReentrantLock を例にとると、状態は 0 に初期化され、ロックが解除された状態を示します。スレッド lock() の場合、

tryAcquire() を呼び出して、ロックと state+1 を独占します。その後、他のスレッドは tryAcquire() を実行します。

これは失敗し、A スレッドのunlock() が state=0 に達する (つまり、ロックが解放される) まで、他のスレッドがロックを取得する機会を持ちます。もちろん、ロックが解放される前に、スレッド A は繰り返しロックを取得できます (状態は疲れます)。

さらに)、これはリエントランシーの概念です。ただし、状態を確実にゼロに戻すためには、何回解放する必要があるかに注意してください。

CountDownLatch を例にとると、タスクは N 個のサブスレッドに分割されて実行され、状態も次のように初期化されます。

N (N はスレッド数と一致する必要があることに注意してください)。N 個のサブスレッドは並列に実行され、各サブスレッドの実行後に countDown() が 1 回実行され、CAS (Compare and Swap) で状態が 1 減らされます。すべてのサブスレッドが実行された後 (つまり、state=0)、メイン呼び出しスレッドは unpark() になり、その後、メイン呼び出しスレッドが await() 関数から戻って残りのアクションを続行します。

一般に、カスタム シンクロナイザーは排他メソッドまたは共有メソッドのいずれかであり、tryAcquire-tryRelease および tryAcquireShared-tryReleaseShared のいずれかを実装するだけで済みます。ただし、AQS は、次のような排他的メソッドと共有メソッドを同時に実装するカスタム シンクロナイザーもサポートしています。

リエントラント読み取り書き込みロック。

ReentrantLock(リエントラントロック)は、公平なロックや不公平なロックとは実装原理が異なります。

ReentrantLockはLockインターフェースを実装したクラスで、実際のプログラミングでもよく使われます。

再入可能をサポートする高いレートのロック。これは、共有リソースを繰り返しロックできることを意味します。つまり、現在のスレッドが再度ロックを取得してもブロックされません。

Java キーワード synchronized は再入可能を暗黙的にサポートしており、synchronized は自動インクリメントの取得と自動デクリメントの解放によって再入可能を実現します。同時に、ReentrantLock は公平なロックと不公平なロックもサポートします。したがって、ReentrantLock を完全に理解したい場合、重要なことは次のとおりです。

ReentrantLock 同期セマンティクスの学習: 1. リエントラント性の実現原理、2. 公平なロックと不公平なロック。

リエントランシーの実装原理 リエントランシーをサポートするには、次の 2 つの問題を解決する必要があります: 1. スレッドがロックを取得するとき、すでにロックを取得しているスレッドが現在のスレッドである場合、ロックを直接再度正常に取得します。ロックが n 回取得された場合、ロックは同じ n 回解放された後でのみ完全に解放されたとみなされます。

ReentrantLock は、公正なロックと不公平なロックという 2 種類のロックをサポートします。公平性とはロックの取得を指します。ロックが公平である場合、ロックを取得する順序はリクエストの絶対時間順序に従っており、FIFO を満たす必要があります。

読み書きロックのソースコード解析 ReentrantReadWriteLock

読み取り書き込みロックとは何ですか

まず最初に、ReentrantLock が悪いのではなく、ReentrantLock には制限がある場合があることを明確にしましょう。ReentrantLockを使用する場合、スレッドAがデータを書き込み、スレッドBがデータを読み取ることによるデータの不整合を防ぐためかもしれませんが、この方法では、スレッドCがデータを読み取り、スレッドDもデータを読み取っている場合、データを読み取ってもデータは変更されません、ロックする必要はありませんが、ロックされたままであるため、プログラムのパフォーマンスが低下します。このため、読み取り/書き込みロック ReadWriteLock が誕生しました。

ReadWriteLock は読み取り/書き込みロック インターフェイスであり、読み取り/書き込みロックは同時実行プログラムのパフォーマンスを向上させるために使用されるロック分離です。

ReentrantReadWriteLock テクノロジは、読み取りと書き込みの分離を実現する ReadWriteLock インターフェイスの特別な実装です。読み取りロックは共有され、書き込みロックは排他的です。読み取りと読み取りの間には相互排他はありません。読み取りと書き込み、書き込みと書き込み読み取り、書き込み、および書き込みは相互に排他的であるため、読み取りと書き込みのパフォーマンスが向上します。

読み取り/書き込みロックには、次の 3 つの重要な特性があります。

(1) 公平な選択性: 不公平 (デフォルト) および公平なロック取得方法をサポートしており、スループットは依然として公平より優れています。

(2) 再エントリ: 読み取りロックと書き込みロックの両方がスレッドの再エントリをサポートします。

(3) ロックのダウングレード: 書き込みロックの取得、読み取りロックの取得、書き込みロックの解放の順序に従って、書き込みロックを読み取りロックにダウングレードできます。

条件ソースコード解析と待機通知機構 LockSupport コンカレントコンテナの詳細説明 ConcurrentHashMap コンカレントコンテナ(JDK1.8版)の詳細説明とソースコード解析

コンカレントハッシュマップとは何ですか?

ConcurrentHashMap は、Java でのスレッドセーフで効率的な HashMap 実装です。通常、高い同時実行性が伴いますが、マップ構造を使用したい場合は、それが最初に思い浮かびます。ハッシュマップと比較すると、ConcurrentHashMap はスレッドセーフなマップであり、ロック セグメンテーションのアイデアを使用して同時実行性を向上させます。

では、スレッドセーフはどのように正確に実現するのでしょうか?

JDK 1.6 バージョンの主要な要素:

  • セグメントは、ロックとして ReentrantLock の役割を継承し、各セグメントにスレッドの安全性を保証します。
  • セグメントはハッシュ テーブルの複数のバケットを維持し、各バケットは HashEntry で構成されるリンク リストです。

JDK1.8以降、ConcurrentHashMapは元のSegmentセグメントロックを放棄し、

同時実行性のセキュリティを確保するために CAS + 同期。

Java の ConcurrentHashMap の同時実行性は何ですか?

ConcurrentHashMap は、実際のマップをいくつかの部分に分割して、スケーラビリティとスレッドの安全性を実現します。この分割は、ConcurrentHashMap クラスのコンストラクターである同時実行性を使用して取得されます。

のオプションのパラメータ。デフォルト値は 16 で、マルチスレッド状況で競合を回避できます。

JDK8 以降、セグメント (ロック セグメント) の概念は放棄されましたが、CAS アルゴリズムを使用してそれを実装する新しい方法が可能になりました。同時に、同時実行性を向上させるために補助変数も追加されています。詳細はソースコードを確認してください。

同時コンテナの実装とは何ですか?

同期コンテナとは: 単純に言うと、synchronized を通じて同期を実現するコンテナと理解できます。

同期されたコンテナのメソッドを呼び出すスレッドが複数あり、それらはシリアルに実行されます。たとえば、ベクトル、

Hashtable、および Collections.synchronizedSet、synchronizedList、その他のメソッドによって返されるコンテナー。Vector や Hashtable などの同期コンテナの実装コードを確認すると、これらのコンテナがスレッド セーフを実現する方法は、状態をカプセル化し、同期する必要があるメソッドにキーワード synchronized を追加することであることがわかります。

同時コンテナーは、同期コンテナーとはまったく異なるロック戦略を使用して、より高い同時実行性とスケーラビリティーを提供します。たとえば、ConcurrentHashMap では、セグメント化されたロックと呼ばれる、よりきめの細かいロック機構が採用されています。このロック機構の下では、任意のロック機構が使用されます。同時にマップにアクセスできる読み取りスレッドの数が許可され、一定数の書き込み操作を許可しながら、読み取り操作を実行するスレッドと書き込み操作を実行するスレッドも同時にマップにアクセスできます。

ワーカー スレッドはマップを同時に変更するため、同時環境でより高いスループットを実現できます。

Java における同期コレクションと同時コレクションの違いは何ですか?

同期コレクションと同時コレクションはどちらも、マルチスレッドと同時実行に適したスレッドセーフなコレクションを提供しますが、同時コレクションの方がスケーラブルです。Java 1.5 より前は、プログラマは同期コレクションのみを使用する必要がありました。これにより、複数のスレッドが同時実行されると競合が発生し、システムのスケーラビリティが妨げられました。Java5 では、次のような同時コレクションが導入されました。

ConcurrentHashMap は、スレッドの安全性を提供するだけでなく、ロックの分離や内部パーティショニングなどの最新のテクノロジーによりスケーラビリティも向上します。

SynchronizedMap と ConcurrentHashMap の違いは何ですか?

SynchronizedMap はスレッドの安全性を確保するためにテーブル全体を一度にロックするため、一度に 1 つのスレッドのみがマップにアクセスできます。

ConcurrentHashMap はセグメント ロックを使用して、マルチスレッドでのパフォーマンスを保証します。

ConcurrentHashMap では、一度に 1 つのバケットがロックされます。ConcurrentHashMap はデフォルトでハッシュ テーブルを 16 個のバケットに分割し、get、put、remove などの一般的な操作は現在必要なバケットのみをロックします。

このように、以前は 1 つのスレッドしか入力できませんでしたが、現在は 16 個の書き込みスレッドが同時に実行できるようになり、同時実行パフォーマンスの向上は明らかです。

さらに、ConcurrentHashMap は別の反復方法を使用します。この反復メソッドでは、反復子が作成されてコレクションが変更されたときに、反復子がスローされなくなりました。

ConcurrentModificationException は、変更時に元のデータに影響を与えないように新しいデータに置き換えられます。イテレータの完了後、ヘッド ポインタを新しいデータに置き換えて、イテレータ スレッドが元の古いデータを使用できるようにし、書き込みスレッドも使用できるようにします。変更を完了します。

同時実行コンテナのCopyOnWriteArrayListの詳細説明

CopyOnWriteArrayList とは何ですか?また、どのようなアプリケーション シナリオに使用できますか? 長所と短所は何ですか?

CopyOnWriteArrayList は同時実行コンテナーです。多くの人がこれをスレッドセーフと呼んでいますが、この文は厳密ではなく、前提条件、つまり非複合シナリオでの動作がスレッドセーフであるという前提条件が欠けていると思います。

CopyOnWriteArrayList (ロックフリー コンテナ) の利点の 1 つは、複数のイテレータが同時にリストを走査して変更しても、ConcurrentModificationException がスローされないことです。存在する

CopyOnWriteArrayList では、書き込みにより基になる配列全体のコピーが作成されますが、ソース配列はそのまま残るため、コピーされた配列の変更中に読み取り操作を安全に実行できます。

CopyOnWriteArrayList の使用シナリオはソース コードを通じて分析され、その利点と欠点がより明らかであることがわかり、使用シナリオがより明確になります。読み取りが多く書き込みが少ないシナリオに適しています。

CopyOnWriteArrayList の欠点

  1. 書き込み操作中に配列をコピーする必要があるため、メモリが消費され、元の配列に多くのコンテンツが含まれている場合は、若い gc または完全な gc が発生する可能性があります。

  2. 配列のコピーや新しい要素の追加などのリアルタイムの読み取りシナリオには使用できません。時間がかかるため、set オペレーションを呼び出した後、読み取ったデータが古いままである可​​能性があります。

CopyOnWriteArrayList は最終的な整合性を実現できますが、それでもリアルタイム要件を満たすことはできません。

  1. 実際に使用する場合、CopyOnWriteArrayList にどれだけのデータが配置されるかは保証できない可能性があるため、少しデータが多すぎると、add/set のたびに配列をコピーする必要があり、コストがかかりすぎます。高性能のインターネット アプリケーションでは、このような操作により毎分エラーが発生します。

CopyOnWriteArrayListの設計思想

  1. 読み取りと書き込みの分離、読み取りと書き込みの分離

  2. 最終的な整合性

  3. 同時実行の競合を解決するためにスペースを開くという別のアイデアを使用します。同時実行コンテナの ThreadLocal の詳細な説明

スレッドローカルとは何ですか? どのような使用シナリオがありますか?

hreadLocal は、ローカル スレッド コピー変数ツール クラスです。

ThreadLocalMap オブジェクト、簡単に言えば、ThreadLocal は空間と時間を交換する方法です。

各スレッドは、独自の内部 ThreadLocalMap オブジェクトの値にアクセスできます。このようにして、複数のスレッド間でのリソース共有が回避されます。

原則: スレッドローカル変数は、スレッド自体に限定され、スレッド自体に属し、複数のスレッド間で共有されない変数です。Java は、スレッド セーフを実現する方法であるスレッド ローカル変数をサポートする ThreadLocal クラスを提供します。ただし、ワーカー スレッドがアプリケーション変数よりも長く存続する管理環境 (Web サーバーなど) でスレッド ローカル変数を使用する場合は注意してください。

作業終了後にスレッドローカル変数が解放されない場合、Java アプリケーションはメモリ リークの危険にさらされます。

古典的な使用シナリオは、スレッドごとに JDBC 接続 Connection を割り当てることです。このようにすることで、各スレッドが独自の Connection でデータベース操作を実行することが保証され、A のスレッドが B のスレッドが使用している Connection を閉じるなどの問題は発生せず、セッション管理などの問題も発生します。ThreadLocal の使用例:

public class TestThreadLocal {
2
3	//线程本地存储变量
4	private static final ThreadLocal<Integer> THREAD_LOCAL_NUM
5	= new ThreadLocal<Integer>() {
6	@Override
7	protected Integer initialValue() {
8	return 0;
9	}
10	};
11
12	public static void main(String[] args) {
13	for (int i = 0; i <3; i++) {//启动三个线程
14	Thread t = new Thread() {
15	@Override
16	public void run() {
17	add10ByThreadLocal();
18	}
19	};
20	t.start();
21	}
22	}
23
24	/**
25	* 线程本地存储变量加 5
*/
26 
27	private static void add10ByThreadLocal() {
28	for (int i = 0; i <5; i++) {
29	Integer n = THREAD_LOCAL_NUM.get();
30	n += 1;
31	THREAD_LOCAL_NUM.set(n);
32	System.out.println(Thread.currentThread().getName() + " : ThreadLocal n um=" + n);
33	}
34	}
35
36 }

結果の出力: 3 つのスレッドが開始され、各スレッドが「ThreadLocal num=5」に出力され、値が 15 になるまで num が累積されません。

1	Thread‐0 : ThreadLocal num=1
2	Thread‐1 : ThreadLocal num=1
3	Thread‐0 : ThreadLocal num=2
4	Thread‐0 : ThreadLocal num=3
5	Thread‐1 : ThreadLocal num=2
6	Thread‐2 : ThreadLocal num=1
7	Thread‐0 : ThreadLocal num=4
8	Thread‐2 : ThreadLocal num=2
9	Thread‐1 : ThreadLocal num=3
10	Thread‐1 : ThreadLocal num=4
11	Thread‐2 : ThreadLocal num=3
12	Thread‐0 : ThreadLocal num=5
13	Thread‐2 : ThreadLocal num=4
14	Thread‐2 : ThreadLocal num=5
15	Thread‐1 : ThreadLocal num=5

スレッドローカル変数とは何ですか?

スレッドローカル変数は、スレッド自体に属し、複数のスレッド間で共有されない、スレッド自体に限定された変数です。Java は、スレッド セーフを実現する方法であるスレッド ローカル変数をサポートする ThreadLocal クラスを提供します。ただし、ワーカー スレッドがアプリケーション変数よりも長く存続する管理環境 (Web サーバーなど) でスレッド ローカル変数を使用する場合は注意してください。作業終了後にスレッドローカル変数が解放されない場合、Java アプリケーションはメモリ リークの危険にさらされます。

ThreadLocal メモリ リークの分析と解決策

ThreadLocal がメモリ リークを引き起こす原因は何ですか?

ThreadLocalMap で使用されるキーは ThreadLocal への弱参照ですが、値は強参照です。したがって、ThreadLocal が外部から強く参照されていない場合、キーはガベージ コレクション中にクリーンアップされますが、値はクリーンアップされません。このようにして、キーが null のエントリが ThreadLocalMap に表示されます。何も対策を講じないと、値は GC によって回収されず、このときにメモリ リークが発生する可能性があります。ThreadLocalMap の実装ではこの状況が考慮されており、set()、get()、remove() メソッドが呼び出されると、キーが null のレコードがクリーンアップされます。使い終わる

ThreadLocal メソッドの後で、remove() メソッドを手動で呼び出すことをお勧めします。

ThreadLocal のメモリ リーク ソリューション?

  • ThreadLocal が使用されるたびに、remove() メソッドが呼び出されてデータがクリアされます。
  • スレッド プールを使用する場合、ThreadLocal のクリーンアップが間に合わないと、メモリ リークの問題だけでなく、さらに深刻な問題として、ビジネス ロジックに問題が発生する可能性があります。したがって、ThreadLocal を使用することは、ロック後にロックを解除し、使用後にクリーンアップすることと同じです。

同時コンテナの BlockingQueue の詳細な説明

ブロッキングキューとは何ですか? ブロッキングキューの実装原理は何ですか? ブロッキング キューを使用してプロデューサー/コンシューマー モデルを実装するにはどうすればよいですか?

ブロッキング キュー (BlockingQueue) は、2 つの追加操作をサポートするキューです。

これら 2 つの追加操作は次のとおりです。キューが空の場合、要素を取得するスレッドはキューが空でなくなるまで待機します。キューがいっぱいになると、要素を格納しているスレッドはキューが使用可能になるまで待機します。

ブロッキング キューは、プロデューサーとコンシューマーのシナリオでよく使用されます。プロデューサーはキューに要素を追加するスレッドであり、コンシューマーはキューから要素を取得するスレッドです。ブロッキング キューは、プロデューサーが要素を格納し、コンシューマーはコンテナから要素のみを取得するコンテナです。

JDK7 は 7 つのブロッキング キューを提供します。彼らです:

ArrayBlockingQueue : 配列構造で構成される境界付きブロッキング キュー。

LinkedBlockingQueue: リンク リスト構造で構成される境界付きブロッキング キュー。PriorityBlockingQueue : 優先順位の並べ替えをサポートする無制限のブロッキング キュー。

DelayQueue: 優先キューを使用して実装された無制限のブロッキング キュー。

SynchronousQueue: 要素を格納しないブロッキング キュー。

LinkedTransferQueue: リンク リスト構造で構成される無制限のブロッキング キュー。LinkedBlockingDeque: リンク リスト構造で構成される双方向ブロッキング キュー。

Java 5 より前に同期アクセスを実装する場合、通常のコレクションを使用してから、スレッドの協調的なアクセスを使用できます。

操作とスレッドの同期はプロデューサーモードとコンシューマーモードを実現するもので、それをうまく利用するのが主な技術です。

wait、notify、notifyAll、これらのキーワードを同期しました。Java 5 以降では、ブロッキング キューを使用して実装できるため、コードの量が大幅に削減され、マルチスレッド プログラミングが容易になり、セキュリティが保証されます。

BlockingQueue インターフェイスは Queue のサブインターフェイスであり、その主な目的はコンテナとしてではなく、

スレッド同期のためのツールなので、当然の特徴ですが、プロデューサスレッドが BlockingQueue に要素を入れようとしたとき、キューが満杯の場合、スレッドはブロックされ、コンシューマスレッドがそこから要素を取り出そうとすると、スレッドはブロックされます。 , キューが空の場合、スレッドはブロックされます。この機能があるからこそ、プログラム内の複数のスレッドが交互に BlockingQueue に要素を入れたり要素を取り出したりすることで、スレッド間の通信を非常にうまく制御することができます。

ブロッキング キューを使用する古典的なシナリオは、ソケット クライアント データの読み取りと解析です。データを読み取るスレッドは継続的にデータをキューに入れ、その後、解析スレッドは解析のためにキューからデータを継続的にフェッチします。

ConcurrentLinkedQueue の詳細説明とコンカレント コンテナのソース コード分析 コンカレント コンテナの ArrayBlockingQueue と LinkedBlockingQueue の詳細説明 スレッド プール エグゼキュータによる 4 つの共通スレッド プールの作成 スレッド プールとは何ですか? 作成するにはどのような方法がありますか?

プーリング テクノロジは他の人に比べて珍しいものではなく、スレッド プール、データベース接続プール、HTTP 接続プールなどはすべてこのアイデアの応用です。プーリング技術のアイデアは主に、各リソース取得の消費を削減し、リソースの利用率を向上させることです。

オブジェクト指向プログラミングでは、オブジェクトの作成にはメモリ リソースなどのリソースを取得する必要があるため、オブジェクトの作成と破棄には非常に時間がかかります。Java ではさらにそうですが、オブジェクトが破棄された後にガベージ コレクションできるように、仮想マシンは各オブジェクトを追跡しようとします。したがって、サービス プログラムの効率を向上させる 1 つの方法は、オブジェクトの作成と破棄の数、特にリソースを大量に消費する一部のオブジェクトの作成と破棄の数をできる限り減らすことであり、これが「リソース プーリング」テクノロジの理由です。

スレッドプールとは、その名のとおり、あらかじめ複数の実行スレッドを作成しておき、それをプール(コンテナ)に入れておくことで、スレッドを自分で作成することなく、必要なときにプールからスレッドを取得することができ、使用後は不要になります。スレッド オブジェクトを破棄する際のオーバーヘッド。Java 5 以降の Executor インターフェイスは、スレッドを実行するための機能を定義します。そのサブタイプであるスレッド プール インターフェイスは ExecutorService です。特にスレッド プールの原理があまり明確でない場合、スレッド プールの構成はより複雑になるため、ツール クラスで

Executor は、次のような一般的に使用されるスレッド プールを生成するためのいくつかの静的ファクトリ メソッドを提供します。

(1) newSingleThreadExecutor: シングルスレッドのスレッド プールを作成します。このスレッド プールでは動作するスレッドが 1 つだけあり、これはすべてのタスクを 1 つのスレッドで直列に実行することと同じです。唯一のスレッドが異常終了した場合は、新しいスレッドが代わりに使用されます。このスレッド プールは、すべてのタスクの実行順序が、タスクが送信された順序で実行されることを保証します。

(2) newFixedThreadPool: 固定サイズのスレッド プールを作成します。スレッドは、スレッド プールの最大サイズに達するまで、タスクが送信されるたびに作成されます。スレッド プールのサイズが大きな値に達すると、

変更は行われず、実行例外によりスレッドが終了した場合、スレッド プールは新しいスレッドを追加します。サーバー上でスレッド プールを使用する場合は、newFixedThreadPool メソッドを使用してスレッド プールを作成することをお勧めします。これにより、パフォーマンスが向上します。

(3) newCachedThreadPool: キャッシュ可能なスレッド プールを作成します。スレッド プールのサイズがタスクの処理に必要なスレッド数を超えると、一部のアイドル スレッド (60 秒間タスクを実行しなかった) が回復されます。タスクの数が増加すると、スレッド プールはタスクを処理するために新しいスレッドをインテリジェントに追加できます。 。このスレッド プールは、スレッド プールのサイズを制限しません。スレッド プールのサイズは、オペレーティング システム (または JVM) が作成できる最大スレッドのサイズに完全に依存します。

(4) newScheduledThreadPool: サイズ無制限のスレッド プールを作成します。このスレッド プールは、タスクのタイミングと定期的な実行をサポートします。

スレッドプールの利点は何ですか?

  • リソース消費の削減: 既存のスレッドを再利用し、オブジェクトの作成と破棄のオーバーヘッドを削減します。
  • 応答性を向上させます。同時スレッドの数を効果的に制御し、システム リソースの使用率を向上させ、過度のリソースの競合やブロックを回避できます。タスクが到着すると、スレッドの作成を待たずにすぐにタスクを実行できます。
  • スレッドの管理性を向上させます。スレッドは希少なリソースです。無制限に作成すると、システム リソースを消費するだけでなく、システムの安定性も低下します。スレッド プールを使用すると、一元的な割り当て、チューニング、監視が可能になります。
  • 追加機能: タイミング実行、周期実行、シングルスレッド、同時実行制御などの機能を提供します。要約すると、スレッド プール フレームワーク Executor を使用すると、スレッドをより適切に管理し、システム リソースの使用率を向上させることができます。

スレッド プールの状態は何ですか?

  • 実行中: これは通常の状態であり、新しいタスクを受け入れ、待機キュー内のタスクを処理しています。シャットダウン: 新しいタスクの送信は受け入れませんが、待機キュー内のタスクの処理は続行されます。
  • STOP: 新しいタスクの送信を受け入れず、待機キュー内のタスクを処理しなくなり、タスクを実行しているスレッドを中断します。
  • TIDYING: すべてのタスクが破棄され、workCount は 0 になり、スレッド プールのステータスは次のように変化します。
  • TIDYING 状態では、フックメソッドterminated()が実行されます。
  • TERMINATED:terminated() メソッドが終了すると、スレッド プールの状態は次のようになります。

エグゼキューターフレームワークとは何ですか? Executor フレームワークを使用する理由は何ですか?

Executor フレームワークは、一連の実行ポリシーに従って呼び出し、スケジュール、実行、制御される非同期タスクのフレームワークです。

タスクが実行されるたびにスレッド new Thread() を作成すると、パフォーマンスがさらに消費されます。スレッドの作成には時間とリソースが消費され、無制限にスレッドを作成するとアプリケーション メモリ オーバーフローが発生します。

したがって、スレッド プールを作成することは、スレッドの数を制限できるため、より良い解決策です。

これらのスレッドをリサイクルして再利用します。Executors フレームワークを使用してスレッド プールを作成すると非常に便利です。

Java の Executor と Executor の違いは?

  • Executor ツール クラスのさまざまなメソッドは、ビジネス ニーズを満たすためのニーズに応じてさまざまなスレッド プールを作成します。
  • Executor インターフェイス オブジェクトはスレッド タスクを実行できます。
  • ExecutorService インターフェイスは Executor インターフェイスを継承および拡張し、タスクの実行ステータスとタスクの戻り値を取得するためのメソッドをさらに提供します。
  • ThreadPoolExecutor を使用してカスタム スレッド プールを作成します。
  • Future は非同期計算の結果を表し、計算が完了したかどうかを確認し、計算の完了を待ち、get() メソッドを使用して計算結果を取得するメソッドを提供します。

スレッドプールの submit() メソッドとexecute() メソッドの違いは何ですか?

受け取ったパラメータ:execute() は、Runnable タイプのタスクのみを実行できます。submit()を実行できる

実行可能および呼び出し可能タイプのタスク。

戻り値: submit() メソッドは計算結果を保持する Future オブジェクトを返すことができますが、execute() には例外処理がありません。submit() は例外処理に便利です。

スレッド グループとは何ですか? Java でスレッド グループが非推奨になったのはなぜですか?

ThreadGroup クラスは、スレッドを特定のスレッド グループに割り当てることができます。スレッド グループ内にスレッド オブジェクトが存在することも、スレッド グループが存在することもできます。グループ内にスレッドが存在することもあります。この組織構造は、木。

スレッド グループとスレッド プールは 2 つの異なる概念であり、その機能はまったく異なります。前者はスレッドの管理を容易にし、後者はスレッドのライフ サイクルを管理し、スレッドを再利用し、スレッドの作成と破棄のオーバーヘッドを削減します。 。スレッド グループが廃止されるのはなぜですか? 使用にはセキュリティ上のリスクが多いため、具体的な調査は行われていませんが、使用する必要がある場合はスレッドプールを使用することをお勧めします。

スレッドプールのThreadPoolExecutorの詳細説明

スレッドプールを作成する Executor と ThreaPoolExecutor の違い

「Alibaba Java Development Manual」では、Executor を使用してスレッド プールを作成することは許可されていませんが、ThreadPoolExecutor を通じて作成することを強制しています。この処理方法により、執筆者はスレッド プールの実行ルールについてより明確になり、リソース枯渇のリスクが回避されます。

Executor の各方法の欠点:

  • newFixedThreadPool と newSingleThreadExecutor:
    主な問題は、蓄積されたリクエスト処理キューが非常に大きなメモリ、さらには OOM を消費する可能性があることです。
  • newCachedThreadPool と newScheduledThreadPool:
    主な問題は、スレッドの最大数が Integer.MAX_VALUE であるため、非常に多くのスレッドが作成されたり、OOM が作成される可能性があることです。

ThreaPoolExecutor がスレッド プールを作成する方法は 1 つだけあり、そのコンストラクターを使用してパラメーターを自分で指定することです。

スレッドプールの作成方法をご存知ですか?

スレッド プールを作成するにはさまざまな方法がありますが、ここでは ThreadPoolExecutor に答えるだけで済みます。

ThreadPoolExecutor() は、元のスレッド プール作成であり、Alibaba Java 開発マニュアルでスレッド プールを作成する明確に規定された方法でもあります。

ThreadPoolExecutor コンストラクターの重要なパラメーターの分析

ThreadPoolExecutor の 3 つの最も重要なパラメータ:

corePoolSize : コア スレッドの数。スレッドの数は、同時に実行できるスレッドの数を定義します。

minimumPoolSize : スレッド プール内に存在できるワーカー スレッドの最大数

workQueue: 新しいタスクが来ると、まず現在実行中のスレッド数がコアスレッド数に達しているかどうかを判断し、達している場合はキューにタスクを格納します。

ThreadPoolExecutor のその他の共通パラメータ:

  1. keepAliveTime: スレッド プール内のスレッドの数が corePoolSize より大きい場合、この時点で新しいタスクの送信がない場合、コア スレッドの外側のスレッドはすぐには破棄されませんが、待機時間が keepAliveTime を超えるまで待機してから破棄されます。リサイクルされ破壊される。

  2. unit : keepAliveTime パラメーターの時間単位。

  3. threadFactory: スレッド プールに新しいスレッドを作成するためのスレッド ファクトリを提供します。

  4. ハンドラー: スレッド プールのタスク キューが MaximumPoolSize を超えた後の拒否ポリシー ThreadPoolExecutor 飽和ポリシー

ThreadPoolExecutor 飽和ポリシーの定義:

現在同時に実行されているスレッドの数が最大スレッド数に達し、キューがいっぱいの場合、

ThreadPoolTask​​Executor は、いくつかの戦略を定義します。

  • ThreadPoolExecutor.AbortPolicy: RejectedExecutionException をスローして、新しいタスクの処理を拒否します。
  • ThreadPoolExecutor.CallerRunsPolicy: 独自のスレッド実行タスクを実行するために呼び出します。タスクのリクエストは行いません。ただし、この戦略は新しいタスクの送信速度を低下させ、プログラムの全体的なパフォーマンスに影響を与えます。また、この戦略はキューの容量を増やすことを好みます。アプリケーションがこの遅延を許容でき、タスク要求をタスク削除できない場合は、この戦略を選択できます。
  • ThreadPoolExecutor.DiscardPolicy: 新しいタスクを処理せず、単に破棄します。
  • ThreadPoolExecutor.DiscardOldestPolicy: このポリシーは、古い未処理のタスク要求を破棄します。

例: Spring が ThreadPoolTask​​Executor を通じて、または ThreadPoolExecutor のコンストラクターを通じて直接スレッド プールを作成するとき、指定しないとき

RejectedExecutionHandler 飽和戦略は、スレッド プールを構成するときにデフォルトで使用されます。

ThreadPoolExecutor。AbortPolicy。デフォルトでは、ThreadPoolExecutor は RejectedExecutionException をスローして新しいタスクを拒否します。これは、このタスクの処理が失われることを意味します。スケーラブルなアプリケーションの場合は、次の使用をお勧めします。

ThreadPoolExecutor.CallerRunsPolicy。大きなプールがいっぱいになった場合、この戦略により次のことが得られます。

スケールキュー。(これは、ThreadPoolExecutor のコンストラクターのソース コードを見ることで直接確認できます。比較的単純な理由により、コードはここには掲載されません。)

シンプルなスレッド プールのデモ: Runnable+ThreadPoolExecutor スレッド プールの実装原理

[外部リンク画像の転送に失敗しました。ソース サイトにはリーチ防止メカニズムがある可能性があります。画像を保存して直接アップロードすることをお勧めします (img-IUxHoZtN-1692509038143) (04-同時プログラミング面接の質問 (2020 最新版)-focus .assets/image-20201109203453876 .png)]

上記のインタビューの質問のいくつかの概念を皆さんにもっと知ってもらうために、簡単なスレッド プールを作成しました。

デモ。

まず、Runnable インターフェイスの実装クラスを作成します (もちろん、Callable インターフェイスにすることもできます。上記 2 つの違いについても説明しました)。

1 import java.util.Date;
2
3/** 
4  * 这是一个简单的Runnable类,需要大约5秒钟来执行其任务。
5  */
6 public class MyRunnable implements Runnable {
7 
8  private String command;
9 
10  public MyRunnable(String s) { 
11  this.command = s;
12  }
13
14  @Override 
15  public void run() { 
16  System.out.println(Thread.currentThread().getName() + " Start. Time = " + new Date()); 
17  processCommand();
18  System.out.println(Thread.currentThread().getName() + " End. Time = " + new Date()); 
19  }
20 
21  private void processCommand() {
22  try {
23  Thread.sleep(5000);
24  } catch (InterruptedException e) {
25  e.printStackTrace(); 
26  } 
27  }
28 
29  @Override
30  public String toString() { 
31  return this.command;
32  }
33 } 

テスト プログラムを作成するために、ここでは Alibaba が推奨する ThreadPoolExecutor コンストラクターのカスタム パラメーターを使用してスレッド プールを作成します。

1 import java.util.concurrent.ArrayBlockingQueue; 
2 import java.util.concurrent.ThreadPoolExecutor;
3 import java.util.concurrent.TimeUnit;
4 
5 public class ThreadPoolExecutorDemo {
6
7  private static final int CORE_POOL_SIZE = 5;
8  private static final int MAX_POOL_SIZE = 10; 
9  private static final int QUEUE_CAPACITY = 100;
10  private static final Long KEEP_ALIVE_TIME = 1L; 
11  public static void main(String[] args) {
12
13  //使用阿里巴巴推荐的创建线程池的方式
14  //通过ThreadPoolExecutor构造函数自定义参数创建 
15  ThreadPoolExecutor executor = new ThreadPoolExecutor( 
16  CORE_POOL_SIZE, 
17  MAX_POOL_SIZE, 
18  KEEP_ALIVE_TIME, 
19  TimeUnit.SECONDS,
20  new ArrayBlockingQueue<>(QUEUE_CAPACITY), 
21  new ThreadPoolExecutor.CallerRunsPolicy());
22
23  for (int i = 0; i < 10; i++) { 
24  //创建WorkerThread对象(WorkerThread类实现了Runnable 接口)
25  Runnable worker = new MyRunnable("" + i); 
26  //执行Runnable 
27  executor.execute(worker);
28  } 
29  //终止线程池 
30  executor.shutdown(); 
31  while (!executor.isTerminated()) { 
32  } 
33  System.out.println("Finished all threads");
34  }
35 } 

上記のコードでは次のことが指定されていることがわかります。

  1. corePoolSize: コア スレッドの数は 5 です。

  2. MaximumPoolSize: 最大スレッド数 10

  3. keepAliveTime : 待ち時間は 1L です。

  4. Unit: 待ち時間の単位は TimeUnit.SECONDS です。

  5. workQueue: タスク キューは、容量 100 の ArrayBlockingQueue です。

ハンドラー: 飽和ポリシーは CallerRunsPolicy です。出力:

1	pool‐1‐thread‐2 Start. Time = Tue Nov 12 20:59:44 CST 2019
2	pool‐1‐thread‐5 Start. Time = Tue Nov 12 20:59:44 CST 2019
3	pool‐1‐thread‐4 Start. Time = Tue Nov 12 20:59:44 CST 2019
4	pool‐1‐thread‐1 Start. Time = Tue Nov 12 20:59:44 CST 2019
5	pool‐1‐thread‐3 Start. Time = Tue Nov 12 20:59:44 CST 2019
6	pool‐1‐thread‐5 End. Time = Tue Nov 12 20:59:49 CST 2019
7	pool‐1‐thread‐3 End. Time = Tue Nov 12 20:59:49 CST 2019
8	pool‐1‐thread‐2 End. Time = Tue Nov 12 20:59:49 CST 2019
9	pool‐1‐thread‐4 End. Time = Tue Nov 12 20:59:49 CST 2019
10	pool‐1‐thread‐1 End. Time = Tue Nov 12 20:59:49 CST 2019
11	pool‐1‐thread‐2 Start. Time = Tue Nov 12 20:59:49 CST 2019
12	pool‐1‐thread‐1 Start. Time = Tue Nov 12 20:59:49 CST 2019
13	pool‐1‐thread‐4 Start. Time = Tue Nov 12 20:59:49 CST 2019
14	pool‐1‐thread‐3 Start. Time = Tue Nov 12 20:59:49 CST 2019
15	pool‐1‐thread‐5 Start. Time = Tue Nov 12 20:59:49 CST 2019
16	pool‐1‐thread‐2 End. Time = Tue Nov 12 20:59:54 CST 2019
17	pool‐1‐thread‐3 End. Time = Tue Nov 12 20:59:54 CST 2019
18	pool‐1‐thread‐4 End. Time = Tue Nov 12 20:59:54 CST 2019
19	pool‐1‐thread‐5 End. Time = Tue Nov 12 20:59:54 CST 2019
20	pool‐1‐thread‐1 End. Time = Tue Nov 12 20:59:54 CST 2019

スレッドプールのScheduledThreadPoolExecutorの詳細説明 FutureTaskのアトミックオペレーションクラスの詳細説明

アトミック操作とは何ですか? Java Concurrency API のアトミック クラスとは何ですか?

アトミック操作とは、「中断できない操作または一連の操作」を意味します。

プロセッサは、キャッシュのロックまたはバスのロックに基づく方法を使用して、複数のプロセッサ間のアトミック操作を実現します。

Java では、ロックと循環 CAS を通じてアトミック操作を実現できます。CAS操作——

比較と設定、または比較と交換、ほぼすべての CPU 命令がサポートされるようになりました。

CAS のアトミック操作。

アトミック操作は、他の操作の影響を受けない操作タスクの単位です。マルチスレッド環境でのデータの不整合を回避するには、アトミック操作が必要です。

int++并不是一个原子操作,所以当一个线程读取它的值并加 1 时,另外一个线程有可能会读到之前的值,这就会引发错误。

为了解决这个问题,必须保证增加操作是原子的,在 JDK1.5 之前我们可以使用

同步技术来做到这一点。到 JDK1.5,java.util.concurrent.atomic 包提供了 int 和long 类型的原子包装类,它们可以自动的保证对于他们的操作是原子的并且不需要使用同步。

java.util.concurrent 这个包里面提供了一组原子类。其基本的特性就是在多线程环境下,当有多个线程同时执行这些类的实例包含的方法时,具有排他性,即当某个线程进入方法,执行其中的指令时,不会被其他线程打断,而别的线程就像自旋锁一样,一直等到该方法执行完成,才由 JVM 从等待队列中选择另一个线程进入,这只是一种逻辑上的理解。

原子类:AtomicBoolean,AtomicInteger,AtomicLong,

AtomicReference

原子数组:AtomicIntegerArray,AtomicLongArray,

AtomicReferenceArray

原子属性更新器:AtomicLongFieldUpdater,AtomicIntegerFieldUpdater,

AtomicReferenceFieldUpdater

解决 ABA 问题的原子类:AtomicMarkableReference(通过引入一个

boolean来反映中间有没有变过),AtomicStampedReference(通过引入一个 int 来累加来反映中间有没有变过)说一下 atomic 的原理?

Atomic包中的类基本的特性就是在多线程环境下,当有多个线程同时对单个

(包括基本类型及引用类型)变量进行操作时,具有排他性,即当多个线程同时对该变量的值进行更新时,仅有一个线程能成功,而未成功的线程可以向自旋锁一样,继续尝试,一直等到执行成功。

AtomicInteger 类的部分源码:

1	// setup to use Unsafe.compareAndSwapInt for updates(更新操作时提供“比较并替换”的作用)
2	private static final Unsafe unsafe = Unsafe.getUnsafe(); 3 private static final long valueOffset;
4
5	static {
6	try {
7	valueOffset = unsafe.objectFieldOffset
8	(AtomicInteger.class.getDeclaredField("value"));
9	} catch (Exception ex) { throw new Error(ex); }
10	}
11
12 private volatile int value;

AtomicInteger クラスは主に CAS (比較およびスワップ) + 揮発性およびネイティブ メソッドを使用してアトミックな操作を保証するため、同期による高いオーバーヘッドを回避し、実行効率を大幅に向上させます。CAS の原理は、期待値を元の値と比較し、同じであれば新しい値に更新することです。

UnSafe クラスの objectFieldOffset() メソッドはローカル メソッドであり、「元の値」のメモリ アドレスを取得するために使用され、戻り値は valueOffset です。さらに付加価値は、

揮発性変数はメモリ内に表示されるため、JVM はどのスレッドがいつでも変数の新しい値を取得できることを保証できます。

同時実行ツール

同時実行ツールの CountDownLatch と CyclicBarrier

Java における CycliBarriar と CountdownLatch の違いは何ですか?

CountDownLatch と CyclicBarrier は両方とも同時実行性を制御するために使用されるツール クラスであり、どちらもカウンターを維持するものとして理解できますが、この 2 つは依然として異なる重点を持っています。

  • CountDownLatch は通常、特定のスレッド A がタスクを実行する前に他のいくつかのスレッドが実行されるのを待機するために使用されます。一方、CyclicBarrier は通常、スレッドのグループが互いに特定の状態になるのを待機し、その後、このスレッドのグループが次の時点で実行されるようにするために使用されます。同時に; CountDownLatch はスレッドを強調します 複数のスレッドが何かを完了するのを待ちます。CyclicBarrier は複数のスレッドが互いに同等であり、全員が終了した後、それらは一緒に動作します。
  • CountDownLatch の countDown メソッドを呼び出した後、現在のスレッドはブロックされずに実行を継続しますが、CyclicBarrier の await メソッドを呼び出すと、CyclicBarrier で指定されたすべてのスレッドが指定されたポイントに到達するまで現在のスレッドがブロックされてから続行されます。
  • CountDownLatch メソッドは比較的小さく、操作は比較的単純ですが、CyclicBarrier には getNumberWaiting()、isBroken() などのメソッドが多数用意されています。これらのメソッドは、複数の現在のスレッドのステータスを取得でき、CyclicBarrier の構築メソッドを次のメソッドに渡すことができます。スレッドがすべて到着したときにビジネス関数が実行されるタイミングを指定する、barrierAction。
  • CountDownLatch は再利用できませんが、CyclicLatch は再利用できます。

同時実行ツールのセマフォとエクスチェンジャー

セマフォは何をするのですか?

セマフォはセマフォであり、その機能は特定のコード ブロックの同時実行数を制限することです。

セマフォには、整数 n を渡すことができるコンストラクターがあり、特定のコード部分には最大で n 個のスレッドのみがアクセスできることを示します。n を超える場合は、特定のスレッドがこのコード ブロックの実行を終了するまで待機してください。次のスレッド 再入力してください。このことから、セマフォ コンストラクターで int 整数 n=1 が渡された場合、同期されることと同等であることがわかります。

セマフォ (セマフォ) - 複数のスレッドが同時にアクセスできるようにします: 同期および

ReentrantLock では、一度に 1 つのスレッドのみが特定のリソースにアクセスできますが、Semaphore (セマフォ) では、複数のスレッドが特定のリソースに同時にアクセスすることを指定できます。

スレッド間でデータを交換するツール Exchanger とは

Exchanger は、スレッド間のコラボレーションのためのツール クラスであり、2 つのスレッド間でデータを交換するために使用されます。それは提供します

交換同期ポイントが確立され、2 つのスレッドがデータを交換できるようになります。データの交換は、交換メソッドによって行われます。一方のスレッドが交換メソッドを最初に実行すると、もう一方のスレッドも交換メソッドを実行するのを待ちます。この時点で、両方のスレッドが同期ポイントに到達しており、2 つのスレッドは交換できるようになります。データです。

一般的に使用される同時実行ツールのクラスは何ですか?

  • セマフォ (セマフォ) - 複数のスレッドが同時にアクセスできるようにします: synchronized と ReentrantLock の両方で、一度に 1 つのスレッドのみが特定のリソースにアクセスできます。また、セマフォ (セマフォ) は、特定のリソースに同時にアクセスする複数のスレッドを指定できます。 。
  • CountDownLatch (カウントダウン タイマー): CountDownLatch は、複数のスレッド間の同期を調整するために使用される同期ツール クラスです。このツールは通常、スレッドの待機を制御するために使用され、カウントダウンが終了するまで特定のスレッドを待機させてから実行を開始できます。
  • CyclicBarrier (サイクル フェンス): CyclicBarrier は CountDownLatch に非常に似ており、スレッド間の技術的な待機も実装できますが、その機能は CountDownLatch よりも複雑で強力です。主なアプリケーション シナリオは CountDownLatch に似ています。CyclicBarrier は文字通り、リサイクル可能な (Cyclic) バリア (Barrier) を意味します。しなければならないことは、スレッドのグループがバリア (同期ポイントとも呼ばれます) に到達したときにブロックすることです。バリアは、次のスレッドがバリアに到達するまで開かず、バリアによってブロックされたすべてのスレッドは動作し続けます。 。CyclicBarrier のデフォルトの構築メソッドは CyclicBarrier(int party) で、そのパラメータはバリアによってインターセプトされたスレッドの数を示し、各スレッドは await() メソッドを呼び出して通知します。
  • CyclicBarrier バリアに到達すると、現在のスレッドがブロックされます。

ile 変数はメモリ内で可視であるため、JVM はどのスレッドがいつでも変数の新しい値を取得できることを保証できます。

同時実行ツール

同時実行ツールの CountDownLatch と CyclicBarrier

Java における CycliBarriar と CountdownLatch の違いは何ですか?

CountDownLatch と CyclicBarrier は両方とも同時実行性を制御するために使用されるツール クラスであり、どちらもカウンターを維持するものとして理解できますが、この 2 つは依然として異なる重点を持っています。

  • CountDownLatch は通常、特定のスレッド A がタスクを実行する前に他のいくつかのスレッドが実行されるのを待機するために使用されます。一方、CyclicBarrier は通常、スレッドのグループが互いに特定の状態になるのを待機し、その後、このスレッドのグループが次の時点で実行されるようにするために使用されます。同時に; CountDownLatch はスレッドを強調します 複数のスレッドが何かを完了するのを待ちます。CyclicBarrier は複数のスレッドが互いに同等であり、全員が終了した後、それらは一緒に動作します。
  • CountDownLatch の countDown メソッドを呼び出した後、現在のスレッドはブロックされずに実行を継続しますが、CyclicBarrier の await メソッドを呼び出すと、CyclicBarrier で指定されたすべてのスレッドが指定されたポイントに到達するまで現在のスレッドがブロックされてから続行されます。
  • CountDownLatch メソッドは比較的小さく、操作は比較的単純ですが、CyclicBarrier には getNumberWaiting()、isBroken() などのメソッドが多数用意されています。これらのメソッドは、複数の現在のスレッドのステータスを取得でき、CyclicBarrier の構築メソッドを次のメソッドに渡すことができます。スレッドがすべて到着したときにビジネス関数が実行されるタイミングを指定する、barrierAction。
  • CountDownLatch は再利用できませんが、CyclicLatch は再利用できます。

同時実行ツールのセマフォとエクスチェンジャー

セマフォは何をするのですか?

セマフォはセマフォであり、その機能は特定のコード ブロックの同時実行数を制限することです。

セマフォには、整数 n を渡すことができるコンストラクターがあり、特定のコード部分には最大で n 個のスレッドのみがアクセスできることを示します。n を超える場合は、特定のスレッドがこのコード ブロックの実行を終了するまで待機してください。次のスレッド 再入力してください。このことから、セマフォ コンストラクターで int 整数 n=1 が渡された場合、同期されることと同等であることがわかります。

セマフォ (セマフォ) - 複数のスレッドが同時にアクセスできるようにします: 同期および

ReentrantLock では、一度に 1 つのスレッドのみが特定のリソースにアクセスできますが、Semaphore (セマフォ) では、複数のスレッドが特定のリソースに同時にアクセスすることを指定できます。

スレッド間でデータを交換するツール Exchanger とは

Exchanger は、スレッド間のコラボレーションのためのツール クラスであり、2 つのスレッド間でデータを交換するために使用されます。それは提供します

交換同期ポイントが確立され、2 つのスレッドがデータを交換できるようになります。データの交換は、交換メソッドによって行われます。一方のスレッドが交換メソッドを最初に実行すると、もう一方のスレッドも交換メソッドを実行するのを待ちます。この時点で、両方のスレッドが同期ポイントに到達しており、2 つのスレッドは交換できるようになります。データです。

一般的に使用される同時実行ツールのクラスは何ですか?

  • セマフォ (セマフォ) - 複数のスレッドが同時にアクセスできるようにします: synchronized と ReentrantLock の両方で、一度に 1 つのスレッドのみが特定のリソースにアクセスできます。また、セマフォ (セマフォ) は、特定のリソースに同時にアクセスする複数のスレッドを指定できます。 。
  • CountDownLatch (カウントダウン タイマー): CountDownLatch は、複数のスレッド間の同期を調整するために使用される同期ツール クラスです。このツールは通常、スレッドの待機を制御するために使用され、カウントダウンが終了するまで特定のスレッドを待機させてから実行を開始できます。
  • CyclicBarrier (サイクル フェンス): CyclicBarrier は CountDownLatch に非常に似ており、スレッド間の技術的な待機も実装できますが、その機能は CountDownLatch よりも複雑で強力です。主なアプリケーション シナリオは CountDownLatch に似ています。CyclicBarrier は文字通り、リサイクル可能な (Cyclic) バリア (Barrier) を意味します。しなければならないことは、スレッドのグループがバリア (同期ポイントとも呼ばれます) に到達したときにブロックすることです。バリアは、次のスレッドがバリアに到達するまで開かず、バリアによってブロックされたすべてのスレッドは動作し続けます。 。CyclicBarrier のデフォルトの構築メソッドは CyclicBarrier(int party) で、そのパラメータはバリアによってインターセプトされたスレッドの数を示し、各スレッドは await() メソッドを呼び出して通知します。
  • CyclicBarrier バリアに到達すると、現在のスレッドがブロックされます。

並行練習

おすすめ

転載: blog.csdn.net/leader_song/article/details/132391098