最近看了一篇大佬的博文,深有體會,來了一手依瓢畫葫蘆仿寫一篇關於synchronized關鍵字思考的博文。
在Java中,synchronized關鍵字是用來控制線程同步的,就是在多線程的環境下,控制synchronized代碼段不被多個線程同時執行。synchronized既可以加在一段代碼上,也可以加在方法上。
然而,給方法或代碼塊上加上synchronized關鍵字就萬事大吉了嗎?不妨運行一下下面這段代碼試試:
測試代碼
import java.util.concurrent.TimeUnit;
// synchronized鎖住的究竟是什麼?
public class ThreadTest3 implements Runnable {
private final Object Mutex=new Object();
//方法1 static 共享對象
@Override
public void run(){
//方法2 synchronized (ThreadTest3.class)
/*
synchronized(Sync.class)實現了全局鎖的效果。
static synchronized方法,static方法可以直接類名加方法名調用,方法中無法使用this,所以它鎖的不是this,而是類的Class對象,
所以,static synchronized方法也相當於全局鎖,相當於鎖住了代碼段。
*/
synchronized (Mutex){
System.out.println("開始進行中----");
try {
//模擬程序進行過程
TimeUnit.MILLISECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("結束");
}
}
public static void main(String[] args){
// 方法3 ThreadTest3 t=new ThreadTest3();
for(int i=0;i<3;i++){
ThreadTest3 t=new ThreadTest3();
Thread newThread=new Thread(t);
newThread.start();
}
}
可見結果如下:
開始進行中—-
開始進行中—-
開始進行中—-
結束
結束
結束
程序並沒有如我們想象中的那麼運行,原因是什麼呢,這要看synchronized究竟是對什麼起作用了,是代碼?還是對象? 相信當你把測試代碼瀏覽一遍的時候你心中已經有了答案。
接下來,驗證你的猜測吧,在測試代碼註釋中有三種方法我們來測試一下:
- 將Mutex對象定義爲靜態變量,實現共享資源的唯一性。
- 將運用了synchronized關鍵字的方法改寫,改爲鎖住ThreadTest3.class這個對象
- 在測試運行的時候將循環下的第一句ThreadTest3的構造方法移到外面,從而只創建一個實例對象。
接下來結果驗證如下:
開始進行中—-
結束
開始進行中—-
結束
開始進行中—-
結束
可見,synchronized(this)以及非static的synchronized方法,只能防止多個線程同時執行同一個對象的同步代碼段,它鎖住的正是括號中的對象。
在測試代碼中我設置了一個私有變量Mutex以便理解,其實可以將它註釋掉,在源代碼中對應位置改爲synchronized(this)是一個道理。
而我們在代碼中所做的以上幾種修改其實也只有一個目的,那便是使它們鎖住的對象一致。
在這裏再多談一句關於“鎖”的理解,這種說法其實並不是很嚴謹,準確的來說應該是某線程獲取了與Mutex(對象)關聯的monitor鎖,爲了方便起見,我們便直接以鎖來稱呼。
Q:爲甚叫monitor鎖?
A:synchronized關鍵字包括monitor enter和monitor exit兩個JVM指令,它能夠保證在任何時候任何線程執行到monitor enter成功之前都必須從主內存中獲取數據,而不是從緩存中,在monitor exit運行成功之後,共享變量被更新後的值必須刷入主內存。
當synchronized鎖住一個對象後,別的線程如果也想拿到這個對象的monitor鎖 ,就必須等待這個線程執行完成釋放鎖,才能再次給對象加鎖,不然只能進入阻塞狀態。爲了達到線程同步的目的。即使兩個不同的代碼段,都要鎖住同一個引用(對象),這樣的話兩個代碼段便不能再多線程的狀態下併發執行。
由於synchronized關鍵字具有排他性,即所有線程必須串行地經過synchronized保護的共享區域,synchronized作用域越大,其效率也會越低,所以我們在進行開發的時候應該注意儘量控制synchronized的作用域。
PS: 一個小總結
對於非static用synchronized修飾的同步方法=方法內用synchronized(this)覆蓋代碼塊
以下兩條方法是等價的:
public synchronized void method(){
//執行代碼塊----
}
public void method(){
synchronized(this){
//執行代碼塊
}
}
- 對於static用synchronized修飾的靜態方法=靜態方法內用synchronized(類名.class)覆蓋代碼塊
以下兩條方法是等價的:
public static synchronized void method(){
//執行代碼塊
}
public static void method(){
synchronized(類名.class){
//執行代碼塊
}
}
參考資料
1.https://www.cnblogs.com/QQParadise/articles/5059824.html
2.汪文君.Java高併發編詳解,2018(1):63-76.