Java テクノロジーの仕上げ (3) - マルチスレッド同時実行

1. Javaスレッドの実装・作成方法

(1) Threadクラスを継承する

Thread クラスは本質的に、スレッドのインスタンスを表す Runnable インターフェイスを実装するインスタンスであり、開始によってメソッドをstart()自動的に実行しますrun()

(2) Runnableインターフェースの実装

Runnable は値を返さないスレッド タスク クラスで、Java には次の 2 つの方法で実装できます。

  • 1. カスタム スレッド クラスは Runnable インターフェイスを実装し、run メソッドをオーバーライドします。メイン プログラムでは、Thread クラス コンストラクターを使用してカスタム スレッドを渡し、デフォルトの Thread インスタンスをオーバーライドします。
  • 2. 匿名クラスを使用して Thread コンストラクターに匿名クラスを渡し、デフォルトの Thread インスタンスを上書きします。

(3) ExcutorService、呼び出し可能、戻り値のある将来のスレッド

  • 戻り値を使用してタスクを実行する必要がある場合は、Callable インターフェイスを実装する必要があります。
  • Callable タスクを実行した後、Future オブジェクトを取得し、結果が返されるのを待ち、getメソッドを使用して戻り値を取得できます。
  • ExcutorService を使用してスレッド プール実行タスクを実装し、戻り値を含むマルチスレッドを実装する

(4) スレッドプール

スレッド プールは、スレッドの作成と破棄によって引き起こされるシステム リソースの消費の問題を解決するために実装されたキャッシュ戦略です。

Java には 4 つのスレッド プールが用意されています。

Excutors の静的メソッドを使用して共通スレッド プーリングを実行します。その実際の最上位の親クラスは ExcutorService インターフェイスです。

  • newCachedThreadPool:必要なタスクの数に応じてスレッドを作成し、キャッシュ時間を設定します
    • execute が呼び出されるたびに、スレッド プールを調べて使用可能なスレッドがあるかどうかを確認します。使用可能なスレッドがない場合は、タスクを実行するために新しいスレッドが作成されます。
    • スレッドが 60 秒以上使用されなかった場合、スレッドはリサイクルされます。
  • newFixedThreadPool:固定数のスレッドを含むスレッド プールを作成し、これらのスレッドを無制限の共有キューで実行します。
    • すべてのスレッドがタスクによって占有されている場合、新しいタスクはタスク キュー内に残り、実行のスケジュールが設定されるのを待機します。
    • タスク例外によりスレッドが終了した場合、新しいスレッドが作成され、タスク キュー内のタスクの実行がスケジュールされます。
  • newScheduledThreadPool:タイミング機能を備えたスレッド プールを作成し、schedule()タスクの追加と実行サイクルの設定に使用します。
  • newSingleThreadExcutor:スレッドが 1 つだけあるスレッド プールを作成します。タスク例外によりスレッドが終了すると、内部で新しいスレッドが再作成され、古いスレッドを置き換えて新しいタスクを実行します。

2. スレッドのライフサイクル:

スレッドのライフサイクルには、新規、準備完了、実行中、ブロック、デッドの 5 つがあります。

  • 新しい状態 (New):プログラムが new キーワードを使用してスレッドを作成すると、スレッドは新しい状態になり、JVM はそのスレッドにメモリを割り当て、メンバー変数の値を初期化します。
  • 準備完了状態 (実行可能):スレッド オブジェクトが start() を呼び出すと、スレッドは準備完了状態になり、JVM はそのメソッド呼び出しスタックとプログラム カウンタを作成し、スケジューリング実行を待機します。
  • 実行状態 (Running):準備完了状態のスレッドが CPU を取得し、run() のスレッド メソッド本体の実行を開始します。
  • ブロック状態 (Blocked):スレッドは何らかの理由で CPU の使用権を放棄し、実行可能状態になるまで実行を一時停止します。ブロックされる状況は 3 つあります
    • 1. ブロック待ち:スレッド オブジェクトは wait() を実行し、JVM はスレッドを待機キュー (待機キュー) に入れます。
    • 2. 同期ブロック:実行中のスレッドが同期ロックを取得するときに、その同期ロックが他のスレッドによって占有されている場合、JVM はそのスレッドをロック プールに入れます。
    • 3. その他のブロッキング:実行状態のスレッドは sleep() または join() を実行します。IO リクエストが開始されると、JVM はスレッドをブロック状態にします。スリープ状態がタイムアウトになるか、join() が待機すると、終了またはタイムアウトするスレッド、または IO 処理が完了すると、スレッドは実行可能状態に戻ります。
  • スレッドの死亡:スレッドの死亡には 3 つの方法があります
    • 1. 走るか電話する。メソッドの実行が完了し、スレッドが正常に終了する
    • 2. スレッドがキャッチされなかった例外またはエラーをスローする
    • 3. スレッドオブジェクトの stop() を呼び出すとデッドロックが発生する可能性があります

3. スレッドを終了する

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

  • 1. 正常終了:プログラムが終了し、スレッドも自動的に終了します。
  • 2. 終了フラグを使用してスレッドを終了します。 run() が実行されると、スレッドは自動的に終了します。スレッドが終了するために条件が必要な場合、ブール型フラグを使用して終了できます。(valatile、同期終了フラグを使用できます)
  • 3. Interrupt() を呼び出します。
    • (1) スレッドがブロック状態の場合 スレッドは、sleep、同期ロックのwait()、ソケットのreceiver.accept()が含まれるため、ブロック状態となります。 InterruptException がスローされた場合、スレッドは例外をキャッチした場合にのみ正常に終了できます。
    • (2) スレッドがノンブロッキング状態にある場合、isInterrupt() スレッドがループを終了するかどうかを判断するために割り込みフラグを使用します (使用するとinterrupt()、割り込みフラグは true に設定されます)。
  • 4. stop() を呼び出す: thread.stop() を呼び出した後、子スレッドを作成したスレッドは ThreadDeathError エラーをスローし、子スレッドによって保持されているすべてのロックを解放します。この操作はデータの不整合を引き起こす可能性があるため、通常は使用しません。スレッドを終了するにはこのメソッドを使用します。

4. 寝て待ちます

  • (1) sleep: Thread クラスのメソッドに属し、スレッド オブジェクトがこのメソッドを呼び出すと、プログラムは一定時間実行を一時停止して CPU を放棄しますが、オブジェクトのロックは保持して監視状態を維持します。時間が経過すると、自動的に実行状態が再開されます。
  • (2) wait: Object クラスのメソッドに属し、スレッド オブジェクトがこのメソッドを呼び出すと、スレッド オブジェクトは待機中のロック プールに入り、オブジェクトのロックが解放されます。オブジェクト ロック プールに入り、オブジェクト ロックの取得を準備し、実行状態に入ります。

5. 起動して実行する

  • (1) start:スレッドは、真にマルチスレッド実行を実現するためにこのメソッドを呼び出します。run メソッドが完了するまで待つ必要はなく、コードの実行を続行します。この時点で、スレッドは準備完了状態にあり、実行します。走りません。
  • (2) run:スレッドがこのメソッドを呼び出すと、準備完了状態のスレッドが run 関数内のコードの実行を開始し、run メソッドが終了、つまりスレッドが終了します。

