Synchronized
官方解釋:
同步方法支持一種簡單的策略來防止線程干擾和內存一致性錯誤:如果一個對象對多個線程可見,則對該對象變量的所有讀取或寫入都是通過同步方法完成的。
一句話總結出Synchronized的作用:
能夠保證在同一時刻最多只有一個線程執行該段代碼,以達到保證並發安全的效果
Synchronized的地位:
synchronized是Java的關鍵字,被Java語言原生支持
是最基本的互斥同步手段
必學
synchronized的兩個用法
對象鎖:
包含方法鎖(默認鎖對象為this當前實力對象),同步代碼塊鎖(自己制定鎖對象)
類鎖:
指sychronized修飾靜態的方法或指鎖為Class對象
第一個用法:對象鎖
代碼塊形式:手動指定鎖對象
方法鎖形式:synchronized修飾普通方法,鎖默認對象為this
第二個用法:類鎖
概念:java類可能有有很多個對象,但是只有一個class對象
本質:所以所謂的類鎖,不過是Class對象的鎖而已
用法和效果:類鎖只能在同一時刻被一個對象擁有
形式1:synchronized加載static方法上
形式2:synchronized(*.class)代碼塊
多線程訪問同步方法的7種情況
1.兩個線程同時訪問一個對象的同步方法
串行
2.兩個線程訪問的是兩個對象的同步方法
鎖對象不同,互不干擾,並行
3.如果兩個線程訪問的是Synchronized的靜態方法
串行
4.同時訪問同步方法與非同步方法
並行
5.訪問同一對象的不同的普通同步方法
同一對象鎖,串行
6.同時訪問靜態synchronized和非靜態synchronized方法
鎖不同,並行
7.方法拋異常后,會釋放鎖嗎
如果一個線程在進入同步方法后拋出了異常,則另一個線程會立刻進入該同步方法
7種情況的總結
1.一把鎖只能同時被一個線程獲取,沒有拿到鎖的線程必須等待(對應1,5種情況)
2.每個實例都對應有自己的一把鎖,不同實例之間互不影響,例如,鎖對象是*.class以及synchronized修飾的
是static方法的時候,所有對象共用同一把鎖(對應第2,3,4,6種情況)
3.無論是方法正常執行完步或者方法拋出異常,都會釋放鎖(對應第7種情況)
性質
1.可重入
什么是可重入(遞歸鎖)-----例如:Synchronized,ReentranLock
指的是同一線程的外層方法獲得獲得鎖之后,內層方法可以直接在此獲取該鎖
例如: 買完車,需要搖號上車牌(鎖),這樣才能開車(執行相應的方法).
如果我有三輛車,但只有一個車牌,顯然只能給一輛車上車牌----獲取鎖后只能執行一個方法
---------------為不可重入性
反之:一個車牌,三輛車都可以開
---------------為可重入性
好處:
避免死鎖,提高封裝性
粒度(范圍):
可重入粒度測試: 證明線程而非調用(用三種情況來說明和pthread的區別)
情況一:證明同一方法是可重入的---遞歸調用本方法
package cn.cg; /** * 可重入測試----使用遞歸方式 * 情況1:同一個類中,同一方法可重入 */ public class SynchronizedTest { int i = 0; //主線程可以重入以this為鎖對象的method方法 public static void main(String[] args) { SynchronizedTest s1 = new SynchronizedTest(); s1.method(); } // 同步方法 private synchronized void method() { if (i <=3) { i++; System.out.println(i); method(); } } }
測試通過.
情況二:證明可重入不要求是同一個方法
package cn.cg; /** * 可重入測試---- * 情況2:同一個類中,不同方法可重入 */ public class SynchronizedTest2 { //主線程可以重入以this為鎖對象的method方法 public static void main(String[] args) { SynchronizedTest2 s1 = new SynchronizedTest2(); s1.method1(); } // 同步方法 private synchronized void method1() { System.out.println("method1"); method2(); } private synchronized void method2() { System.out.println("method2"); } }
測試通過
情況三:證明可重入不要求是同一個類中的
public class Demo1 { public synchronized void method(){ System.out.println("我是Demo1"); } } class Demo1Zi extends Demo1{ public synchronized void method(){ System.out.println("我是Demo1兒子"); super.method(); } public static void main(String[] args) { Demo1Zi zi = new Demo1Zi(); zi.method(); } }
測試通過
2.不可中斷
一旦這個鎖已經被別人獲得了,如果我還想獲取,我只能選擇等待或者阻塞,直到別的線程釋放這個鎖.如果別人永遠不釋放鎖,那么我只能永遠地等下去
相比之下,Lock類,可以擁有中斷的能力,
第一點:如果我覺得我等的時間太長了,有權中斷現在已經獲取到鎖的線程的執行,
第二點:如果我絕得我等待的時間太長不想再等了,也可以退出.
加鎖和釋放鎖的原理
現象
每一個類的實例對應一把鎖,每次調用被synchronized修飾的方法都必須首先獲得該方法所屬調用者實例的鎖.否則線程阻塞,
方法一旦執行,該線程就獨占了該鎖知道該方法返回或者拋出異常,才能釋放鎖.
獲取和釋放鎖的時機:內置鎖(監視器鎖)
等價代碼
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class Demo2 { private Lock lock = new ReentrantLock(); public synchronized void method1(){ System.out.println("我是synchronized鎖住的方法"); } public void method2(){ //加鎖 lock.lock(); try { System.out.println("我是ReentranLock鎖住的方法"); }finally { lock.unlock(); } } public static void main(String[] args) { Demo2 demo2 = new Demo2(); demo2.method1(); demo2.method2(); } }
synchronized的缺點
1.效率低:鎖的釋放情況少,試圖獲得鎖時不能設定超時,不能中斷一個正在試圖獲得所得線程
2.不夠靈活(讀寫鎖更靈活):加鎖和釋放的時機單一,每個鎖僅有單一的條件(某個對象),可能是不夠的
3.無法知道是否成功獲取到鎖