目錄
一、Synchronized的基本使用
1、沒有同步的情況:
2、對普通方法同步:
3、靜态方法(類)同步
4、代碼塊同步
二、Synchronized 原理
1、同步代碼塊:
三、運作結果解釋
四 、總結
回到頂部
一、Synchronized的基本使用
關于Synchronized在JVM的原理(偏向鎖,輕量級鎖,重量級鎖)可以參考 :
http://www.cnblogs.com/dennyzhangdd/p/6734638.html
Synchronized是Java中解決并發問題的一種最常用的方法,也是最簡單的一種方法。
Synchronized的作用主要有三個:
(1)確定線程互斥的通路同步代碼
(2)保證共享變量的修改能夠及時可見
(3)有效解決重排序問題。
從文法上講,Synchronized總共有三種用法:
(1)修飾普通方法
(2)修飾靜态方法
(3)修飾代碼塊
接下來我就通過幾個例子程式來說明一下這三種使用方式(為了便于比較,三段代碼除了Synchronized的使用方式不同以外,其他基本保持一緻)。
1、沒有同步的情況:
代碼段一:
package cn.com.jdk.thread;
public class SynchronizedTest1 {
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 SynchronizedTest1 test = new SynchronizedTest1();
new Thread(new Runnable() {
@Override
public void run() {
test.method1();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
test.method2();
}
}).start();
}
}
執行結果如下:
1 Method 1 start
2 Method 1 execute
3 Method 2 start
4 Method 2 execute
5 Method 2 end
6 Method 1 end
線程1和線程2同時進入執行狀态,線程2執行速度比線程1快,是以線程2先執行完成,這個過程中線程1和線程2是同時執行的。
2、對普通方法同步:
代碼段二:
package cn.com.jdk.thread;
public class SynchronizedTest2 {
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 SynchronizedTest2 test = new SynchronizedTest2();
new Thread(new Runnable() {
@Override
public void run() {
test.method1();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
test.method2();
}
}).start();
}
}
執行結果如下:
1 Method 1 start
2 Method 1 execute
3 Method 1 end
4 Method 2 start
5 Method 2 execute
6 Method 2 end
跟代碼段一比較,可以很明顯的看出,線程2需要等待線程1的method1執行完成才能開始執行method2方法(也有可能先執行線程2,線程1需要等待線程2的method2執行完成才能開始執行method1方法),方法級别串行執行。
3、靜态方法(類)同步
代碼段三:
package cn.com.jdk.thread;
public class SynchronizedTest3 {
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 SynchronizedTest3 test = new SynchronizedTest3();
final SynchronizedTest3 test2 = new SynchronizedTest3();
new Thread(new Runnable() {
@Override
public void run() {
test.method1();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
test2.method2();
}
}).start();
}
}
執行結果如下:
1 Method 1 start
2 Method 1 execute
3 Method 1 end
4 Method 2 start
5 Method 2 execute
6 Method 2 end
對靜态方法的同步本質上是對類的同步(靜态方法本質上是屬于類的方法,而不是對象上的方法),是以即使test和test2屬于不同的對象,但是它們都屬于SynchronizedTest類的執行個體,是以也隻能順序的執行method1和method2,(也有可能是線程2先執行,順序執行method2和method1)不能并發執行。
4、代碼塊同步
代碼段四:
package cn.com.jdk.thread;
public class SynchronizedTest4 {
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 SynchronizedTest4 test = new SynchronizedTest4();
new Thread(new Runnable() {
@Override
public void run() {
test.method1();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
test.method2();
}
}).start();
}
}
執行結果如下:
1 Method 1 start
2 Method 2 start
3 Method 1 execute
4 Method 1 end
5 Method 2 execute
6 Method 2 end
雖然線程1和線程2都進入了對應的方法開始執行,但是線程2在進入同步塊之前,需要等待線程1中同步塊執行完成,代碼塊級别串行。
回到頂部
二、Synchronized 原理
實際上,JVM隻區分兩種不同用法 1.修飾代碼塊 2.修飾方法。上SE8規範:http://docs.oracle.com/javase/specs/jvms/se8/html/jvms-3.html#jvms-3.14
上圖中,紅框框中說明了,1.monitorenter+monitorexit(上圖中的onlyMe方法中同步代碼塊) 2.修飾方法
如果對上面的執行結果還有疑問,也先不用急,我們先來了解Synchronized的原理,再回頭上面的問題就一目了然了。
1、同步代碼塊:
我們先通過反編譯下面的代碼來看看Synchronized是如何實作對代碼塊進行同步的:
public class SynchronizedDemo {
public void method (){
synchronized (this) {
System.out.println("method 1 start!!!!");
}
}
}
javac -encoding utf-8 SynchronizedDemo.java 編譯生成class 後,javap -c 反編譯一下,看指令:
這裡着重分析2個monitorenter、monitorexit這兩個指令。這裡以JSE8位為準,查到屬于JVM指令集。官網各種API、JVM規範,指令等,傳送門:http://docs.oracle.com/javase/8/docs/。
1.1,monitorenter螢幕準入指令
關于這兩條指令的作用,我們直接參考JVM規範中描述:
這段話的大概意思為:
每個對象有一個螢幕鎖(monitor)。當monitor被占用時就會處于鎖定狀态,線程執行monitorenter指令時嘗試擷取monitor的所有權,過程如下:
1、如果monitor的進入數為0,則該線程進入monitor,然後将進入數設定為1,該線程即為monitor的所有者。
2、如果線程已經占有該monitor,隻是重新進入,則進入monitor的進入數加1.
3.如果其他線程已經占用了monitor,則該線程進入阻塞狀态,直到monitor的進入數為0,再重新嘗試擷取monitor的所有權。
1.2,monitorexit螢幕釋放指令
這段話的大概意思為:
1,執行monitorexit的線程必須是objectref所對應的monitor的所有者。
2,指令執行時,monitor的進入數減1,如果減1後進入數為0,那線程退出monitor,不再是這個monitor的所有者。其他被這個monitor阻塞的線程可以嘗試去擷取這個 monitor 的所有權。
通過這兩段描述,我們應該能很清楚的看出Synchronized的實作原理,Synchronized的語義底層是通過一個monitor的對象來完成,其實wait/notify等方法也依賴于monitor對象,這就是為什麼隻有在同步的塊或者方法中才能調用wait/notify等方法,否則會抛出java.lang.IllegalMonitorStateException的異常的原因。
2、同步方法
我們再來看一下同步方法的反編譯結果:
源代碼:
package cn.com.jdk.thread;
public class SynchronizedDemo0 {
public synchronized void method (){
System.out.println("method start!!!!");
}
}
反編譯結果:
從反編譯的結果來看,方法的同步并沒有通過指令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關鍵字,另一方面也能夠幫助我們更好的了解并發程式設計機制,有助我們在不同的情況下選擇更優的并發政策來完成任務。對平時遇到的各種并發問題,也能夠從容的應對。
本文轉載了
全文參考:
https://www.cnblogs.com/dennyzhangdd/p/6670307.html#_labelTop