1. Synchronized の基本的な使用法
Synchronized は、Java での同時実行の問題を解決するために最も一般的に使用される方法の 1 つであり、最も簡単な方法でもあります。 Synchronized には 3 つの主な機能があります: (1) スレッドが同期コードにアクセスする際に相互に排他的であることを保証する (2) 共有変数への変更が適時に確認できることを保証する (3) 並べ替えの問題を効果的に解決する。文法的に言えば、Synchronized には合計 3 つの使用法があります:
(1) 通常のメソッドを変更する
(2) 静的メソッドを変更する
(3) コードブロックを変更する
次に、いくつかのサンプルプログラムを通してこれを説明します。 (比較の便宜上、Synchronized の使用方法が異なることを除いて、3 つのコードは基本的に同じです)。
1. 同期なし:
コードセグメント 1:
package com.paddx.test.concurrent; public class SynchronizedTest { public void method1(){ System.out.println("Method 1 start"); try { System.out.println("Method 1 execute"); Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Method 1 end"); } public void method2(){ System.out.println("Method 2 start"); try { System.out.println("Method 2 execute"); Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Method 2 end"); } public static void main(String[] args) { final SynchronizedTest test = new SynchronizedTest(); new Thread(new Runnable() { @Override public void run() { test.method1(); } }).start(); new Thread(new Runnable() { @Override public void run() { test.method2(); } }).start(); } }
スレッド 1 とスレッド 2 は同時に実行状態になるため、スレッド 2 はスレッドを完了します。この処理はスレッド 1 とスレッド 2 が同時に実行されます。
メソッド1 start
メソッド1execute
メソッド2 start
メソッド2execute
メソッド2 end
メソッド1 end
2. 通常のメソッドを同期します:
コードセグメント2:
package com.paddx.test.concurrent; public class SynchronizedTest { public synchronized void method1(){ System.out.println("Method 1 start"); try { System.out.println("Method 1 execute"); Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Method 1 end"); } public synchronized void method2(){ System.out.println("Method 2 start"); try { System.out.println("Method 2 execute"); Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Method 2 end"); } public static void main(String[] args) { final SynchronizedTest test = new SynchronizedTest(); new Thread(new Runnable() { @Override public void run() { test.method1(); } }).start(); new Thread(new Runnable() { @Override public void run() { test.method2(); } }).start(); } }
実行結果は次のとおりです。コードに従ってください。 最初の段落を比較すると、スレッド 2 はメソッド 2 メソッドの実行を開始する前に、スレッド 1 のメソッド 1 の完了を待つ必要があることが明確にわかります。
メソッド1開始
メソッド1実行
メソッド1終了
メソッド2開始
メソッド2実行
メソッド2終了
3.静的メソッド(クラス)の同期
コードセグメント3:
package com.paddx.test.concurrent; public class SynchronizedTest { public static synchronized void method1(){ System.out.println("Method 1 start"); try { System.out.println("Method 1 execute"); Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Method 1 end"); } public static synchronized void method2(){ System.out.println("Method 2 start"); try { System.out.println("Method 2 execute"); Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Method 2 end"); } public static void main(String[] args) { final SynchronizedTest test = new SynchronizedTest(); final SynchronizedTest test2 = new SynchronizedTest(); new Thread(new Runnable() { @Override public void run() { test.method1(); } }).start(); new Thread(new Runnable() { @Override public void run() { test2.method2(); } }).start(); } }
実行結果は以下の通りです。静的メソッドの同期は本質的にクラスの同期です (静的メソッドは本質的にクラスに属するメソッドであり、オブジェクトのメソッドではありません)。そのため、test と test2 が異なるオブジェクトに属していても、両方とも SynchronizedTest クラスのインスタンスに属します。 Method1 と Method2 は順次にのみ実行でき、同時に実行することはできません。
メソッド 1 開始
メソッド 1 実行
メソッド 1 終了
メソッド 2 開始
メソッド 2 実行
メソッド 2 終了
4. コードブロックの同期
コードセグメント 4:
package com.paddx.test.concurrent; public class SynchronizedTest { public void method1(){ System.out.println("Method 1 start"); try { synchronized (this) { System.out.println("Method 1 execute"); Thread.sleep(3000); } } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Method 1 end"); } public void method2(){ System.out.println("Method 2 start"); try { synchronized (this) { System.out.println("Method 2 execute"); Thread.sleep(1000); } } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Method 2 end"); } public static void main(String[] args) { final SynchronizedTest test = new SynchronizedTest(); new Thread(new Runnable() { @Override public void run() { test.method1(); } }).start(); new Thread(new Runnable() { @Override public void run() { test.method2(); } }).start(); } }
実行結果は次のとおりです。スレッド 1 とスレッド 2 は対応するメソッドに入って実行を開始しましたが、スレッド 2 は同期ブロックに入る前にスレッド 1 の同期ブロックが完了するまで待つ必要があります。
方法 1 開始
方法 1 実行
方法 2 開始
方法 1 終了
方法 2 実行
方法 2 終了
2. 上記の実行結果についてまだ疑問がある場合は、心配しないでください。まずは同期の原理を理解しましょう。そうすれば、上記の疑問は一目瞭然です。まず、次のコードを逆コンパイルして、Synchronized がコード ブロックをどのように同期するかを見てみましょう:
package com.paddx.test.concurrent; public class SynchronizedDemo { public void method() { synchronized (this) { System.out.println("Method 1 start"); } } }
逆コンパイル結果:
これら 2 つの命令の役割については、JVM 仕様の説明を直接参照します:
monitorenter:
Each object is associated with a monitor. A monitor is locked if and only if it has an owner. The thread that executes monitorenter attempts to gain ownership of the monitor associated with objectref, as follows: • If the entry count of the monitor associated with objectref is zero, the thread enters the monitor and sets its entry count to one. The thread is then the owner of the monitor. • If the thread already owns the monitor associated with objectref, it reenters the monitor, incrementing its entry count. • If another thread already owns the monitor associated with objectref, the thread blocks until the monitor's entry count is zero, then tries again to gain ownership.
この文章の一般的な意味は次のとおりです:
各オブジェクトにはモニター ロック (モニター) があります。モニターが占有されている場合、スレッドはモニターの所有権を取得しようとします:
1。 、スレッドはモニターに入り、エントリー番号を 1 に設定します。 、このスレッドはモニターの所有者です。
2. スレッドが既にモニターを占有していて再入する場合、モニターへのエントリー数は 1 つ増加します。
3. 他のスレッドが既にモニターを占有している場合、スレッドはブロック状態になります。モニターへのエントリー数が 0 になるまで、モニターの所有権を再度取得してみます。
monitorexit:
The thread that executes monitorexit must be the owner of the monitor associated with the instance referenced by objectref. The thread decrements the entry count of the monitor associated with objectref. If as a result the value of the entry count is zero, the thread exits the monitor and is no longer its owner. Other threads that are blocking to enter the monitor are allowed to attempt to do so.
この部分の一般的な意味は次のとおりです:
monitorexit を実行するスレッドは、objectref に対応するモニターの所有者でなければなりません。
命令が実行されると、モニターのエントリー番号が 1 ずつ減分されます。1 減分した後にエントリー番号が 0 になった場合、スレッドはモニターを終了し、モニターの所有者ではなくなります。このモニターによってブロックされている他のスレッドは、このモニターの所有権を取得しようとする可能性があります。
これらの 2 つの段落の説明を通じて、Synchronized の基本的なセマンティクスはモニター オブジェクトを通じて完了することが明確に理解できるはずです。実際、wait/notify およびその他のメソッドもモニター オブジェクトに依存します。そのため、wait/notify などのメソッドのみを同期されたブロックまたはメソッドでのみ呼び出すことができ、それ以外の場合は java.lang.IllegalMonitorStateException がスローされます。
同期メソッドの逆コンパイル結果を見てみましょう:
ソースコード:
package com.paddx.test.concurrent; public class SynchronizedMethod { public synchronized void method() { System.out.println("Hello World!"); } }
从反编译的结果来看,方法的同步并没有通过指令monitorenter和monitorexit来完成(理论上其实也可以通过这两条指令来实现),不过相对于普通方法,其常量池中多了ACC_SYNCHRONIZED标示符。JVM就是根据该标示符来实现方法的同步的:当方法调用时,调用指令将会检查方法的 ACC_SYNCHRONIZED 访问标志是否被设置,如果设置了,执行线程将先获取monitor,获取成功之后才能执行方法体,方法执行完后再释放monitor。在方法执行期间,其他任何线程都无法再获得同一个monitor对象。 其实本质上没有区别,只是方法的同步是一种隐式的方式来实现,无需通过字节码来完成。
三、运行结果解释
有了对Synchronized原理的认识,再来看上面的程序就可以迎刃而解了。
1、代码段2结果:
虽然method1和method2是不同的方法,但是这两个方法都进行了同步,并且是通过同一个对象去调用的,所以调用之前都需要先去竞争同一个对象上的锁(monitor),也就只能互斥的获取到锁,因此,method1和method2只能顺序的执行。
2、代码段3结果:
虽然test和test2属于不同对象,但是test和test2属于同一个类的不同实例,由于method1和method2都属于静态同步方法,所以调用的时候需要获取同一个类上monitor(每个类只对应一个class对象),所以也只能顺序的执行。
3、代码段4结果:
对于代码块的同步实质上需要获取Synchronized关键字后面括号中对象的monitor,由于这段代码中括号的内容都是this,而method1和method2又是通过同一的对象去调用的,所以进入同步块之前需要去竞争同一个对象上的锁,因此只能顺序执行同步块。
四 总结
Synchronized是Java并发编程中最常用的用于保证线程安全的方式,其使用相对也比较简单。但是如果能够深入了解其原理,对监视器锁等底层知识有所了解,一方面可以帮助我们正确的使用Synchronized关键字,另一方面也能够帮助我们更好的理解并发编程机制,有助我们在不同的情况下选择更优的并发策略来完成任务。对平时遇到的各种并发问题,也能够从容的应对。
更多Java 并发编程学习笔记之Synchronized简介相关文章请关注PHP中文网!