1. 同期化された変更された同期化されたコード ブロック
コード セグメントを同期ロックとして変更するには、synchronized を使用します。コードは次のとおりです。
public class LockDemo {
public Object object = new Object();
public void show(){
synchronized (object) {
System.out.println(">>>>>>hello world!");
}
}
public static void main(String[] args) {
new LockDemo().show();
}
}
次のように、main() メソッドを直接実行して LockDemo クラスをコンパイルし、特定の LockDemo.class クラス ファイル パスを見つけて、javap
コマンドを通じて基になるコードを表示します。
javap -c LockDemo.class
上の図から、最下層は同期操作を実装するためにmonitorenterとmonitorexitによって実装されていることがわかります。
しかし、ここでは 1 つの Monitorenter が 2 つの Monitorexits に対応していることがわかります。論理的に言えば、ロックとロック解除は 2 つの操作であるべきではありませんか? なぜ 2 つのロック解除があるのでしょうか? 実際、この設計は、例外が発生したときにロックを解放でき、プログラムがスタックしないようにするためのものです。ただし、次のコードのように、必ずしも 1:2 である必要はありません。
public class LockDemo {
public Object object = new Object();
public void show(){
synchronized (object) {
System.out.println(">>>>>>hello world!");
throw new RuntimeException(">>>>>>异常");
}
}
public static void main(String[] args) {
new LockDemo().show();
}
}
javap コマンドを使用して、次の図を表示します。
throwrow を通じて最外層に例外をスローし、最後にmonitorexit によってロックが解放されます。要約すると、同期ロックはペアで表示されますが、異常な状況でロックを解放できるようにするために、適切な末尾処理が追加されています。
2. 同期修飾方法
コードは以下のように表示されます。
public class LockDemo {
public Object object = new Object();
public void show(){
synchronized (object) {
synchronized (this) {
System.out.println(">>>>>>hello world!");
}
}
}
public static synchronized void sop() {
}
public static void main(String[] args) {
new LockDemo().show();
}
}
合格javap -v LockDemo.class以下に示すように、バイトコードを表示するコマンド:
ヒント: より包括的な情報を表示するには、java -v Xxx を使用します。
メソッドレベルの同期は目に見えず、バイトコード命令で制御する必要はなく、メソッド呼び出しと戻り操作で実装されます。仮想マシンは、メソッド定数プールのメソッド テーブル構造のACC_SYNCHRONIZEDメソッド識別子から、このメソッドが同期メソッドとして宣言されているかどうかを知ることができます。呼び出し時に、呼び出し命令は、メソッドのACC_SYNCHRONIZEDアクセス識別子が設定されているかどうかを確認します。これが設定されている場合、実行スレッドは最初にMontior
モニターを正常に保持する必要が。その後、メソッドを実行できます。他のスレッドは同じモニターを取得できません。同期メソッドが実行時に例外をスローし、メソッド内で処理できない場合、同期メソッドの境界外で例外がスローされると、同期メソッドによって保持されているモニターが自動的に解放されます。
3. モニターとは何ですか?
HotSpot 仮想マシンでは、Monitor は同期を実現するために使用されるメカニズムです。Monitor は、マルチスレッド環境でクリティカル セクション (共有リソース) に同時にアクセスできるスレッドが 1 つだけであることを確認し、スレッドの安全性を確保します。
Java では、各オブジェクトにモニターがあり、オブジェクトのモニターは synchronized キーワードを通じて取得できます。スレッドが同期ブロックに入ると、オブジェクトのモニターを取得しようとします。モニターが別のスレッドによって保持されている場合、モニターが使用可能になるまで、現在のスレッドはブロックされます。スレッドが同期ブロックを終了すると、オブジェクトのモニターが解放され、他のスレッドがモニターを取得して同期ブロックに入ることができるようになります。
以下は、synchronized キーワードを使用してオブジェクトのモニターを取得し、スレッドの安全性を確保する方法を示す簡単な例です。
class Counter {
private int count = 0;
public synchronized void increment() {
count++;
}
public synchronized void decrement() {
count--;
}
public synchronized int getCount() {
return count;
}
}
class Main {
public static void main(String[] args) {
Counter counter = new Counter();
// 创建 1000 个线程,每个线程分别执行 1000 次增加和减少操作
for (int i = 0; i < 1000; i++) {
new Thread(() -> {
for (int j = 0; j < 1000; j++) {
counter.increment();
counter.decrement();
}
}).start();
}
// 等待所有线程执行完毕
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 输出最终计数器的值
System.out.println(counter.getCount()); // 0
}
}
上記のコードでは、Counter という名前のクラスを作成しました。このクラスには、カウンター変数 count と 3 つの同期メソッド、increment()、decrement()、getCount() が含まれています。これらのメソッドでは、synchronized キーワードを使用してオブジェクトの Monitor を取得し、マルチスレッド環境でカウンタの操作がスレッドセーフであることを確認します。
main メソッドでは、1,000 個のスレッドを作成し、それぞれが 1,000 回のインクリメントとデクリメントを実行したため、カウンターに対する多数の同時変更が行われました。最後に、すべてが正しく行われたことを確認するために 0 が得られることを期待して、カウンターの値を出力します。
モニターの最下層はObjectMonitor.cpp jvm を使用して実装され、各オブジェクトはモニターとともに生成されます。
初期化ObjectMonitor構築メソッドを参照してください。ソース コードは以下のとおりです。
属性 | プロパティの説明 |
---|---|
_所有者 | ObjectMonitor オブジェクトを保持するスレッドを指します。 |
-WaitSet | スレッドキューを待機状態に保存する |
_EntryList | ロックブロック状態を待っているスレッドキューを格納します |
_再帰 | ロックの再入力時間 |
_カウント | スレッドがロックを取得した回数を記録するために使用されます。 |
スレッド A がやって来て、EntryList コレクションに入り、Monitor を取得しようとすると、ロックを取得しようとしています。ロックを取得した後、Owner 領域に入ります。これは、スレッド A が正常にロックされたことを意味します。次に、同期メソッドのコードを呼び出します。同期メソッドの実行時に、wait メソッドが見つかると、スレッド A が WaitSet キューに入り、モニターの Owner も null になり、再エントリ数 Recursions = 0 になり、先ほど来たばかりの新しいスレッドが現れるとします。 EntryList で終了するか、WaitSet で起動されたスレッドが、Monitor ロックの競合を開始します。このプロセスはスレッド A のロックと同じです。