6. Java バックグラウンド スレッド

  • (1) 概念:バックグラウンド スレッドは **「ガーディアン スレッド」** とも呼ばれ、ユーザー スレッドにパブリック サービスを提供し、ユーザー スレッドがなくなると自動的に離脱するスレッドです。
  • (2) 優先度:デーモンスレッドはパブリックスレッドであるため、他のスレッドより優先度が低くなります。
  • (3) 設定:ユーザースレッドオブジェクトを作成する前に、スレッドオブジェクトを使用してsetDaemon(true)スレッドをデーモンスレッドとして設定します。
  • (4) 子スレッドの作成:デーモンスレッドによって作成された子スレッドもデーモンスレッドです。
  • (5) 同時実行性:デーモン スレッドは JVM 内にのみ存在し、デーモン スレッドは JVM の動作を停止することによってのみ破棄できます。
  • (6) ケース: GC スレッドは古典的なデーモン スレッドであり、リサイクル可能なオブジェクトがある場合にのみサービス メソッドを実行し、常に低レベルの状態で実行されます。
  • (7) ライフサイクル: JVM ライフサイクルにリンクされており、すべての JVM スレッドがデーモン スレッドである場合、JVM は終了できます。

7. Java ロック

(1) 楽観的ロック:データの取得にロックは必要なく、更新時にバージョン番号を取得してから、データのバージョンが保存されているバージョンと同じ場合はロックを取得して更新し、データのバージョンが異なる場合はロックを取得して更新します。保存されたバージョンに応じて、操作が繰り返されます读 - 比较 - 写Java のオプティミスティック ロックは、CAS 操作を通じて実装されます。

(2) 悲観的ロック:データの読み取りと書き込みの両方で、続行するにはロックを取得する必要があります。Java で同期されるのは悲観的ロックです。AQS フレームワークにおけるロックは、まず CAS 楽観的ロックによるロックの取得を試み、取得できない場合は ReetrantLock などの悲観的ロックの取得に移ります。

(3) スピンロック:ロックを獲得するための仕組みであり、ロックを保持しているスレッドが短時間でロックリソースを解放できれば、競合を待っているスレッドはカーネル状態とユーザー状態を切り替える必要がなくなり、ブロッキングおよびサスペンド状態に入ると、少し待つだけ (スピン) で、ロックを保持しているスレッドがロックを解放した直後にロックを取得できます。スピンは CPU のロックを継続的に取得しようとする方法であるため、CPU を占有し続け、CPU に無駄な作業が発生するため、通常はスピン待機の最大時間を設定する必要があります。アップ状態でロックリソースを取得していない場合は停止し、スピンしてブロック状態になります。

スピンロックの長所と短所:

  • スピン ロックのさまざまなシナリオのパフォーマンスの向上:
  • ロックの競争が激しくないシナリオ:スピンの損失がブロックと再覚醒の損失よりも少ないため、プログラムのパフォーマンスが大幅に向上します。
  • ロックの競合が激しい場合、またはロックを保持しているスレッドが同期ブロックを実行するために長時間ロックを占有するシナリオ: 時間の経過とともにスピンの損失が増えるため、スピン ロックは適用できません。そのため、スピン ロックをオフにする必要があります。

スピン ロックはスピンの実行時間をどのように選択しますか? JDK 1.5 では、スピン時間は固定されています。JDK 1.6 では、同じロック上の以前のスピン時間とロック所有者の状態によって決定される適応スピン ロックが導入されています。コンテキスト スイッチの時間は最適なスピン時間です。

スピンロックの開口部:

  • JDK 1.6-XX:+UseSpinningでは
  • -XX:PreBlockSpin=10スピン数を設定する
  • JDK1.7以降、スピンはJVMによって制御されます

(4) 同期同期ロック

synchronized は、NULL 以外の任意のオブジェクトをロックとして使用できる実装メソッドであり、排他的悲観ロックおよびリエントラント ロックです。

  • 同期スコープ
    • 1. メソッドを操作するときにロックされるのはオブジェクト (this) のインスタンスです
    • 2. 静的メソッドを操作する場合、Class インスタンスはロックされますが、クラスの関連データは永続世代 (メタデータ領域) に格納されるため、静的メソッドをロックすることは、そのメソッドを呼び出すすべてのスレッドをロックすることと同等です。
    • 3. オブジェクト インスタンスを操作する場合、オブジェクトをロックするすべてのコード ブロックがロックされます。複数のキューがあり、複数のスレッドが一緒にオブジェクト モニタにアクセスすると、オブジェクト モニタはこれらのスレッドを別のコンテナに保存します。
  • 同期されたコアコンポーネント
    • 1. Wait Set: wait() を呼び出すスレッドがここに配置されます。
    • 2. 競合リスト: 競合キュー。ロックを要求しているすべてのスレッドは、最初にこのキューに配置されます。
    • 3. エントリ リスト: 競合キュー内の競合スレッドは、このキューに移動されます。
    • 4. OnDesk: ロック リソースをめぐって競合するスレッドは常に最大 1 つあり、このスレッドは OnDesk と呼ばれます。
    • 5. 所有者: 現在ロックを取得しているスレッド オブジェクト
    • 6. !Owner: 現在ロックを解放しているスレッド オブジェクト
  • 同期された実装
    • 1. JVM は、競合リストの末尾からオブジェクトを OnDesk として取得します。同時実行の場合、JVM は競合リストからコンテンツの一部を取得し、エントリ リストなどを OnDesk 候補スレッドとして移動します。列。
    • 2. 所有者スレッドのロックが解除されると、一部のスレッドが競合リストからエントリ リストに移行され、スレッドが OnDesk (通常は最初に入るスレッド) として指定されます。
    • 3. オーナー スレッドがロックを解放すると、システム スループットの向上と引き換えに一定の公平性が犠牲になり、ロックをめぐって競合する OnDesk スレッドになるという最大の利点があります。この動作は競合スイッチングと呼ばれます。
    • 4. OnDesk がロックを取得するとオーナーとなり、ロックを取得しなかったスレッドはエントリーリストに残りますが、オーナースレッドが待機によりブロックされると自動的にロックを解放し、待機セットに移行します。通知または通知すべてを送信してエントリーリストに転送するまで
    • 5. 待機セット、競合リスト、およびエントリ リストのスレッドはすべてブロック状態にあり、OS によって完了されます。
    • 6. 同期化は不公平なロックであり、スレッドが競合リストに入る前に、スレッドはスピンして競合ロック リソース リスト内のスレッド オブジェクトのロック リソース (OnDesk を含む) をプリエンプトします。
    • 7. オブジェクトのロックはモニタオブジェクトを通じて行われ、monitorenter和monitorexit命令がロックの範囲となり、メソッドがロックされているか否かはフラグビットで判断されます。
    • 8. Synchronized は重量のあるロックであり、OS 関連のインターフェイスを呼び出す必要があり、パフォーマンスが低い
    • 9. Java 1.6 は同期を最適化します: 適応スピン、ロックのクリア、ロックの粗密化、軽量ロックおよびバイアスされたロックなど。Java 1.7 および 1.8 はキーワード実装メカニズムを最適化します。
    • 10.ロックが拡張され、バイアス ロックから軽量ロック、さらに重量ロックにアップグレードできます。
    • 11. バイアスされたロックと軽量ロックは、JDK 1.6 ではデフォルトで有効になっており、-XX:-UseBiasedLockingバイアスされたロックは、

