スケジューリングメカニズム
-
特定のスケジューリングの実装は、オペレーティングシステムとJVMに分かれています
- オペレーティングシステムには多くのスケジューリングメカニズムがあり、2つの一般的なメカニズムがあります。タイムスライスUnixとプリエンプティブウィンドウです。
-
すべてのJava仮想マシンには、その時点で実行するスレッドを決定するためのスレッドスケジューラがあります。2つの主要なスケジューリングモデルがあります:タイムシェアリングスケジューリングモデルとプリエンプティブスケジューリングモデル
- タイムスライスローテーション方式に基づくプリエンプティブスケジューリング。スレッドスケジューラは、高い優先度を実行する機会を増やします。優先度が同じである場合、ランダムに選択され、実行時間がタイムスライスに達した後に実行時間が再スケジュールされます。
-
スレッドのスケジューリングはクロスプラットフォームではなく、Java仮想マシンだけでなくオペレーティングシステムにも依存します。一部のオペレーティングシステムでは、実行中のスレッドがブロックされていなくても、一定期間実行した後にCPUを放棄し、他のスレッドに実行の機会を与えます。
スレッドの優先度
Javaはプリエンプティブスケジューリングメカニズムを使用して、起動後に準備完了状態に入るプログラム内のすべてのスレッドを監視するスレッドスケジューラを提供します。スレッドスケジューラは、スレッドの優先度に従って、実行をスケジュールするスレッドを決定します。
スレッドの優先度は1から10の範囲の数値で表され、デフォルト値は5です。ただし、優先度の具体的な実装はオペレーティングシステムに依存する必要があるため、Javaの優先度をオペレーティングシステムにマッピングする必要があるため、オペレーティングシステムでサポートされる優先度の数が少なく、複数ある場合があることに注意してください。 Javaの優先度は、同じオペレーティングシステムの優先度にマップされます。したがって、特定の用途では、優先度の差を比較的大きくすることをお勧めします。事前定義された定数
t2.setPriority(Thread.MAX_PRIORITY); ---- 10
t1.setPriority(Thread.NORM_PRIORITY);を使用することをお勧めします。 ----- 5デフォルト値
t3.setPriority(Thread.MIN_PRIORITY); ----- 1
getPriority():int現在のスレッドオブジェクトの優先度を取得します
setPriority(int):void現在のスレッドオブジェクトの優先度を設定します。start()メソッドを呼び出す前に優先度を変更する必要があることに注意してください。
総括する
1.スレッドの優先度が指定されていない場合、スレッドは通常の優先度になります。
2.スレッドの優先度は1から10に分けることができます。10は最高の優先度、1は最低の優先度、通常の優先度は5です。
3.実行時に最も優先度の高いスレッドが優先されますが、スレッドが開始された直後に実行状態になる保証はありません。
4.スレッドプール内の待機中のスレッドと比較して、実行中のスレッドの優先度が高くなります。
5.スケジューラーは、実行するスレッドを決定します。
6. setPriority(int)を使用して、スレッドの優先度を設定します。
7.スレッドのstartメソッドを呼び出す前に、スレッドの優先度を指定する必要があります
スレッド同期の問題
マルチスレッドプログラミングでは、システムのスレッドスケジューリングのランダム性が原因でエラーが発生する傾向があり、これらの状況を排除する必要があります。
-スレッドの実行シーケンスは再現できませんが、実行結果は再現可能で
ある必要があります-解決策は処理を同期することです
同期方法1
共有データを操作するスレッドが複数ある場合、同時に共有データを操作するスレッドが1つだけであることを確認する必要があり、他のスレッドはスレッドがデータを処理するまで待機してから続行する必要があります。このメソッドには名前があります。相互除外ロックと呼ばれます。これは、相互除外アクセスの目的を達成するロックです。つまり、現在アクセスしているスレッドによって共有データがミューテックスに追加されると、同時に他のスレッドは現在のスレッドがロックの処理を終了し、ロックを解放するまで待機状態。
特定のプログラミングでsynchronizedキーワードを使用すると、相互排他ロックを使用できます。
もちろん、シリアルと並行して同期された変更は、プログラムの実行効率に影響を与え、実行速度に影響を与えます。次に、同期された操作スレッドのブロックは、オペレーティングシステムがCPUコアを制御してコンテキストを切り替えることを意味します。この切り替え自体にも時間がかかります。したがって、synchronizedキーワードを使用すると、プログラムの効率が低下します。
プログラミングの実現
要件:同じ数で加算と減算の計算を実行する4つのスレッドを定義し、2つのスレッドがそれぞれ50の加算を実行し、2つのスレッドがそれぞれ50の減算を実行します
1.操作が必要なデータと対応する操作メソッドをカプセル化するクラスを定義します
//封装数据和对应的操作
public class NumOps {
private int num;//具体操作数据
//提供的方法,业务处理
public void add() {
num=num+1;
System.out.println(Thread.currentThread().getName()+":add...."+this.num);
}
public void sub() {
num=num-1;
System.out.println(Thread.currentThread().getName()+":sub...."+this.num);
}
}
2.対応するスレッド実装を
定義します。スレッドを定義する方法は4つあります。スレッドを拡張する、Runnable、Callable、Futureを実装する、スレッドプール
- 単一ルートの継承のため、スレッドは基本的に使用されません
- Runnableは通常、戻り値がない場合に使用され、さらに使用されます。スレッドの実行結果を記録する必要がある場合は、自分でプログラムする必要があります。
- Callableは通常、戻り値があり、FutureTaskを使用するように構成する必要がある場合に使用されます。callメソッドを使用すると、例外をスローできます
- ThreadPoolExecutorの使用には、一般的に5種類のスレッドプールを使用することをお勧めします。短期間に多数の短期実行スレッド要件がある場合に使用されるシステム事前定義の使用はお勧めしません。
実際の開発では、スレッドが直接使用されることはめったになく、スレッドは通常、フレームワークを通じて使用されます。
public class Test1 {
private static NumOps ops=new NumOps();
static class AddThread extends Thread{
@Override
public void run() {
for(int i=0;i<50;i++) {
ops.add();
}
}
}
static class SubRunnable implements Runnable{
@Override
public void run() {
for(int i=0;i<50;i++) {
ops.sub();
}
}
}
}
3.2つのプラススレッドと2つのマイナススレッドを開始します
public class Test1 {
private static NumOps ops=new NumOps();
public static void main(String[] args) throws Exception {
Thread[] arr=new Thread[4];
for(int i=0;i<4;i++) {
if(i%2==0) {
AddThread at=new AddThread();
arr[i]=at;
at.start();
}else {
Thread st=new Thread(new SubRunnable());
arr[i]=st;
st.start();
}
}
for(Thread tmp:arr) {
tmp.join();
}
System.out.println("Main:"+ops.getNum());
}
}
結論:計算結果は正しいですが、出力に問題があります
解決策:NumOpsクラスの加算および減算メソッドに同期されたキーワードを追加します
public class NumOps {
private int num;//具体操作数据
//提供的方法,业务处理
public synchronized void add() {
num=num+1;
System.out.println(Thread.currentThread().getName()+":add...."+this.num);
}
public synchronized void sub() {
num=num-1;
System.out.println(Thread.currentThread().getName()+":sub...."+this.num);
}
public int getNum() {
return num;
}
}
同期の意味を理解する
1.同期は、現在のNumOpsタイプのオブジェクトに相互排他ロックメカニズムを追加することです。ロックは1つしか存在できません。
2.スレッドが同期メソッドで実行されている場合、他のスレッドはオブジェクトの同期メソッドに入ることができませんが、非同期メソッドに入ることができます
一般的な4種類のJavaスレッドロック:原子量AtomicInteger、セマフォSemaphone、同期処理、および再入可能ロックReentrantLock
jdk6がヘビーウェイトロックになる前は、JDK6は、ロックフリー状態(同期なし)、バイアスロック、ライトウェイトロック、およびヘビーウェイトロックの合計4つのロック状態に対して最適化され始めました。ロック状態の変更は、競争の度合いに応じて行われます。競争がほとんどない状況では、バイアスロックが使用されます。軽い競争の状況では、バイアスロックから軽量ロックにアップグレードされます。厳しい競争条件では、 、ヘビーウェイトロックにアップグレードされます。ロックの競合により、ロックはバイアスロックから軽量ロックにアップグレードしてから、重量ロックにアップグレードできますが、ロックのアップグレードは一方向です。つまり、低から高にしかアップグレードできません。ロックはありません。ダウングレード
同期ロックバイトコードを使用する場合、monitorenterとmonitorexitの2つの命令があります。これらは、コードブロックが実行される前のロックと、同期を終了するときのロック解除として理解できます。