Java マルチスレッド カテゴリには 21 のマルチスレッド記事が書かれていますが、個人的には、内容が多く、より複雑な知識を学ぶほど、より深い要約を作成する必要があると思います。深く覚えて、知識を自分のものにできるように。この記事は主にマルチスレッドの問題をまとめているため、マルチスレッドの問題が 40 個リストされています。
これらのマルチスレッドの問題の中には、主要な Web サイトからのものもあれば、私自身の考えから来ているものもあります。オンライン上にはいくつかの質問があり、いくつかの質問に対する答えがあり、ネットユーザー全員が読んだことがあるものもあるかもしれませんが、この記事を書くことの焦点は、自分の理解に従ってすべての質問に答えることであり、見る必要はありませんオンラインでの回答なので、質問の一部が間違っている可能性があります。修正していただければ幸いです。
1. マルチスレッドの用途は何ですか?
多くの人にとってはナンセンスに思えるかもしれない質問です。マルチスレッドを使用できる限り、それは何に役立ちますか?私の意見では、この答えはさらにナンセンスです。いわゆる「それがどのようにあるかを知り、それがなぜであるかを知る」、「それの使い方を知る」は単に「それがどのようにであるかを知る」、「なぜそれを使うのか」は「それがどのようにであるかを知る」に到達したときにのみです。 「それがどのようになっているのか、なぜそうなのかを知っている」というレベルは、「それがどのようになっているのかを知っている」と言えるでしょうか。 知識ポイントは自由に使用できます。さて、この問題についての私の意見を話しましょう:
(1) マルチコアCPUを活用する
業界の進歩により、今日のノートブック、デスクトップ、さらには商用アプリケーション サーバーでも、シングル スレッド プログラムであれば少なくとも 4 コア、8 コア、さらには 16 コアが搭載されることも珍しくありません。デュアルコア CPU では 50% が無駄になり、4 コア CPU では 75% が無駄になります。シングルコア CPU のいわゆる「マルチスレッド」は、偽のマルチスレッドです。プロセッサは同時にロジックの一部を処理するだけですが、スレッド間の切り替えが高速になり、複数のスレッドが実行されているように見えます。 「同時に」実行します。マルチコア CPU でのマルチスレッドは、複数のロジックを同時に動作させることができ、マルチコア CPU の利点を最大限に活用することができます。 CPUの使用。
(2) ブロックを防ぐ
プログラムの実行効率の観点から見ると、シングルコア CPU ではマルチスレッドを利用できないだけでなく、プログラム全体の効率も低下します。これは、シングルコア CPU でマルチスレッドを実行するとスレッド コンテキストが発生するためです。切り替え。ただし、シングルコア CPU の場合は、ブロッキングを防ぐためにマルチスレッドを使用する必要があります。想像してみてください。シングルコア CPU が単一のスレッドを使用する場合、このスレッドがブロックされている限り (たとえば、特定のデータをリモートで読み取る場合)、ピアがまだ戻っておらず、タイムアウトを設定していない限り、プログラム全体がデータが返される前にブロックされ、実行が停止されます。マルチスレッドを使用すると、複数のスレッドが同時に実行されるため、データの読み取り中に 1 つのスレッドのコードの実行がブロックされても、他のタスクの実行には影響しません。
(3) モデル化が簡単
これもあまり目立たない利点です。シングルスレッドプログラミングという大きなタスクAがあるとすると、考慮すべきことがたくさんあり、プログラム全体のモデルを構築するのは面倒です。しかし、この大きなタスク A をいくつかの小さなタスク (タスク B、タスク C、タスク D) に分割し、それぞれプログラム モデルを確立し、これらのタスクをマルチスレッドで個別に実行すると、はるかに簡単になります。
2. スレッドの作成方法
比較的よくある質問ですが、一般に次の 2 つのタイプがあります:
(1) Threadクラスを継承
(2) Runnableインターフェースを実装します
どちらが良いかというと、インターフェースを実装する方法はクラスを継承する方法よりも柔軟であり、プログラム間の結合も減らすことができるため、後者の方が良いことは言うまでもありません。インターフェース指向プログラミングも核となります。デザインパターンの6つの原則のうち。
3. start() メソッドと run() メソッドの違い
start() メソッドが呼び出された場合にのみマルチスレッド機能が表示され、異なるスレッドの run() メソッド内のコードが交互に実行されます。 run() メソッドを呼び出すだけの場合でも、コードは同期的に実行されます。あるスレッドの run() メソッド内のコードがすべて実行されるまで、別のスレッドが run() メソッド内のコードを実行できるようにする必要があります。
4. RunnableインターフェースとCallableインターフェースの違い
これは少し深い質問であり、Java プログラマーが取得できる知識の広さを示しています。
Runnable インターフェイスの run() メソッドの戻り値は void であり、単に run() メソッドのコードを実行するだけです。Callable インターフェイスの call() メソッドは戻り値を持ち、ジェネリックです。 、Future および FutureTask と組み合わせて使用して、非同期実行の結果を取得できます。
これは実際には非常に便利な機能です。なぜなら、マルチスレッドがシングルスレッドよりも困難で複雑である重要な理由は、マルチスレッドが未知の要素でいっぱいだからです。スレッドの実行時間はどのくらいですか?スレッドが実行されるとき、予期したデータは割り当てられていますか?それを知る方法はありません。私たちにできることは、このマルチスレッド タスクが完了するのを待つことだけです。 Callable+Future/FutureTask はマルチスレッド操作の結果を取得したり、待ち時間が長すぎて必要なデータが取得できなかった場合にスレッドのタスクをキャンセルしたりすることができ、非常に便利です。
5. CyclicBarrier と CountDownLatch の違い
どちらも java.util.concurrent の下にある、少し似ている 2 つのクラスは、コードが特定のポイントまで実行されることを示すために使用できます。 2 つのクラスの違いは次のとおりです。 (1) CyclicBarrier のスレッドが特定の時点まで実行されると、スレッドは実行を停止します。すべてのスレッドがこの時点に到達するまでは、すべてのスレッドが再実行されません。その後、スレッドは特定の時点まで実行されます。特定の値 -1 の場合、スレッドは実行を継続します
(2) CyclicBarrier は 1 つのタスクのみを呼び出すことができますが、CountDownLatch は複数のタスクを呼び出すことができます
(3) CyclicBarrierは再利用可能ですが、CountDownLatchは再利用できません カウント値が0の場合、CountDownLatchは再利用できません
。
6. 揮発性キーワードの役割
マルチスレッドを学習して適用するすべての Java プログラマーが習得する必要がある非常に重要な問題です。 volatile キーワードの役割を理解するための前提条件は、Java メモリ モデルを理解することです。ここでは Java メモリ モデルについては説明しません。ポイント 31 を参照してください。 volatile キーワードには 2 つの主な機能があります。 (1) マルチスレッドは主に、可視性とアトミック性という 2 つの特性を中心に展開しており、volatile キーワードで変更された変数は、マルチスレッド間での可視性を保証します。つまり、volatile 変数が読み取られるたびに、それは最新のデータである必要があります
。 (2) コードの基本的な実行は、私たちが目にする高級言語ほど単純ではありません。Java プログラムの実行は、Java コード -> バイトコード -> バイトコードに従って対応する C/C++ を実行します。 -> C/C++ コードはアセンブリ言語にコンパイルされます --> 実際には、パフォーマンスを向上させるために、JVM が命令を並べ替えることがあり、マルチスレッドでは予期しない問題が発生する可能性があります。 。 volatile を使用すると、禁止セマンティクスの順序が変更され、当然、コードの実行効率もある程度低下します
実用的な観点から見ると、volatile の重要な役割は、CAS と組み合わせてアトミック性を確保することです。詳細については、AtomicInteger などの java.util.concurrent.atomic パッケージ内のクラスを参照してください。
7. スレッドセーフとは何ですか
これも理論的な質問であり、さまざまな答えがありますが、私が最もよく説明していると思われる答えを紹介します。コードが複数のスレッドで実行された場合でも、単一のスレッドで実行された場合でも、常に同じ結果が得られます。そうすれば、コードはスレッドセーフになります。 この質問について言及する価値があるのは、スレッドの安全性にはいくつかのレベルがあるということです。
(1) 不変
String、Integer、Long などのクラスはすべて最終型であり、新しいものを作成しない限り、スレッドは値を変更できません。したがって、これらの不変オブジェクトは、同期手段を使用せずにマルチスレッド環境で直接使用できます。
(2) 絶対にスレッドセーフ 実行時環境に関係なく、呼び出し元は追加の同期手段を必要としません。これを実現するには、通常、多額の追加コストを支払う必要があります。スレッド セーフとしてマークされている Java クラスのほとんどは、実際にはスレッド セーフではありません。ただし、次のような完全にスレッド セーフなクラスもあります。 CopyOnWriteArrayList と CopyOnWriteArraySet (3) 比較的スレッドセーフ 相対的なスレッド セーフティは、Vector と同様にアトミックな操作であり、中断されませんが、スレッドが Vector をトラバースしている場合、これを追加するスレッドがある場合に限定されます。 Vector では同時に、99% のケースで ConcurrentModificationException が発生します。これはフェイルファスト メカニズムです。 (4) スレッドは安全ではありません これについてはあまり言うことはありません。ArrayList、LinkedList、HashMap などはすべてスレッド非安全なクラスです。8. Javaでスレッドダンプファイルを取得する方法
無限ループ、デッドロック、ブロッキング、ページを開くのが遅いなどの問題の場合、スレッド ダンプが問題を解決する最良の方法です。いわゆるスレッド ダンプはスレッド スタックです。スレッド スタックを取得するには、次の 2 つの手順があります。 (1) スレッドの pid を取得するには、jps コマンドを使用します。Linux 環境では、ps -ef grep javaを使用することもできます。 (2) スレッドスタックを出力するには、jstack pid コマンドを使用できます。Linux 環境では、kill -3 pid を使用することもできます
。 さらに、Thread クラスは、スレッド スタックの取得にも使用できる getStackTrace() メソッドを提供します。これはインスタンス メソッドであるため、このメソッドは特定のスレッド インスタンスにバインドされ、取得するたびに、特定のスレッド によって現在実行されているスタックを取得します。9. スレッドで実行時例外が発生するとどうなるか
この例外がキャッチされない場合、スレッドは実行を停止します。もう 1 つの重要な点は、このスレッドが特定のオブジェクトのモニターを保持している場合、そのオブジェクトのモニターはすぐに解放されるということです
10. 2 つのスレッド間でデータを共有する方法
スレッド間でオブジェクトを共有し、wait/notify/notifyAll、await/signal/signalAll を通じて起動して待機するだけです。たとえば、ブロッキング キュー BlockingQueue は、スレッド間でデータを共有するように設計されています
。
11. sleepメソッドとwaitメソッドの違いは何ですか
この質問はよく聞かれます。sleep メソッドと wait メソッドは両方とも、一定期間 CPU を放棄するために使用できます。違いは、スレッドがオブジェクトのモニターを保持している場合、sleep メソッドは放棄しないことです。このオブジェクトのモニター、そして wait メソッドはオブジェクトモニターを放棄します
12. 生産者・消費者モデルの役割とは何ですか
この質問は非常に理論的ですが、非常に重要です:
(1)生産者の生産能力と消費者の消費能力のバランスをとり、システム全体の経営効率を向上させることが、生産者・消費者モデルの最も重要な役割です
。 (2) デカップリング、これは生産者と消費者モデルの付随的な機能です。デカップリングとは、生産者と消費者の間の接続が少なくなり、相互の制約なしに独立して発展できることを意味します
。
13. ThreadLocal の用途は何ですか
簡単に言うと、ThreadLocal は空間と時間を交換するメソッドであり、各スレッドはオープン アドレス メソッドによって実装された ThreadLocal.ThreadLocalMap を維持します。これにより、データが分離され、当然、スレッドの安全性の問題は発生しません
。
14. wait()メソッドとnotify()/notifyAll()メソッドが同期ブロックで呼び出される理由
これは JDK によって必須です。wait() メソッドと notify()/notifyAll() メソッドの両方は、
を呼び出す前にオブジェクトのロックを取得する必要があります。
15. オブジェクトモニターを放棄する場合のwait()メソッドとnotify()/notifyAll()メソッドの違いは何ですか
オブジェクト モニターを放棄する場合の wait() メソッドと notify()/notifyAll() メソッドの違いは、wait() メソッドはオブジェクト モニターをすぐに解放しますが、notify()/notifyAll() メソッドはオブジェクト モニターの解放を待機することです。スレッドの残りのコードが実行を完了すると、オブジェクト モニターが放棄されます。
16. スレッドプールを使用する理由
スレッド オブジェクトを再利用するために、頻繁にスレッドを作成および破棄することは避けてください。また、スレッドプールを利用することで、プロジェクトに応じて柔軟に同時実行数を制御することも可能です。
17. スレッドがオブジェクトモニターを保持しているかどうかを検出する方法
また、インターネットでマルチスレッドに関するインタビューの質問を見て、スレッドがオブジェクト モニターを保持しているかどうかを判断する方法があることを知りました。 Thread クラスは、オブジェクト obj のモニターを保持している場合に限り、holdsLock(Object obj) メソッドを提供します。これは、「特定のスレッド」が現在のスレッドを指すことを意味する静的メソッドであることに注意してください。
18. 同期ロックとリエントラントロックの違い
synchronized は if、else、for、while と同じキーワードであり、ReentrantLock はクラスです。これが 2 つの本質的な違いです。 ReentrantLock はクラスであるため、継承でき、メソッドを持つことができ、さまざまなクラス変数を持つことができ、同期と比較した場合のスケーラビリティはいくつかの点に反映されています。 (1) ReentrantLockでロック取得の待ち時間を設定できるのでデッドロックを回避できます
(2) ReentrantLockは様々なロックの情報を取得できます
(3) ReentrantLockは複数の通知を柔軟に実装可能
さらに、この 2 つのロック機構は実は異なります。 ReentrantLock の最下層は Unsafe の park メソッドを呼び出してロックし、synchronized はオブジェクト ヘッダーのマーク ワードで動作する必要があります。これについてはわかりません。
19. ConcurrentHashMapの同時実行性とは何ですか
ConcurrentHashMap の同時実行性はセグメントのサイズであり、デフォルトは 16 です。これは、Hashtable に対する ConcurrentHashMap の最大の利点でもあります。同時にスレッドを実行してハッシュテーブルのデータを取得しますか?
20. リードライトロックとは
まず最初に明確にしておきたいのは、ReentrantLock が悪いということではなく、ReentrantLock には場合によっては制限があるというだけです。 ReentrantLock を使用する場合、スレッド A がデータを書き込み、スレッド B がデータを読み取ることによって生じるデータの不整合を防ぐためである可能性があります。ただし、スレッド C がデータを読み取り、スレッド D もデータを読み取っている場合、データを読み取ってもデータは変更されません。ロックは必要ありませんが、ロックされたままなので、プログラムのパフォーマンスが低下します。
このため、読み取り/書き込みロック ReadWriteLock が誕生しました。 ReadWriteLock は読み取り/書き込みロック インターフェイスであり、読み取りと書き込みの分離を実現します。読み取りと読み取りの間には相互排他がありません。読み取りと書き込み、書き込みと読み取り、書き込みと書き込みは相互に排他的であるため、読み取りと書き込みのパフォーマンスが向上します。
21. フューチャータスクとは
実際、前述したように、FutureTask は非同期操作タスクを表します。 FutureTaskにはCallableの特定の実装クラスを渡すことができ、この非同期動作タスクの結果取得待ちや完了判定、タスクのキャンセルなどの操作を行うことができます。もちろん、FutureTaskもRunnableインターフェースの実装クラスであるため、FutureTaskをスレッドプールに配置することも可能です。
22. Linux環境で最も長くCPUを使用しているスレッドを見つける方法
これはより実践的な質問であり、非常に意味のある質問だと思います。これを行うことができます:
(1) プロジェクトの PID を取得します (jps または ps -ef grep java)。これについては前に説明しました
(2) top -H -p pid、順序は変更できません
これにより、現在のプロジェクトと各スレッドが費やした CPU 時間の割合が出力されます。ここで入力されているのは LWP であり、これはオペレーティング システムのネイティブ スレッドのスレッド番号であることに注意してください。私のラップトップは Linux 環境に Java プロジェクトをデプロイしていないため、ネチズンの場合はデモ用のスクリーンショットを撮る方法がありません。会社では Linux 環境を使用してプロジェクトを展開しているため、一度試してみることができます。
「top -H -p pid」+「jps pid」を使用すると、大量の CPU を占有しているスレッドのスレッド スタックを簡単に見つけることができ、それによって CPU 占有率が高い原因を突き止めることができます。これは通常、不適切なコード操作によるものです。それは無限ループにつながります。
最後にもう 1 つ言及しておきます。「top -H -p pid」で出力される LWP は 10 進数で、「jps pid」で出力されるローカル スレッド番号は 16 進数で表示されます。これを変換することで、使用するスレッドを見つけることができます。現在のスレッドがスタックされています。
23. デッドロックを引き起こすプログラムを書く Java プログラミング
初めてこのトピックを見たとき、とても良い質問だと思いました。デッドロックが何であるかは多くの人が知っています。スレッド A とスレッド B が互いのロックを待機し、プログラムが無限にループすることになります。もちろんこれに限るのですが、デッドロックのプログラムをどうやって書けばいいのかというと、理論を理解しているだけで終わってしまいます。実際には、デッドロックの問題は基本的に目に見えません。
デッドロックとは何かを本当に理解するには、この問題は実際には難しいものではなく、ほんの数ステップで完了します:
(1) 2 つのスレッドは、lock1 と lock2 という 2 つの Object オブジェクトを保持します。これら 2 つのロックは、同期されたコード ブロックのロックとして機能します。
(2) スレッド 1 の run() メソッド内の同期コード ブロックは、最初に lock1 のオブジェクト ロック、Thread.sleep(xxx) を取得します。時間はあまり長くする必要はなく、50 ミリ秒程度あれば十分です。 lock2 のオブジェクト ロック。これを行う主な目的は、スレッド 1 が lock1 オブジェクトと lock2 オブジェクトのオブジェクト ロックを一度に継続的に取得するのを防ぐことです
(3) スレッド 2 の実行) (メソッド内の同期コード ブロックは、最初に lock2 のオブジェクト ロックを取得し、次に lock1 のオブジェクト ロックを取得します。もちろん、このとき、lock1 のオブジェクト ロックはスレッド 1 によってロックされています。 、スレッド 2 はスレッド 1 のオブジェクト ロックを解放する必要があります
このようにして、スレッド 1 が「スリープ」した後、スレッド 2 がロック 2 のオブジェクト ロックを取得しようとしますが、この時点でデッドロックが形成されます。この記事には、上記の手順のコード実装である Java マルチスレッド 7: デッドロックが含まれているため、コードは書きません。
24. ブロックされたスレッドを起動する方法
wait()、sleep()、または join() メソッドの呼び出しによってスレッドがブロックされた場合、スレッドが IO ブロックに遭遇した場合は、InterruptedException をスローすることでスレッドを中断してウェイクアップできます。IO があるため、何もすることができません。はオペレーティング システムとして実装されているため、Java コードはオペレーティング システムに直接接続する方法がありません。
25. 不変オブジェクトはマルチスレッドにどのように役立ちますか
前に述べたように、不変オブジェクトはオブジェクトのメモリ可視性を確保するため、追加の同期手段を必要とせず、コードの実行効率が向上します。
26. マルチスレッドコンテキストスイッチとは
マルチスレッド コンテキストの切り替えとは、CPU 制御を、すでに実行中の 1 つのスレッドから、CPU 実行権の取得の準備ができて待機している別のスレッドに切り替えるプロセスを指します。
27. タスクを送信したときにスレッドプールのキューがいっぱいになった場合はどうなりますか
無制限のキューである LinkedBlockingQueue を使用している場合、LinkedBlockingQueue はほぼ無限のキューと見なされ、タスクを無限に保存できるため、ブロッキング キューにタスクを追加し続けても問題ありません。たとえば、ArrayBlockingQueue の場合、タスクは ArrayBlockingQueue に追加されます。ArrayBlockingQueue が満杯の場合、拒否ポリシー RejectedExecutionHandler が使用されて、満杯のタスクが処理されます。デフォルトは AbortPolicy です。
28. Javaで使用されるスレッドスケジューリングアルゴリズムとは何ですか
先制的。スレッドが CPU を使い果たすと、オペレーティング システムはスレッドの優先順位、スレッド ハングリーさ、その他のデータに基づいて合計の優先順位を計算し、実行のために特定のスレッドに次のタイム スライスを割り当てます。
29. Thread.sleep(0)の機能は何ですか
この質問は上記の質問と関連しているので、それらを結び付けました。 Java はプリエンプティブ スレッド スケジューリング アルゴリズムを使用するため、特定のスレッドが CPU 制御を頻繁に取得することがあり、優先順位の低い一部のスレッドが CPU 制御を取得できるようにするには、Thread.sleep(0) の操作を手動でトリガーします。これは、CPU 制御のバランスをとるための操作でもあります。
30. スピンとは
同期コードの多くは非常に単純なコードであり、スレッドのブロックにはユーザー モードとカーネル モードの切り替えが含まれるため、この時点で待機中のスレッドをすべてロックすることは価値のある操作ではない可能性があります。同期中のコードは非常に高速に実行されるため、ロックを待機しているスレッドがブロックされるのを防ぐ方が良いですが、同期境界でビジー ループを実行します。これがスピンです。複数のビジー ループを実行し、ロックが取得されていないことが判明した場合は、再度ブロックする方が良い戦略になる可能性があります。
31. Javaメモリモデルとは
Java メモリ モデルは、Java メモリへのマルチスレッド アクセスの仕様を定義します。 Java メモリ モデルの完全な説明は、数文で明確に説明することはできません。Java メモリ モデルのいくつかの部分を簡単に要約します。 (1) Java メモリ モデルでは、メモリがメイン メモリとワーキング メモリに分割されます。クラスの状態、つまりクラス間で共有される変数はメイン メモリに格納され、Java スレッドがメイン メモリ内のこれらの変数を使用するたびに、メイン メモリ内の変数を 1 回読み取って存在させます。独自のスレッド コードを実行するとき、これらの変数を使用すると、独自の作業メモリ内でコピーが操作されます。スレッドコードが実行されると、最新の値がメインメモリに更新されます
(2) メインメモリとワーキングメモリの変数を操作するためのアトミック操作がいくつか定義されています
(3) 揮発性変数の使用ルールを定義する
(4) happens-before、つまり前に発生する原則は、操作 A が操作 B の前に発生しなければならないといういくつかのルールを定義します。たとえば、同じスレッド内では、制御フローの前のコードは後ろのコードより前に発生する必要があります。制御フロー、ロックの解放 ロック解除アクションは、同じロックに対する後続のロック アクションよりも前に実行する必要があります。これらのルールが満たされている限り、コードがすべての条件を満たしていない場合、追加の同期措置は必要ありません。ルールの前、そしてこのコードはスレッドセーフでなければなりません
32. CASとは
CASとはCompare and Swapの略で、比較して置き換えるという意味です。メモリ値 V、古い期待値 A、および変更される値 B の 3 つのオペランドがあるとします。期待値 A とメモリ値 V が同じである場合に限り、メモリ値は B に変更され、true になります。それ以外の場合は、どちらも行わず false を返します。もちろん、CAS は、毎回取得される変数がメイン メモリ内の最新の値であることを保証するために、揮発性変数と連携する必要があります。そうでない場合、古い期待値 A は、あるスレッドに対して常に不変の値 A になります。 CAS 操作は失敗します。成功することはありません。
33. 楽観的ロックと悲観的ロックとは何ですか
(1) 楽観的ロック: その名前のように、楽観的ロックは、同時操作によって引き起こされるスレッドの安全性の問題について楽観的であるため、常に競合が発生するとは限らないため、ロックを保持する必要がなく、これらを比較して置き換えます。 2 原子的な操作として、このアクションはメモリ内の変数の変更を試みますが、失敗した場合は競合が発生していることを意味するため、対応する再試行ロジックが必要です。
(2) 悲観的ロック: その名前のように、同時操作によって引き起こされるスレッドの安全性の問題について悲観的です。悲観的ロックは常に競合が発生すると考えているため、リソースが操作されるたびに排他的ロックを保持します。何が起こっても、ロックした後はリソースを直接操作できます。
34.AQSとは
AQS について簡単に説明します。AQS の正式名は AbstractQueuedSychronizer で、抽象キュー シンクロナイザーと訳されます。
java.util.concurrent の基礎が CAS である場合、AQS は Java 同時実行パッケージ全体の中核となり、ReentrantLock、CountDownLatch、Semaphore などはすべてそれを使用します。 AQS は実際には、ReentrantLock などの双方向キューの形式ですべてのエントリを接続します。待機中のすべてのスレッドはエントリに配置され、双方向キューに接続されます。前のスレッドが ReentrantLock を使用している場合、実際には双方向キューが実行を開始します。
AQS は双方向キューに対するすべての操作を定義しますが、開発者は tryLock メソッドと tryRelease メソッドのみを公開し、独自の実装に従って tryLock メソッドと tryRelease メソッドを書き換えて、独自の同時実行機能を実現できます。
35. シングルトンモードのスレッドセーフ
よくある質問ですが、まず最初に言っておきたいのは、シングルトン モードのスレッド セーフとは、特定のクラスのインスタンスがマルチスレッド環境で 1 回だけ作成されることを意味するということです。シングルトン パターンを記述する方法はたくさんあります:
を要約します。 (1) Hungry-style シングルトンパターンの書き方: スレッドセーフ
(2) 遅延シングルトンパターンの書き方: 非スレッドセーフ
(3) ダブルチェックロック シングルトンモードの書き方: スレッドセーフ
36. セマフォの機能とは何ですか
セマフォはセマフォであり、その機能は特定のコード ブロックの同時実行数を制限することです。セマフォには整数 n を渡すことができるコンストラクターがあります。これは、特定のコード部分には最大でも n 個のスレッドしかアクセスできないことを意味します。n を超えた場合は、スレッドがこのコード ブロックの実行を終了するまで待機してください。スレッドを再入力します。このことから、Semaphore コンストラクタに int 型整数 n=1 を渡した場合は、同期化されたことと同等であることがわかります。
37. Hashtable の size() メソッドに「return count」というステートメントが 1 つだけあるのはなぜですか?
これは私が以前に感じた混乱でした。この問題について考えた人はいないでしょうか。メソッド内に複数のステートメントがあり、それらがすべて同じクラス変数で動作している場合、マルチスレッド環境でロックしないと必然的にスレッド セーフティの問題が発生します。これは理解するのが簡単ですが、size() メソッドには明らかに問題があります。たった 1 つのステートメントですが、なぜロックする必要があるのでしょうか?
この問題に関しては、仕事や勉強をしていくうちに少しずつ理解できるようになりましたが、その理由は大きく2つあります
。 (1) 固定クラスの同期メソッドは同時に実行できるスレッドは 1 つだけですが、クラスの非同期メソッドは複数のスレッドから同時にアクセスできます。したがって、問題が発生する可能性があります。スレッド A はデータを追加するために Hashtable の put メソッドを実行しており、スレッド B は通常どおりに size() メソッドを呼び出して Hashtable 内の現在の要素の数を読み取ることができますが、読み取られた値は正しくない可能性があります。最新。おそらくスレッド A がデータを追加しましたが、スレッド B は size++ を調整せずにすでにサイズを読み取っているため、スレッド B によって読み取られたサイズは不正確であるはずです。 size() メソッドに同期を追加すると、スレッド B はスレッド A が put メソッドの呼び出しを完了した後にのみ size() メソッドを呼び出すことができるようになり、スレッドの安全性が確保されます
(2) CPU は Java コードではなくコードを実行します。これは非常に重要なので覚えておく必要があります。 Java コードは、最終的に実行のためにアセンブリ コードに変換されます。アセンブリ コードは、実際にハードウェア回路と対話できるコードです。 Java コードが 1 行しかない場合でも、または Java コードのコンパイル後に生成されるバイトコードが 1 行だけである場合でも、このステートメントの最下位レベルの操作が 1 つだけであることを意味するわけではありません。 。文「return count」が実行のために 3 つのアセンブリ文に変換されると仮定すると、最初の文の実行後にスレッドが切り替わる可能性は十分にあります。
38. スレッドクラスのコンストラクターメソッドと静的ブロックと呼ばれるスレッドはどれですか
これは非常にトリッキーで狡猾な質問です。覚えておいてください。スレッド クラスの構築メソッドと静的ブロックは、新しいスレッド クラスが配置されているスレッドによって呼び出され、run メソッドのコードはスレッド自体によって呼び出されます。
上記のステートメントが混乱する場合は、Thread1 が Thread2 で新しく、Thread2 が main 関数で新しいものであると仮定します。
(1) Thread2の構築メソッドと静的ブロックはメインスレッドから呼び出され、Thread2のrun()メソッドはThread2自身から呼び出されます
(2) Thread1の構築メソッドと静的ブロックはThread2から呼び出され、Thread1のrun()メソッドはThread1自身から呼び出されます
39. 同期方法と同期ブロック、どちらが良い選択ですか
同期ブロック。同期ブロックの外側のコードが非同期で実行されることを意味します。これにより、メソッド全体を同期するよりもコードの効率が向上します。 1 つのルールを知っておいてください。同期の範囲は小さいほど良いということです。
これを念頭に置いて、同期の範囲は小さいほど良いのですが、Java 仮想マシンには同期の範囲を大きくするためのロック粗密化と呼ばれる最適化手法がまだ存在することを述べておきたいと思います。これは便利です。たとえば、StringBuffer はスレッドセーフなクラスです。当然のことながら、コードを記述するとき、最も一般的に使用される append() メソッドは、繰り返しロックを意味します。ロック解除は、Java 仮想マシンがこのスレッド上でカーネル モードとユーザー モードを繰り返し切り替える必要があるため、パフォーマンスに悪影響を及ぼします。そのため、Java 仮想マシンは複数の追加メソッドを呼び出すコードを処理することになり、ロック粗大化操作が延長されます。これにより、append メソッドの先頭と末尾に複数の追加操作が追加され、それが大きな同期ブロックに変換され、ロックとロック解除の回数が減り、コードの実行効率が効果的に向上します。
40. 同時実行性が高く、タスクの実行時間が短いビジネス向けにスレッド プールを使用する方法は?同時実行性が低く、タスクの実行時間が長いビジネス向けにスレッド プールを使用するにはどうすればよいですか?同時実行性が高く、ビジネス実行時間が長いビジネス向けにスレッド プールを使用するにはどうすればよいですか?
これは、同時プログラミング インターネットで見た質問です。この質問は非常に優れており、非常に実践的で、非常に専門的であるため、皆さんに見ていただき、考えていただければ幸いです。この問題に関して、私の個人的な意見は次のとおりです:
(1) 同時実行性が高く、タスクの実行時間が短いビジネスの場合、スレッド プールのスレッド数を CPU コアの数 + 1 に設定して、スレッド コンテキストの切り替えを減らすことができます
(2) 同時実行性が低く、タスクの実行時間が長いビジネスは区別する必要があります:
a) ビジネス時間が長時間にわたる IO 操作、つまり IO 集約型のタスクに集中している場合、IO 操作は CPU を占有しないため、すべての CPU をアイドル状態にせず、スレッド数を増やすことができます。スレッドプールにより、CPU がより多くの業務を処理できるようになります
b) 業務時間が長時間にわたって計算処理に集中している場合、つまり計算負荷の高いタスクの場合は、(1) と同じです。スレッドコンテキストの切り替えを減らすために、より小さく設定します
(3) 高い同時実行性と長いビジネス実行時間。この種のタスクを解決する鍵は、スレッド プールにあるのではなく、これらのビジネスの特定のデータをキャッシュできるかどうかを確認することが最初のステップです。サーバーの追加は 3 番目のステップです。スレッド プールの設定については、(2) の設定を参照してください。最後に、ビジネス実行時間が長いという問題も分析して、ミドルウェアを使用してタスクを分割および分離できるかどうかを確認する必要がある場合があります。
以上がJava マルチスレッドの問題 40 件の概要の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。