(5)リエントラントロック

ReentantLock は、Lock インターフェイスを継承して実装するリエントラント ロックです。同期のすべての作業を完了することに加えて、応答性の高い割り込みロック、ポーリング可能なロック要求、時限ロックなどのマルチスレッドのデッドロックを回避するメソッドも提供します。

Lock インターフェースのメインメソッド:

  • 1. void Lock(): このメソッドを呼び出すと、ロックが空いている場合はロックが直接取得され、占有されている場合はロックが取得されるまで現在のスレッドがブロックされます。
  • 2. boolean tryLock(): このメソッドを呼び出してロックの取得を試みます。ロックが空いている場合はロックを取得して true を返し、そうでない場合は false を返します。
  • 3. voidunlock(): このメソッドを呼び出して、現在のスレッドにロックを解放させます。ロックが保持されていない場合、例外が発生する可能性があります。
  • 4. 条件 newCondition(): 条件オブジェクト。待機中の通知コンポーネントを取得します。コンポーネントは、ロックを取得し、現在のスレッドが await() を呼び出し、現在のスレッドがロックを解放した後にのみ使用できます。
  • 5. getHoldCount(): 現在のスレッドがロック メソッドを実行した回数をクエリします。
  • 6. getQueueLength(): ロックが解放されるのを待っているスレッドの数を返します。
  • 7. getWaitQueueLength(条件条件): 同じ条件オブジェクトを使用して await() を実行するスレッドの数を返します。
  • 8. boolean hasWaiters(Conditioncondition): 指定された条件オブジェクトを使用し、await()を実行するスレッドが存在するかどうかを問い合わせます。
  • 9. boolean hasQueuedThread(Thread thread): 指定されたスレッドがロックを待機しているかどうかをクエリします。
  • 10. boolean hasQueuedThreads: 現在のロックを待機しているかどうか
  • 11. boolean isFairO: ロックが公平なロックかどうかを問い合わせます
  • 12. boolean isHeldByCurrentThread: 現在のスレッドがロックされているかどうか、主に現在のスレッドが lock() を実行するかどうか
  • 13. boolean isLock(): このロックがスレッドによって使用されているかどうか
  • 14. lockInterruptibly(): 現在のスレッドがロックを取得するために中断されない場合
  • 15. boolean tryLock(): ロックの取得を試みます。ロックを取得できる場合は true、取得できない場合は false
  • 16. boolean tryLock(long timeout, TimeUnit単位): 指定された時間内にスレッドによってロックが取得されなかった場合、ロックを取得します。

ReentrantLock と Synchronized の利点

ReentrantLock は、lock()とによってunlock()ロックが解除され、JVM によって制御されず、手動でロックを解除する必要がありますが、割り込み可能、​​公平なロック、および多重ロックの特性を持っています。

ReetrantLock の実装

public class MyService {
    
    
	//1、获取ReetrantLock,通过布尔值来控制锁是否为公平锁
	private Lock lock = new ReetrantLock(true);
	//2、创建条件等待对象
	private Condition condition = lock.newCondition();
	
	//3、创建服务方法
	public void testMethod(){
    
    
		try{
    
    
			//4、加锁
			lock.lock();
			//5、执行await();
			condition.await();
			//6、唤醒线程
			condition.signal();
			//处理数据...
		}catch(InterruptedException e){
    
    
			e.printStackTrace;
		}finally{
    
    
			//释放锁
			lock.unlock();
		}
	}
	 
}

条件ロック方式とオブジェクトロック方式の違い

  • 1. await は wait と同じです
  • 2. シグナルは通知と同等です。
  • 3. signalAll は、notifyAll と同等です。
  • 4. ReetrantLock はウェイクアップする対象となるスレッドを指定できますが、Object はランダムにウェイクアップします。

tryLock、lock、lockInterruptibly の違い

  • 1. tryLock がロックを取得できた場合は直接ロックを取得して true を返し、ロックを取得できなかった場合は false を返します。待ち時間を長くしてロックの取得を試みることができます。タイムアウトになった場合は、直接 false を返します
  • 2. ロックが取得できた場合は true を返し、ロックが取得できなかった場合は常にロックの取得を待ちます。
  • 3. lock と lockInterceptibly はどちらもロックの取得を試みることができ、割り込みによりロックの取得を停止することもできます。ただし、lock は例外をスローしませんが、lockInterruptibly は例外をスローします。

(6) セマフォ セマフォ

セマフォはカウントベースのセマフォです。リソースを適用するスレッドの数を制御するためにしきい値を設定できます。複数のスレッドがリソースを求めて競合する場合、セマフォが適用されます。適用されたセマフォがしきい値を超えると、適用されたスレッドはスレッドがセマフォを返すまでブロックされます。多くの場合、セマフォは共有リソース プールを制限するために使用されます。

相互排他ロックはセマフォを通じて実装されます。相互排他ロックは、1 つのスレッドだけがロックを取得すること、つまりセマフォのセマフォが 1 であることを意味します。

セマフォと ReentrantLock:セマフォは基本的に ReentrantLock のすべての作業を完了でき、適用可能なメソッドは似ています。

  • ロックの取得:acquire() (デフォルトでは、応答性の高い割り込みロックを使用します) - lockInterruptibly()
  • ロックを解放します: release() -unlock()
  • ロックの取得を試みます: tryAcquire() - tryLock()
  • また、ポーリング可能なロック要求と時限ロックの機能も提供します。
  • 公平なロックと不公平なロックのメカニズムも提供し、コンストラクターで設定することもできます
  • ロックの解除はすべて手動で行われるため、すべて finnaly コード ブロックで行う必要があります。

(7)アトミック整数

AtomicBoolean、AtomicInteger、AtomicLong、AtomicReferenceAtomicInteger は、アトミック操作を提供する Integer クラスです。アトミック操作に変換できるAtomicReference<V>オブジェクトのすべての操作にも同じことが当てはまります。

(8) リエントラントロック

リエントラント ロックは再帰ロックとも呼ばれます。これは、外側の関数がロックを取得しても、内側の再帰関数はロックの影響を受けることなく再帰操作を実行できることを意味します。

(9) 公平なロックと不公平なロック

公平ロック(Fair):ロックする前に、待機中のスレッドの有無を確認し、待機中のスレッドを優先し、順番にロックします。

不公平なロック:ロックする前に待機スレッドの有無を考慮する必要がなく、ロックを直接取得できない場合は自動的にキューの最後尾で待機します。(フェアロックよりも優れたパフォーマンス)

(10) リードロックとライトロック

パフォーマンスを向上させるために、Java は読み取り/書き込みロックを提供します。読み取りロックは読み取りメソッドに追加され、書き込みロックは書き込みメソッドに追加されます。読み取り/書き込みロックは相互排他的ロックですが、読み取りロックは相互排他的ではありません。

読み取りロック:データを読み取るために複数のスレッドで使用できますが、書き込み操作と並行して使用することはできません。
書き込みロック:データを書き込むことができるのは 1 つのスレッドだけであり、読み取り操作は相互に排他的です。

