1、synchronized 和 Lock 孰優(yōu)孰劣,如何選擇?
相同點
synchronized 和 Lock 的相同點非常多,重點講解 3 個比較大的相同點。
1)synchronized 和 Lock 都是用來保護資源線程安全的。
2)都可以保證可見性。
對于 synchronized 而言,線程 A 在進入 synchronized 塊之前或在 synchronized 塊內進行操作,對于后續(xù)的獲得同一個 monitor 鎖的線程 B 是可見的,也就是線程 B 是可以看到線程 A 之前的操作的,這也體現(xiàn)了 happens-before 針對 synchronized 的一個原則。

而對于 Lock 而言,它和 synchronized 是一樣,都可以保證可見性,如圖所示,在解鎖之前的所有操作對加鎖之后的所有操作都是可見的。

3)synchronized 和 ReentrantLock 都擁有可重入的特點。
這里的 ReentrantLock 是 Lock 接口的一個最主要的實現(xiàn)類,在對比 synchronized 和 Lock 的時候,也會選擇 Lock 的主要實現(xiàn)類來進行對比??芍厝胫傅氖悄硞€線程如果已經(jīng)獲得了一個鎖,現(xiàn)在試圖再次請求這個它已經(jīng)獲得的鎖,如果它無需提前釋放這個鎖,而是直接可以繼續(xù)使用持有的這個鎖,那么就是可重入的。如果必須釋放鎖后才能再次申請這個鎖,就是不可重入的。
不同點
synchronized 和 Lock 的不同點非常多,重點講解 7 個比較大的不同點。
1)用法區(qū)別
synchronized 關鍵字可以加在方法上,不需要指定鎖對象(此時的鎖對象為 this),也可以新建一個同步代碼塊并且自定義 monitor 鎖對象;而 Lock 接口必須顯示用 Lock 鎖對象開始加鎖 lock() 和解鎖 unlock(),并且一般會在 finally 塊中確保用 unlock() 來解鎖,以防發(fā)生死鎖。
與 Lock 顯式的加鎖和解鎖不同的是 synchronized 的加解鎖是隱式的,尤其是拋異常的時候也能保證釋放鎖,但是 Java 代碼中并沒有相關的體現(xiàn)。
2)加解鎖順序不同
對于 Lock 而言如果有多把 Lock 鎖,Lock 可以不完全按照加鎖的反序解鎖,比如我們可以先獲取 Lock1 鎖,再獲取 Lock2 鎖,解鎖時則先解鎖 Lock1,再解鎖 Lock2,加解鎖有一定的靈活度,如代碼所示。
lock1.lock();
lock2.lock();
...
lock1.unlock();
lock2.unlock();
但是 synchronized 無法做到,synchronized 解鎖的順序和加鎖的順序必須完全相反,例如:
synchronized(obj1){
synchronized(obj2){
...
}
}
那么在這里,順序就是先對 obj1 加鎖,然后對 obj2 加鎖,然后對 obj2 解鎖,最后解鎖 obj1。這是因為 synchronized 加解鎖是由 JVM 實現(xiàn)的,在執(zhí)行完 synchronized 塊后會自動解鎖,所以會按照 synchronized 的嵌套順序加解鎖,不能自行控制。
3)synchronized 鎖不夠靈活
一旦 synchronized 鎖已經(jīng)被某個線程獲得了,此時其他線程如果還想獲得,那它只能被阻塞,直到持有鎖的線程運行完畢或者發(fā)生異常從而釋放這個鎖。如果持有鎖的線程持有很長時間才釋放,那么整個程序的運行效率就會降低,而且如果持有鎖的線程永遠不釋放鎖,那么嘗試獲取鎖的線程只能永遠等下去。
相比之下,Lock 類在等鎖的過程中,如果使用的是 lockInterruptibly 方法,那么如果覺得等待的時間太長了不想再繼續(xù)等待,可以中斷退出,也可以用 tryLock() 等方法嘗試獲取鎖,如果獲取不到鎖也可以做別的事,更加靈活。
4)synchronized 鎖只能同時被一個線程擁有,但是 Lock 鎖沒有這個限制。
例如在讀寫鎖中的讀鎖,可以同時被多個線程持有。
5)原理區(qū)別
synchronized 是內置鎖,由 JVM 實現(xiàn)獲取鎖和釋放鎖的原理,還分為偏向鎖、輕量級鎖、重量級鎖。
Lock 根據(jù)實現(xiàn)不同,有不同的原理,例如 ReentrantLock 內部是通過 AQS 來獲取和釋放鎖的。
6)是否可以設置公平/非公平
公平鎖是指多個線程在等待同一個鎖時,根據(jù)先來后到的原則依次獲得鎖。ReentrantLock 等 Lock 實現(xiàn)類可以根據(jù)自己的需要來設置公平或非公平,synchronized 則不能設置。
7)性能區(qū)別
在 Java 5 以及之前,synchronized 的性能比較低,但是到了 Java 6 以后,發(fā)生了變化,因為 JDK 對 synchronized 進行了很多優(yōu)化,比如自適應自旋、鎖消除、鎖粗化、輕量級鎖、偏向鎖等,所以后期的 Java 版本里的 synchronized 的性能并不比 Lock 差。
如何選擇
1)如果能不用最好既不使用 Lock 也不使用 synchronized。因為在許多情況下可以使用 java.util.concurrent 包中的機制,它會為你處理所有的加鎖和解鎖操作,也就是推薦優(yōu)先使用工具類來加解鎖。
2)如果 synchronized 關鍵字適合程序, 那么盡量使用它,這樣可以減少編寫代碼的數(shù)量,減少出錯的概率。因為一旦忘記在 finally 里 unlock,代碼可能會出很大的問題,而使用 synchronized 更安全。
3)如果特別需要 Lock 的特殊功能,比如嘗試獲取鎖、可中斷、超時功能等,才使用 Lock。