synchronized,是Java語言的關鍵字,讀['siŋkrənaizd],當它用來修飾一個方法或者一個代碼塊的時候,能夠保證在同一時刻最多只有一個線程執行該段代碼。
一、Java為何要使用synchronized?
線程的同步是為了防止多個線程訪問一個數據對象時,對數據造成的破壞。為確保共享變量不會出現並發問題,通常會對修改共享變量的代碼塊用synchronized
加鎖,確保同一時刻只有一個線程在修改共享變量,從而避免並發問題。所以需要牢牢記住“共享”這兩個字,只有共享資源的讀寫訪問才需要同步化,如果不是共享資源,那么就沒有同步的必要。
二、synchronized修飾范圍
-
修飾實例方法 > 多個線程訪問同一個實例的加鎖方法時,會出現鎖的競爭
-
修飾靜態方法 > 多個線程訪問類的加鎖方法時,會出現鎖的競爭
-
修飾代碼塊 > 多線程訪問到同一個代碼塊時,會出現競爭的問題
三、synchronized同步鎖對象
synchronized可以用來修飾方法或代碼塊,我們可以把獲取的同步鎖歸為以下3種:
- 實例對象鎖:修飾在普通方法上(非靜態方法);在代碼塊中修飾this即synchronized(this)代碼塊
- 類對象鎖:修飾在靜態方法上;在代碼塊中修飾class即synchronized(X.class)代碼塊
- 同步塊非當前實例對象鎖:在代碼塊中修飾非當前實例對象,比如在X類中synchronized(對象a)的代碼
對這3種不同的鎖,使用相互之間不受影響,對於同一種鎖,會出現鎖的競態條件。
四、synchronized同步鎖使用
對於1.實例對象鎖的使用:
- 所有的非靜態同步方法用的都是同一把鎖—實例對象本身,也就是說如果一個實例對象的非靜態同步方法獲取鎖后,該實例對象的其他非靜態同步方法必須等待獲取鎖的方法釋放鎖后才能獲取鎖。
- 實例對象鎖存在於當前實例,不同的實例對象的鎖相互之間不受影響。
- synchronized(this)代碼塊獲取的是該實例對應的鎖,與非靜態的synchronized同步方法使用的是同一把鎖,會出現鎖的競爭。
- 實例鎖和類對象鎖沒有影響,不會造成彼此阻塞。
對於2.類對象鎖的使用:
- 對於靜態同步方法,鎖是針對這個類的,鎖對象是該類的Class對象。
- 而所有的靜態同步方法用的也是同一把鎖——類對象本身,如果一個靜態同步方法獲取鎖后,其他的靜態同步方法都必須等待該方法釋放鎖后才能獲取鎖。
- 不管是同一個實例對象的靜態同步方法之間,還是不同的實例對象的靜態同步方法之間,他們都使用的是同一個類的對象鎖,會出現鎖的競爭。
- synchronized(X.class)代碼塊使用的是類Class的對象鎖,與類中靜態同步方法會出現鎖的競爭。多個類中的synchronized(X.class)代碼塊也會出現鎖的競態條件。
對於3.同步塊非當前實例對象鎖的使用:
- 對於同步塊,由於其對象鎖是可以選擇的,只有使用同一把鎖的同步塊之間才有着競態條件。
- 同步鎖的對象是基於實際對象而不是對象引用的,所以使用時特別注意,在鎖的作用域中因改變實際對象引用從而引起鎖的對象改變導致同步鎖失去競太條件。
五、synchronized使用小結
關於鎖和同步的使用,匯總以下幾個要點:
1、同步鎖只能通過同步方法去保證共享變量安全,而不是同步變量和類。
2、當提到同步時,應該清楚在什么上同步?也就是說,在哪個對象上同步?
3、不必同步類中所有的方法,類可以同時擁有同步和非同步方法。
4、對於出現競態條件鎖的線程,一個線程獲取了鎖,其他線程會阻塞等待該鎖的釋放去獲取該鎖。
5、如果線程擁有同步和非同步方法,則非同步方法可以被多個線程自由訪問而不受鎖的限制。
6、線程睡眠時,它所持的任何鎖都不會釋放。
7、線程可以獲得多個重進入(synchronized )鎖。比如,在一個對象的同步方法里面調用另外一個對象的同步方法,則獲取了兩個對象的同步鎖。
8、同步損害並發性,應該盡可能縮小同步范圍。同步不但可以同步整個方法,還可以同步方法中一部分代碼塊。
9、在使用同步代碼塊時候,應該指定在哪個對象上同步,也就是說要獲取哪個對象的鎖
10、編寫線程安全的類,需要時刻注意對多個線程競爭訪問資源的邏輯和安全做出正確的判斷,對“原子”操作做出分析,並保證原子操作期間別的線程無法訪問。
3.測試case代碼

package com.test.synchroniz; public class Service { public Service(){ System.out.println("當前線程:" + Thread.currentThread().getName() + " 構造方法"); } static{ System.out.println("當前線程:" + Thread.currentThread().getName() + " 靜態代碼塊"); } private Object object1 = new Object(); private Object object2 = new Object(); synchronized public static void printA(){ try{ System.out.println("當前線程:" + Thread.currentThread().getName() + Thread.currentThread().getId() +"在 " + System.currentTimeMillis() + " 進入方法A"); Thread.sleep(3000); System.out.println("當前線程:" + Thread.currentThread().getName() + Thread.currentThread().getId() +"在 " + System.currentTimeMillis() + " 退出方法A"); }catch(Exception e){ System.out.println(e); } } synchronized public void printB(){ try{ System.out.println("當前線程:" + Thread.currentThread().getName() + Thread.currentThread().getId() +"在 " + System.currentTimeMillis() + " 進入方法B"); Thread.sleep(3000); System.out.println("當前線程:" + Thread.currentThread().getName() + Thread.currentThread().getId() +"在 " + System.currentTimeMillis() + " 退出方法B"); }catch(Exception e){ System.out.println(e); } } public void printC(){ System.out.println("當前線程:" + Thread.currentThread().getName() + Thread.currentThread().getId() +"在 " + System.currentTimeMillis() + " 進入方法C"); try{ synchronized(object1){ System.out.println("當前線程:" + Thread.currentThread().getName() + Thread.currentThread().getId() +"在 " + System.currentTimeMillis() + "進入方法C--synchronized{X}"); Thread.sleep(3000); System.out.println("當前線程:" + Thread.currentThread().getName() + Thread.currentThread().getId() +"在 " + System.currentTimeMillis() + " 退出方法C-synchronized{X}"); } System.out.println("當前線程:" + Thread.currentThread().getName() + Thread.currentThread().getId() +"在 " + System.currentTimeMillis() + " 退出方法C"); }catch(Exception e){ System.out.println(e); } } public void printD(){ System.out.println("當前線程:" + Thread.currentThread().getName() + Thread.currentThread().getId() +"在 " + System.currentTimeMillis() + " 進入方法D"); try{ synchronized(object2){ System.out.println("當前線程:" + Thread.currentThread().getName() + Thread.currentThread().getId() +"在 " + System.currentTimeMillis() + "進入方法D--synchronized{X}"); Thread.sleep(3000); System.out.println("當前線程:" + Thread.currentThread().getName() + Thread.currentThread().getId() +"在 " + System.currentTimeMillis() + " 退出方法D-synchronized{X}"); } System.out.println("當前線程:" + Thread.currentThread().getName() + Thread.currentThread().getId() +"在 " + System.currentTimeMillis() + " 退出方法D"); }catch(Exception e){ System.out.println(e); } } public void printE(){ System.out.println("當前線程:" + Thread.currentThread().getName() + Thread.currentThread().getId() +"在 " + System.currentTimeMillis() + " 進入方法E"); try{ synchronized(this){ System.out.println("當前線程:" + Thread.currentThread().getName() + Thread.currentThread().getId() +"在 " + System.currentTimeMillis() + " 進入方法E--synchronized{this}"); Thread.sleep(3000); System.out.println("當前線程:" + Thread.currentThread().getName() + Thread.currentThread().getId() +"在 " + System.currentTimeMillis() + " 退出方法E-synchronized{this}"); } System.out.println("當前線程:" + Thread.currentThread().getName() + Thread.currentThread().getId() +"在 " + System.currentTimeMillis() + " 退出方法E"); }catch(Exception e){ System.out.println(e); } } public static void printF(){ System.out.println("當前線程:" + Thread.currentThread().getName() + Thread.currentThread().getId() +"在 " + System.currentTimeMillis() + " 進入方法E"); try{ synchronized(Service.class){ System.out.println("當前線程:" + Thread.currentThread().getName() + Thread.currentThread().getId() +"在 " + System.currentTimeMillis() + " 進入方法F--synchronized{class}"); Thread.sleep(3000); System.out.println("當前線程:" + Thread.currentThread().getName() + Thread.currentThread().getId() +"在 " + System.currentTimeMillis() + " 退出方法F-synchronized{class}"); } System.out.println("當前線程:" + Thread.currentThread().getName() + Thread.currentThread().getId() +"在 " + System.currentTimeMillis() + " 退出方法F"); }catch(Exception e){ System.out.println(e); } } public void printG(){ System.out.println("當前線程:" + Thread.currentThread().getName() + Thread.currentThread().getId() +"在 " + System.currentTimeMillis() + " 進入方法G"); try{ synchronized(Service.class){ System.out.println("當前線程:" + Thread.currentThread().getName() + Thread.currentThread().getId() +"在 " + System.currentTimeMillis() + " 進入方法G--synchronized{class}"); Thread.sleep(3000); System.out.println("當前線程:" + Thread.currentThread().getName() + Thread.currentThread().getId() +"在 " + System.currentTimeMillis() + " 退出方法G-synchronized{class}"); } System.out.println("當前線程:" + Thread.currentThread().getName() + Thread.currentThread().getId() +"在 " + System.currentTimeMillis() + " 退出方法G"); }catch(Exception e){ System.out.println(e); } } }

package com.test.synchroniz; /** * 0.synchronized同步方法、synchronized靜態同步方法分別是用到的是實例鎖,類鎖,一個線程獲取到synchronized同步方法的鎖時, * 另一線程依然可以進入synchronized靜態同步方法(實例鎖,類鎖兩者不同,相互不影響 * ) * 1.synchronized同步方法,synchronized(this)都是對象鎖,對於其他線程調用synchronized同步方法,synchronized(this)呈阻塞狀態 </br> * 2.同一時間同一線程只有一個線程獲取對象鎖執行 </br> * * 1.synchronized(非this)對象鎖,對於非this如果是同一對象,兩個線程同時只有一個可以獲取該鎖 </br> * 2.對象鎖(synchronized同步方法 或 synchronized(this))、synchronized(非this)對象鎖 兩個線程同時執行,都可獲得各自的鎖 </br> * * 1.synchronized修飾static方法與synchronized(X.class)作用一樣 * * @author fugaoyang * */ public class TestRun { public static void main(String[] args) throws Exception { Service service = new Service(); Thread threadA = new Thread("A"){ @Override public void run(){ service.printA(); } }; Thread threadB = new Thread("B"){ @Override public void run(){ service.printB(); } }; Thread threadC = new Thread("C"){ @Override public void run(){ service.printC(); } }; Thread threadD = new Thread("D"){ @Override public void run(){ service.printD(); } }; Thread threadE = new Thread("E"){ @Override public void run(){ service.printE(); } }; Thread threadF = new Thread("F"){ @Override public void run(){ service.printF(); } }; Thread threadG = new Thread("G"){ @Override public void run(){ service.printG(); } }; threadA.start(); //threadB.start(); //threadC.start(); //threadD.start(); //threadE.start(); threadF.start(); threadG.start(); threadA.join(); threadF.join(); threadG.join(); } }
本篇文章主要總結了synchronized同步鎖的使用,同步鎖的原理和機制后續會做深入了解和學習。
本文參考部分出處:
3.《Java多線程編程核心技術-高洪嚴》