Java には読み取り/書き込みロック用のインターフェイスがありjava.util.concurrent.locks.ReadWriteLock、さらに具体的な実装もあります。ReentrantReadWriteLock

(11) 共有ロックと排他ロック

Java は、共有ロックと排他ロックという 2 つのロック モードを提供します。

排他的ロック モード:一度に 1 つのスレッドのみがロックを取得できます。これは悲観的ロック戦略に属し、読み取り/読み取りの競合を回避します。

共有ロック モード:共有ロック モードでは、複数のスレッドが同時にロックを取得し、共有リソースに同時にアクセスできます。これはオプティミスティック ロック戦略に属します。

  • 1. AQS 内のノードは、待機中のスレッドのロック取得方法を識別するために 2 つの定数 SHARED と EXCLUSIVE を定義します。
  • 2. Java 並行パッケージの読み取り/書き込みロックにより、複数の読み取り操作または 1 つの書き込み操作によってリソースにアクセスできますが、両方を同時に実行することはできません。

(12) 重量ロックと軽量ロック

重量級ロック

オペレーティング システムのミューテックス ロックに依存して実装されるロックの一種であるため、重量ロックはユーザー状態とカーネル状態を頻繁に切り替える必要があり、状態の遷移に時間がかかるため、より多くのリソースを消費します。開発プロセス 重量ロックの使用は最小限に抑える必要があります。

軽量ロック

ロックには 4 つの状態があります (ロックのアップグレード プロセスに従って分類): ロックなし、バイアスされたロック、軽量ロック、重量ロック

軽量ロックは、オペレーティング システムのミューテックスを使用して実装された従来のロックと比較されます。軽量ロックのシナリオでは、スレッドが同期コード ブロックに交互にアクセスし、同時に同じロックにアクセスすると、ロック エスカレーション プロセスがトリガーされます。

(13) バイアスロック

バイアスされたロックの機能は、特定のスレッドがロックを取得した後のロック再エントリのオーバーヘッドを排除することです。バイアスされたロックは、ThreadID を置き換えるときに CAS アトミック命令にのみ依存する必要があるため、スレッドが 1 つだけの場合、スレッドはさらに改善されます。同期コードブロックを実行します。

(14) セグメントロック

セグメンテーション ロックは、本当の意味でのロックではなく、データ領域をセグメントに分割し、各スレッド アクセスはデータのセグメントのみにアクセスするため、複数のスレッドを形成して同じコンテナ内のデータにアクセスします。

(15) ロックの最適化

  • ロック保持時間を短縮します:スレッドの安全性が必要なスレッドのみをロックします。
  • ロックの粒度を減らす:大きなオブジェクトでスレッドの安全性を必要とする特定のコード ブロックにロックを追加します。
  • ロックの分離:通常、読み取り/書き込みロックがあり、読み取り/書き込みロックと書き込みロックは機能に応じて分離されます。
  • ロックの粗密化:複数のスレッド間で効果的な同時実行性を確保するには、スレッドがロックを保持する時間をできるだけ短くする、つまりリソー​​ス使用後すぐにロックを解放する必要がありますが、頻繁な取得/解放によるリソースの浪費が発生します。ロックの数が多くなります。
  • ロックの削除:ロックの削除はコンパイラ レベルの戦略です。リアルタイム コンパイラ中に共有できないオブジェクトが見つかった場合、これらのオブジェクトに対する操作を削除できます。ほとんどの場合は不規則なコーディングが原因です。

8. 基本的な糸通し方法

スレッドの基本的なメソッドには、wait、notify、notifyAll、sleep、join、yield などが含まれます。

(1) スレッドは wait を待機します。このメソッドを呼び出すと、現在のスレッドはウェイクアップされるまで WATTING 状態になりますが、wait メソッドを呼び出すとロックが解放されることに注意してください。そのため、wait は通常、同期メソッドまたは同期で使用されます。コードブロック。

(2) スレッドスリープ sleep:このメソッドを呼び出すと、現在のスレッドは WATTING 状態になり、通知、notifyAll、または期限切れになるとウェイクアップしますが、sleep メソッドの呼び出しではロックは解除されないことに注意してください。

(3) スレッド イールド yield:このメソッドを呼び出すと、現在のスレッドが CPU を放棄し、競合キューに入ります。

(4) スレッド割り込み割り込み:スレッドを割り込み、通知信号をスレッドに与えると、スレッド内の割り込みフラグに影響があり、スレッド自体の状態は変わりません。

  • interrupt() は RUNNING 状態のスレッドに割り込むことはできません
  • interrupt() は TIME_WATTING 状態のスレッドに割り込むことができます
  • InterruptedException のスローを宣言する前に、フラグはクリアされます。
  • 割り込み状態によりスレッドを安全に終了できます

(5) スレッドが結合を終了するのを待ちます。現在のスレッドは join() を呼び出します。その後、現在のスレッドは別のスレッドが終了するまで BLOCKING 状態に入り、現在のスレッドは BLOCKING から RUNNABLE 状態に変わり、CPU スケジューリングを待ちます。 。

Join 使用シナリオ:メインスレッドがサブルーチンから結果が返されるまで待機する必要がある場合、メインスレッドはサブスレッドの join() メソッドを呼び出す必要があります。メインスレッドが CPU によって再度スケジュールされると、結果が返されます。サブスレッドで取得できます。

(6) スレッドウェイクアップ通知:指定されたオブジェクトモニター内の単一スレッドをランダムにウェイクアップします。

(7) その他の方法

  • 1. isAlive(): スレッドが生きているかどうかを判断します。
  • 2. activeCount(): プログラム内のアクティブなスレッドの数。
  • 3. enumerate(): プログラム内のスレッドを列挙します。
  • 4. currentThread(): 現在のスレッドを取得します。
  • 5. isDaemon(): スレッドがデーモン スレッドかどうか。
  • 6. setDaemon(): スレッドをデーモン スレッドとして設定します。(ユーザースレッドとデーモンスレッドの違いは、メインスレッドの終了に応じてメインスレッドの終了を待つかどうかです)
  • 7. setName(): スレッドの名前を設定します。
  • 8. setPriority(): スレッドの優先順位を設定します。
  • 9. getPriority(): スレッドの優先度を取得します。

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

CPU はタイム スライス ローテーションを使用して、一定期間各タスクを処理した後、現在のタスクの状態を保存し、次のタスクの状態をロードして、次のタスクの処理を続けます。「タスクの状態を保存して再ロードするプロセスは、コンテキスト切り替えと呼ばれます。」


10. 同期ロックとデッドロック

(1) 同期ロック:複数のスレッドが同時にデータにアクセスすると、データの不整合が発生する可能性があるため、スレッドの同期と相互排他を確保する必要があります。つまり、複数のスレッドが同時に実行されると、1 つのプログラムのみが共有リソースにアクセスできるようになります。同時に、Synchronized キーワードは Java でオブジェクトの同期ロックを取得するために使用されます。

(2) デッドロック:複数のスレッドが同時にブロックされ、そのうちの 1 つ以上のスレッドがリソースの解放を待っています。


11. スレッドプールの原理

スレッド プールの役割は、スレッドを再利用し、最大同時実行数を制御し、スレッドを管理することです。

(1) スレッド多重化: Thread クラスを継承して書き換え、Queue が連続ループで渡す Runnable オブジェクトをその start メソッドに追加し、その run() メソッドを呼び出すことで、スレッド多重化機構を実現できます。

(2) スレッド プールの構成:

  • スレッド プール マネージャー:スレッド プールの作成と管理に使用されます。
  • ワーカー スレッド プール:スレッド プール内のスレッド
  • タスク インターフェイス:各タスクが実装する必要があるインターフェイス。ワーカー スレッドがその操作をスケジュールするために使用します。
  • タスクキュー:保留中のタスクを保存し、バッファメカニズムを提供するために使用されます。

(3) スレッドプールパラメータ

  • corePoolSize:スレッド プール内のコア スレッドの数を指定します。
  • MaximumPoolSize:スレッド プール内のスレッドの最大数を指定します。
  • keepAliveTime:一時的なスレッドのアイドル時間
  • 単位:時間単位
  • workQueue:タスクキュー、実行を待っているタスクを格納します
  • threadFactory:スレッド ファクトリ。スレッドの作成に使用され、通常はデフォルトで使用されます。
  • ハンドラー:拒否戦略。スレッド プール内のすべてのスレッドがビジーの場合、拒否戦略が採用されます。

(4) スレッド プールにはどのような拒否戦略がありますか?

  • AbortPolicy:例外を直接スローし、プログラムの実行を終了します。
  • CallerRunsPolicy:実行のために呼び出し側スレッドに配信されます。つまり、タスクの実行をシリアル化します。
  • DiscardOldestPolicy:待機時間が最も長いタスクを破棄し、現在のタスクの送信を試みます。
  • DiscardPolicy:例外をスローせずにタスクを直接破棄します。

12. スレッドプールの動作プロセス

(1) 新しいスレッド プールを作成します。プール内にスレッドはなく、タスク キューがパラメータとして渡されます。
(2)execute() メソッドが呼び出されると、スレッド プールは次の判断を行います。

  • 使用されているスレッドの数がコア スレッドの数より少ない場合は、タスクをすぐに実行するためのスレッドを作成します。
  • 使用されているスレッドの数がコア スレッドの数以上の場合、このタスクをタスク キューに入れて実行します。
  • タスク キューがフルしきい値に達し、実行中のスレッドの数がスレッド プール内のスレッドの最大数より少ない場合は、タスクの実行をスケジュールするための一時スレッドを作成します。
  • タスクキューがフルしきい値に達し、実行中のスレッド数がスレッドプール内の最大スレッド数以上の場合、拒否ポリシーに従って実行されます。

(3) スレッドがタスクを完了すると、タスクキュー内のタスクの実行をスケジュールします。
(4) スレッドのアイドル時間がスレッドプールの最大アイドル時間に達し、スレッドプール内の実行スレッド数がコア スレッドの数より大きい場合は、コア スレッドの数と等しくなるまでスレッドを停止します


13. ブロッキングキューの原則 (BlockQueue)

(1) スレッドがブロックされる 2 つのケース:

  • キューにデータがない場合、データがキューに入れられるまで、コンシューマ側のすべてのスレッドはブロック (一時停止) されます。
  • キューがデータでいっぱいになると、キューに空きができるまでプロデューサー セグメントのすべてのスレッドがブロック (一時停止) されます。

(2) キューをブロックする主な方法

メソッドの種類 例外をスローする 特別な値 ブロック タイムアウト
挿入メソッド 追加(e) オファー(e) 置く(e) オファー(e、時間、単位)
削除メソッド 取り除く() ポーリング() 取る() ポーリング(時間、単位)
検査方法 要素() ピーク() 利用不可 利用不可
  • 例外をスローします: 例外をスローします
  • 特別な値: 特別な値 (null または false) を返します。
  • ブロック中: 操作が成功するまでスレッドはブロックされます。
  • タイムアウト: 諦めるまでの最大時間ブロックします。

(3) Javaでのキューのブロッキング

  • SynchronousQueue : 要素を格納しないブロッキング キューです。各 put 操作は take 操作を待つ必要があり、そうでないと要素をキューに追加できません。そのため、SynchronousQueue は一般に配信キューとして使用され、データ循環の問題を効率的に解決できます。列の間。
  • PriorityBlockingQueue : 優先順位の並べ替えをサポートする無制限のブロッキング キューです。デフォルトでは、自然順序の昇順が採用されます。compareTo() をカスタマイズして並べ替えルールを指定するか、PriorityBlockingQueue を初期化するときに、コンストラクターで Comparator を指定して要素を並べ替えます。
  • DelayQueue : 優先キューを使用して実装された遅延無制限ブロッキング キュー。キュー内の要素は Delayed インターフェイスを実装する必要があります。要素を作成するときに、新しい要素にアクセスする時間間隔を指定できます。アプリケーション シナリオには、キャッシュ システム設計、スケジュールされたタスクのスケジューリングが含まれます。
  • ArrayBlockingQueue : 配列によって実装された境界付きブロッキング キューです。要素は FIFO 原則に従って並べ替えられます。デフォルトでは、キューへのアクセスに不公平な戦略が使用されますが、ArrayBlockingQueue コンストラクターは公平な戦略を有効にするパラメータを提供します。
  • LinkedBlockingQueue : リンク リストによって実装された有界ブロッキング キュー。FIFO 原則に従って要素を並べ替え、独立したロックを使用してプロデューサーとコンシューマーのデータ同期をそれぞれ制御し、プロデューサーとコンシューマーがキュー内のデータを並行して操作できるようにします。 , キュー全体の同時実行パフォーマンスが向上します。LinkedBlockingQueue のデフォルトの容量は Integer.MAX_VALUE です。
  • LinkedTransferQueue : リンクされたリストで構成される無制限のブロッキング キュー。他のブロッキング キューと比較して、さらに 2 つのコア機能があります。
    • transfer():コンシューマが要素の受信を待機している場合、転送メソッドはプロデューサから渡された値をコンシューマに即座に転送できます。待機しているコンシューマが存在しない場合、キューは受信した値を末尾ノードに格納して待機します。消費者が受け取りを要求します。
    • tryTransfer():キュー内の値を受信するかどうかをコンシューマに問い合わせるために使用され、要素の受信を待っているコンシューマが存在しない場合は false を返します。
    • tryTransfer(long time, TimeUnit単位):一定期間内にコンシューマが要素を受け取るのを待つために使用され、この期間内に要素が消費されなかった場合はfalseを返します。
  • LinkedBlockingDeque : リンク リスト構造で構成される双方向ブロッキング キュー LinkedBlockingQueue と比較して、より多くの双方向操作メソッドを備えており、スレッドの IO 競合を効果的に削減し、スレッドの読み取りおよび書き込み効率を向上させることができます。

14、サイクリックバリア、カウントダウンラッチ、セマフォ

(1) CountDownLatch(スレッドカウンタ)

CountDownLatch クラスは java.util.concurrent パッケージの下にあり、カウンターと同様の機能を実装するために使用できます。

例: タスク A があり、他の 4 つのタスクが実行されるまで実行できません。

final CountDownLatch latch = new CountDownLatch(2);

new Thread(){
    
    
	()->{
    
    
		System.out.println("子线程"+Thread.currentThread().getName()+"正在执行");
		Thread.sleep(3000);
		System.out.println("子线程"+Thread.currentThread().getName()+"执行完毕");
		latch.countDown();
	};
}.start();

new Thread(){
    
    
	()->{
    
    
		System.out.println("子线程"+Thread.currentThread().getNameO+"正在执行");
		Thread.sleep(3000);
		System.out.println("子线程"+Thread.currentThread().getNameO+"执行完毕");
		latch.countDown();
	};
}.start();

System.out.println("等待2个子线程执行完毕...");
latch.await();
System.out.println("2个子线程已经执行完毕");
System.out.println("继续执行主线程");

(2) CyclicBarrier (ループフェンス - バリア状態になるまで待ってからすべてを同時に実行)

スレッドのグループを特定の状態で待機させ、それらをすべて同時に実行できます。待機中のスレッドがすべて解放されると、CyclicBarrier を再利用できます。当面は、この状態をバリアと呼びます。メソッドが呼び出されると、スレッドはバリア内になります。

CyclicBarrier で最も重要なメソッドは await メソッドです。

  • public int await():すべてのスレッドがバリア状態に達するまで現在のスレッドを一時停止し、その後後続のタスクを同時に実行するために使用されます。
  • public int await(long timeout, TimeUnit単位):これらのスレッドを一定時間待機させ、まだバリア状態に到達していないスレッドがある場合は、バリアに到達したスレッドに直接後続のタスクを実行させます。

(3) セマフォ(セマフォ - 同時にアクセスするスレッド数を制御)

セマフォは、取得を通じて同時にアクセスされるスレッドの数を制御できます。許可を取得し、取得できない場合は待機し、release() で許可を解放します。

  • Semaphore クラスのブロッキング メソッド:

    • public voidacquire():ライセンスを取得するために使用されます。ライセンスを取得していない場合は、ライセンスが取得されるまで待機します。
    • public voidacquire(int allowed):許可ライセンスを取得します。
    • public void release():取得したライセンスを解放します。
    • public void release(int Permits):リリース許可、許可
  • Semaphore クラスのノンブロッキング メソッド:

    • public boolean tryAcquire():ライセンスの取得を試みます。取得が成功した場合はすぐに true を返し、取得に失敗した場合はすぐに false を返します。
    • public boolean tryAcquire(long timeout, TimeUnit単位):ライセンスの取得を試行し、指定された時間内に取得が成功した場合はtrueを返し、それ以外の場合はfalseを返します。
    • public boolean tryAcquire(int allowed):許可の取得を試みます。成功した場合は true を返し、失敗した場合は false を返します。
    • public boolean tryAcquire(int許可、長いタイムアウト、TimeUnit単位):許可の取得を試みます。指定された時間内に取得が成功した場合はtrueを返し、それ以外の場合はfalseを返します。
    • availablePermits():利用可能なライセンスの数を取得します

15. volatile キーワードの役割 (変数の可視性、並べ替えの禁止)

Java 言語は、他のスレッドに変数の更新操作が確実に通知されるようにするために、少し弱い同期メカニズムである揮発性変数を提供します。

揮発性変数はレジスタや他のプロセッサから見えない場所にキャッシュされないため、揮発性タイプの変数を読み取るときは、最後に書き込まれた値が常に返されるため、揮発性変数は共有変数として適しており、複数のスレッドで使用できます。この変数に値を直接割り当てます。

(1) 揮発性変数には、変数の可視性と並べ替えの禁止という 2 つの特徴があります。

  • 変数の可視性:変数がすべてのスレッドに可視であることを確認します。スレッドが変数の値を変更すると、他のスレッドはすぐに新しい値を取得できます。
  • リオーダリングの禁止: volatile は命令のリオーダリングを禁止します。

(2) 揮発性変数の共有の原則:不揮発性変数に値を割り当てるには、各スレッドはまず変数をメモリから CPU キャッシュにコピーする必要があります。コンピュータに複数の CPU がある場合、変数は異なる場所にコピーされる可能性があります。 CPU キャッシュ。JVM は変数が読み取られるたびに、CPU キャッシュをスキップしてメイン メモリから読み取られることを保証します。

(3) 揮発性変数に適用できるシナリオ:

  • 変数への書き込み操作は現在の値に依存しないか (i++)、単純な変数割り当て (ブール値フラグ = true) です。
  • 異なる volatile 変数は相互に依存することはできず、volatile 変数は状態がプログラム内の他のコンテンツから完全に独立している場合にのみ使用できます。

16. スレッド間でデータを共有する方法

Java におけるマルチスレッド通信の主な方法は共有メモリです。共有メモリには、可視性、順序、原子性という 3 つの主な懸念事項があります。

JMM は可視性と順序を解決するだけであり、ロックを使用して原子性を解決します。

一般的な実装方法:

  • (1) データオペレーションコードをメソッドに抽象化し、同期を確実にするための同期ロックを取得するために「synchronized」を追加します。
  • (2) Runnable オブジェクトをクラスの内部クラスとして使用し、共有データをそのクラスのメンバ変数として使用し、共有データに対する各スレッドの操作も外部クラスにカプセル化することで、データ操作の同期と相互排他を実現するため、内部クラスのRunnableオブジェクトとして操作クラスを呼び出します。

17. ThreadLocal の役割

ThreadLocal の役割は、スレッドのライフサイクル内で機能するローカル変数をスレッドに提供し、同じスレッド内の複数の関数またはコンポーネント間でパブリック変数を渡す複雑さを軽減することです。

(1) ThreadLocalMap(スレッドの属性)

  • 各スレッドは、このスレッドのオブジェクトを格納できる ThreadLocalMap オブジェクトを維持し、各スレッドはそれに応じて独自のオブジェクトにアクセスできます。
  • 共通の ThreadLocal 静的インスタンスをキーとして使用し、さまざまなスレッドの ThreadLocalMap 内のさまざまなオブジェクトへの参照を保存し、この静的 ThreadLocal インスタンスの get() メソッドを使用して、スレッドによって実行される各メソッドで独自のスレッドによって保存されたオブジェクトを取得します。このオブジェクトをパラメータとして渡す手間を省きます。

ここに画像の説明を挿入

(2) 利用シーン

最も一般的な ThreadLocal の使用シナリオは、データベース接続、セッション管理などを解決するために使用されます。


18. synchronized と ReentrantLock の違い

(1) 共通点

  • どちらも、共有オブジェクトおよび変数へのマルチスレッド アクセスを調整するために使用されます。
  • すべて再入可能なロックであり、同じスレッドが同じロックを複数回取得できる
  • どちらも可視性と相互排除を保証します

(2) 相違点

  • ReentrantLock はロックの取得と解放を示し、synchronized は暗黙的にロックの取得と解放を示します。
  • ReentrantLock は割り込みに応答し、循環させることができますが、Synchronized は割り込みに応答できないため、ロックが利用できない場合に柔軟に対処できます。
  • ReentrantLock は API レベルであり、synchronized は JVM レベルです。
  • ReentrantLock は公平なロックを実装できます
  • ReentrantLock は、Condition を通じて複数の条件をバインドできます。
  • 基礎となる実装は異なります。synchronized はペシミスティックな同時実行戦略を使用した同期ブロッキングであり、lock はオプティミスティックな同時実行戦略を使用した同期非ブロッキングです。
  • Lock はインターフェイスであり、synchronized は Java のキーワードであり、synchronized は組み込み言語の実装です。
  • Synchronized は例外が発生したときにスレッドが占有しているロックを自動的に解放するため、デッドロックは発生しませんが、Lock は例外が発生した場合、unlock() を通じてロックを積極的に解放しないとデッドロックが発生する可能性があります。したがって、When Locking を使用するには、finally ブロックでロックを解除する必要があります。
  • Lock を使用すると、ロックを待機しているスレッドが割り込みに応答できるようになりますが、同期ではできません。同期を使用すると、待機中のスレッドは永久に待機し、割り込みに応答できなくなります。
  • Lock を通じて、ロックが正常に取得されたかどうかを知ることができますが、同期ではわかりません。
  • ロックにより、読み取り操作、つまり読み取り/書き込みロックなどの複数のスレッドの効率が向上します。

19. ConcurrentHashMap の同時実行性

(1) ロックの粒度を下げる

ロック粒度を減らすことは、ロックされたオブジェクトの範囲を減らすことを指し、それによってロック競合の可能性が減り、システムの同時実行性が向上します。ConcurrentHashMap のロック粒度はセグメント化されたロックによって減少するだけであり、これによりスレッド セーフを実現するコストが削減されます。

(2) ConcurrentHashMapセグメントロック

ConcurrentHashMap は、セグメント (セグメント) と呼ばれるいくつかの小さな HashMap を内部的に細分化します。

デフォルトでは、ConcurrentHashMap はさらに 16 のセグメントに分割されます。つまり、16 の異なるセグメントに 16 のスレッドが並行してアクセスできます。

ConcurrentHashMap に新しいエントリを追加する必要がある場合は、まずハッシュコードに従ってエントリが保存するセグメントを決定し、次に対応するセグメントをロックして、書き込み操作を完了します。複数のスレッドが同じスレッドで書き込み操作を実行しない限り、セグメントでは、スレッドを真に並列にすることができます。

(3) ConcurrentHashMapはSegment配列構造とHashEntry配列構造から構成されます

Segmentのデータ構造は、ConcurrentHashMap でロックの役割を果たすリエントラント ロック ReentrantLock である HashMap と似ています。
HashEntry は、キーと値のペアのデータを格納するために使用されるリンク リスト構造の要素です。

各セグメントは HashEntry 配列の要素を保護します。HashEntry 配列のデータが変更されるときは、まず対応するセグメント ロックを取得する必要があります。


20. Java でのスレッド スケジューリング

(1) プリエンプティブスケジューリング

プリエンプティブ スケジューリングとは、各スレッドの実行時間とスレッドの切り替えがシステムによって制御されることを意味します。

システム制御とは、システムの特定の動作メカニズムの下で、各スレッドに同じ実行タイム スライスが割り当てられる場合や、一部のスレッドの実行タイム スライスがより長い場合、または一部のスレッドが実行タイム スライスを取得できない場合があることを指します。このメカニズムでは、1 つのスレッドがブロックされてもプロセス全体がブロックされることはありません。

(2) 協調スケジューリング

協調スケジューリングとは、スレッドの実行後に、別のスレッドに実行を切り替えるようにシステムに能動的に通知することを意味します。

スレッドの実行時間はスレッド自体によって制御され、スレッドの切り替えが予測でき、マルチスレッドの同期の問題もありません。スレッドのプログラミングに問題がある場合、スレッドは動作の途中でブロックされ、システム全体がクラッシュする可能性があります。

(3) JVMのスレッドスケジューリング実装(プリエンプティブスケジューリング)

Java では、スレッドには優先度に応じて CPU タイム スライスが割り当てられ、優先度が高いほど実行の優先度が高くなります。ただし、優先度が高いからといってスレッドが単独で実行タイム スライスを占有できるわけではなく、優先度が低いほど優先度が高くなります。実行タイム スライスを占有しないという意味ではありません。

(4) スレッドがCPUを放棄する状況

  • 現在実行中のスレッドは積極的に CPU を放棄し、JVM は、yield() メソッドの呼び出しなど、CPU 操作を一時的に放棄します。
  • 現在実行中のスレッドが、I/O のブロックなどの何らかの理由でブロック状態になります。
  • 現在実行中のスレッドが終了します

21. プロセススケジューリングアルゴリズム

オペレーティング システムには、優先スケジューリング アルゴリズム、高優先優先スケジューリング アルゴリズム、タイム スライスに基づくラウンドロビン スケジューリング アルゴリズムの3 つのプロセス スケジューリング アルゴリズムがあります。

(1) 優先スケジューリングアルゴリズム

  • 先着順スケジューリング アルゴリズム (FCFS):このアルゴリズムがジョブ スケジューリングで使用される場合、各スケジュールはバックアップ ジョブ キューから最初にキューに入る 1 つ以上のジョブを選択し、それらをメモリにロードしてリソースを割り当てます。プロセスを作成し、それをレディキューに入れます。スケジュールのスケジューリングには FCFS アルゴリズムが使用されます。各スケジュールは、レディキューから最初にキューに入るプロセスを選択して、プロセッサを割り当てます。プロセスは、プロセスが完了するまで実行されます。プロセッサを放棄する前にイベントをブロックします。
  • ショート ジョブ (プロセス) 優先スケジューリング アルゴリズム:
    ショート ジョブ優先アルゴリズム (SJF) は、予想実行時間が最も短いジョブをバックアップ キューから転送し、メモリに転送して実行します。
    ショートプロセス優先アルゴリズム (SPF) は、予想実行時間が最も短いプロセスを準備完了キューから転送し、それにプロセッサを割り当て、完了するまで、またはイベントが発生してプロセッサがブロックされるまで、即座に実行するようにします。そして放棄されました。

(2) 高優先度の優先スケジューリング アルゴリズム:緊急のジョブを解決し、システムに入ってから優先的に処理させるために、最優先度の優先スケジューリング アルゴリズム (FPF) が導入されています。

ジョブのスケジュールに使用すると、システムはバックアップ キューから最も優先度の高いジョブをいくつか選択し、メモリにロードします。
プロセスのスケジューリングに使用される場合、システムは、レディ キュー内で最も高い優先順位を持つプロセスにプロセッサを割り当てます。

  • 非プリエンプティブ優先順位スケジューリング アルゴリズム:スレッドは CPU をプリエンプトできないため、CPU が最も優先度の高いプロセスに割り当てられている場合、他のプロセスはプログラムを実行するための CPU を取得する前にプロセスが終了するまで待機する必要があります。 。主にバッチ処理システムで使用されますが、リアルタイム性能が高くない一部のリアルタイムシステムでも使用できます。
  • プリエンプティブ優先スケジューリング アルゴリズム:スレッドは CPU をプリエンプトできるため、CPU が最も高い優先度のプロセスを実行している場合、より高い優先度のプロセスが CPU を要求すると、CPU は現在のプロセスを放棄し、そのプロセスの状態を保存します。現在のプロセス。優先度の高いプロセス実行者のスケジュールを設定します。これは主に、高いリアルタイム要件を持つリアルタイム システムや、高いパフォーマンス要件を持つバッチ処理やタイムシェアリング システムで使用されます。
  • 高応答率優先スケジューリングアルゴリズム(非常に優れたスケジューリングアルゴリズム): CPUスケジューリングでスレッドが長時間待たされて実行できない場合があるため、待ち時間に応じてジョブの優先度が高くなる動的優先度の概念を導入。一定の成長率で成長率が上がり、長いジョブは一定時間待ってからCPUに割り当てて処理する必要があります。
    • ジョブの待ち時間が同じ場合、サービス時間が短いほど優先順位が高く、短時間のジョブに適しています。
    • ジョブのサービス時間が同じ場合、ジョブの待ち時間が長いほど優先度が高く、FCFSを実現します
    • 長時間のジョブの場合、アルゴリズムは応答率の計算により CPU による優先実行も取得し、長時間の待機によるサービスのクラッシュを回避します。

(3) タイムスライスに基づくラウンドロビンスケジューリングアルゴリズム

  • タイム スライス ローテーション アルゴリズム:初期のタイム スライス ローテーション アルゴリズムでは、システムが FCFS に従って準備が完了したプロセスを配置します。スケジューリングのたびに、CPU がチーム リーダー プロセスに割り当てられ、タイム スライスが実行されます。タイムスライスは数ミリ秒から数百ミリ秒で始まり、実行時間が使い果たされると、タイマーがクロック割り込み要求を送信し、スケジューラが現在のプロセスの実行を停止して、レディキューの最後にプロセスを送信します。次に、CPU を先頭プロセスに割り当てます。このサイクルは、すべてのプロセス タスクが完了するまで繰り返されます。
  • マルチレベルフィードバックキュースケジューリングアルゴリズム
    • マルチレベルレディキューを通じて、各キューの優先度は異なり、各キューの優先度に従って異なるタイムスライスが割り当てられ、優先度の高い割り当てられたタイムスライスはより長くなります。
    • プロセスがレディ キューに入ると、プロセスは最初に最初のレディ キューに割り当てられ、FCFS 原則に従ってタイム スライスが実行されるのを待ちます。タイム スライスが終了してもプロセス タスクが完了していない場合、プロセスは次のようになります。 2番目のレディキューに割り当てられ、FCFS原則に従って待機します。タイムスライスの実行がまだ完了していない場合は、完了するまで逆方向に配置し続けます。
    • 高優先度のキューが空の場合、CPU は低優先度のキューを実行し、新しいプロセスが高優先度のキューに入ると、CPU は現在実行中のスレッドを現在のキューの最後に置き、優先度を与えます。高優先度の実行プロセス。

22. CAS(コンペアアンドスワップ)とは

(1) 概念: CAS とは、Compare and Exchange の意味で、保存されている値が古い値と同じかどうかを比較することで、データが変更されたかどうかを判断します。
(2) 3 つのパラメータ: V、E、N

  • V:更新された変数(メモリ値)
  • E:期待値(旧値)
  • N:新しい値
    (3) 更新メカニズム: V = E の場合にのみ、V を N に設定できます。V != E の場合、操作は実行されず、最終的に CAS は V の値を返します。
    (4) CAS のスレッド安全原則: CAS は楽観的ロック原則を採用しており、複数のスレッドが CAS を使用して変数を同時に操作すると、1 つのスレッドだけが更新を完了でき、他のスレッドはすべて失敗します。この原則に基づいて、 CAS は変数更新の同期も実現できます。
    (5) アトミック パッケージ java.util.concurrent.atomic:このパッケージはアトミック クラスのセットを提供します. このようなアトミック操作はマルチスレッド環境では排他的になる可能性があります. CAS は CPU 命令セットに基づく操作です. CPU スイッチング アルゴリズムに依存する必要があるため、パフォーマンスが向上します。
    (6) ABA 問題: ABA 問題は、CAS アルゴリズムの時間差によって引き起こされるデータの不整合の問題ですが、最終結果はまだ元の結果であるため、このプロセスでも CAS 操作を成功させることができ、一部の楽観的ロックが発生します。 ABA 問題を解決するためにバージョン番号法で比較します。

23.AQSとは

AQS (AbstractQueueSynchronizer 抽象キュー シンクロナイザー) は、共有リソースへのマルチスレッド アクセスのための一連のシンクロナイザー フレームワークを定義します。多くの同期クラス実装は AQS に依存します。たとえば、一般的に使用されるReentrantLock、Semaphore、CountDownLatch

AQS は、volatile int 状態 (共有リソースを表す) と FIFO スレッド待機キュー (複数プロセスの競合リソースがブロックされ、待機キューに入ります) を維持します。

(1) 共有リソースの状態にアクセスするには、次の 3 つの方法があります。

  • getState()
  • setState()
  • CompareAndSetState()

(2) AQS の 2 つのリソース共有モード:

  • Exclusive (排他モード) - ReentrantLock
  • 共有 (共有モード) - セマフォ/カウントダウンラッチ

AQS は単なるフレームワークです。特定のリソースの取得/解放はカスタム シンクロナイザーによって実装する必要があります。AQS はインターフェイスを定義します

カスタム シンクロナイザーは、状態の get/set/CAS メソッドを通じて、特定のリソースの取得を実現します。状態が抽象として定義されていない理由は、tryAcquire-tryRelease のみが排他モードで実装され、共有モード tryAcquiredShared-tryReleaseShared がすべて抽象として定義されている場合、各モードは別のモードでインターフェイスを実装する必要があります。異なるカスタム シンクロナイザーは、共有リソースの状態を取得および解放する方法を実装するだけで済みます。特定のスレッド待機キューでは、AQS がトップレベルで実装されています。

カスタム シンクロナイザーを実装する場合の主な実装方法は次のとおりです。

  • isHeldExclusively():スレッドがリソースを独占しているかどうかに関係なく、条件が使用される場合にのみ実装する必要があります。
  • tryAquire(int):排他モード、リソースの取得を試みます、成功した場合は true、失敗した場合は false
  • tryRelease(int):排他モード、リソースの解放を試みます、成功した場合は true、失敗した場合は false
  • tryAquireShared(int):共有メソッド、リソースの取得を試行します。負の数値は失敗を意味します。0 は成功を意味しますが、リソースが使い果たされたことを意味します。整数は成功を意味しますが、利用可能なリソースがまだ残っています。
  • tryReleaseShared(int):共有モード。リソースの解放を試行します。解放後のウェイクアップが許可されている場合は、ノードが true を返すまで待機し、それ以外の場合は false を返します。

シンクロナイザーの実装は ABS コア (ステート リソース ステート カウント)

シンクロナイザーの実装は ABS の核心であり、ReentrantLock を例にとると、スレッド A の lock() 実行時に state=0 を初期化し、他のスレッドが到達 = ロックを取得しようとしたときに、状態が 0 であるかどうかを判断します。 0、ロックを取得できるかどうかを知ることができます; スレッドAのunlock()、state - 1、0の状態に戻るとき、それぞれの取得と解放は等しいため、ロックが完全に解放されると、状態が0の状態に戻ります。スレッド A がロックを取得し続ける場合、状態は +1 を維持します。これは、リエントラント ロックの実現です。

おすすめ

転載: blog.csdn.net/Zain_horse/article/details/132148207