兩者在鎖的相關概念上區別:
1.可重新進入鎖
如果鎖具備可重新進入性,則稱作為可重新進入鎖。像synchronized和ReentrantLock都是可重新進入鎖,可重新進入性在我看來實際上表明了鎖的分配機制:基於線程的分配,而不是基於方法調用的分配。舉個簡單的例子,當一個線程執行到某個synchronized方法時,比如說method1,而在method1中會調用另外一個synchronized方法method2,此時線程不必重新去申請鎖,而是可以直接執行方法method2。
看下面這段代碼就明白了:
class MyClass { public synchronized void method1() { method2(); } public synchronized void method2() { }}
上述代碼中的兩個方法method1和method2都用synchronized修飾了,假如某一時刻,線程A執行到了method1,此時線程A擷取了這個對象的鎖,而由於method2也是synchronized方法,假如synchronized不具備可重新進入性,此時線程A需要重新申請鎖。但是這就會造成一個問題,因為線程A已經持有了該對象的鎖,而又在申請擷取該對象的鎖,這樣就會線程A一直等待永遠不會擷取到的鎖。
而由於synchronized和Lock都具備可重新進入性,所以不會發生上述現象。 2.可中斷鎖
可中斷鎖:顧名思義,就是可以相應中斷的鎖。
在Java中,synchronized就不是可中斷鎖,而Lock是可中斷鎖。
如果某一線程A正在執行鎖中的代碼,另一線程B正在等待擷取該鎖,可能由於等待時間過長,線程B不想等待了,想先處理其他事情,我們可以讓它中斷自己或者在別的線程中中斷它,這種就是可中斷鎖。
在前面示範lockInterruptibly()的用法時已經體現了Lock的可中斷性。
3.公平鎖
公平鎖即盡量以請求鎖的順序來擷取鎖。比如同是有多個線程在等待一個鎖,當這個鎖被釋放時,等待時間最久的線程(最先請求的線程)會獲得該所,這種就是公平鎖。
非公平鎖即無法保證鎖的擷取是按照請求鎖的順序進行的。這樣就可能導致某個或者一些線程永遠擷取不到鎖。
在Java中,synchronized就是非公平鎖,它無法保證等待的線程擷取鎖的順序。
而對於ReentrantLock和ReentrantReadWriteLock,它預設情況下是非公平鎖,但是可以設定為公平鎖。
看一下這2個類的原始碼就清楚了:
/** * Sync object for non-fair locks */ static final class NonfairSync extends Sync { private static final long serialVersionUID = 7316153563782823691L; /** * Performs lock. Try immediate barge, backing up to normal * acquire on failure. */ final void lock() { if (compareAndSetState(0, 1)) setExclusiveOwnerThread(Thread.currentThread()); else acquire(1); } protected final boolean tryAcquire(int acquires) { return nonfairTryAcquire(acquires); } } /** * Sync object for fair locks */ static final class FairSync extends Sync { private static final long serialVersionUID = -3000897897090466540L; final void lock() { acquire(1); } /** * Fair version of tryAcquire. Don't grant access unless * recursive call or no waiters or is first. */ protected final boolean tryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; if (nextc < 0) throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false; } }
在ReentrantLock中定義了2個靜態內部類,一個是NotFairSync,一個是FairSync,分別用來實現非公平鎖和公平鎖。
我們可以在建立ReentrantLock對象時,通過以下方式來設定鎖的公平性:
ReentrantLock lock = new ReentrantLock(true);
如果參數為true表示為公平鎖,為fasle為非公平鎖。預設情況下,如果使用無參構造器,則是非公平鎖。
/** * Creates an instance of {@code ReentrantLock}. * This is equivalent to using {@code ReentrantLock(false)}. */ public ReentrantLock() { sync = new NonfairSync(); } /** * Creates an instance of {@code ReentrantLock} with the * given fairness policy. * * @param fair {@code true} if this lock should use a fair ordering policy */ public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); }
另外在ReentrantLock類中定義了很多方法,比如:
isFair() //判斷鎖是否是公平鎖
isLocked() //判斷鎖是否被任何線程擷取了
isHeldByCurrentThread() //判斷鎖是否被當前線程擷取了
hasQueuedThreads() //判斷是否有線程在等待該鎖
在ReentrantReadWriteLock中也有類似的方法,同樣也可以設定為公平鎖和非公平鎖。不過要記住,ReentrantReadWriteLock並未實現Lock介面,它實現的是ReadWriteLock介面。
4.讀寫鎖
讀寫鎖將對一個資源(比如檔案)的訪問分成了2個鎖,一個讀鎖和一個寫鎖。
正因為有了讀寫鎖,才使得多個線程之間的讀操作不會發生衝突。
ReadWriteLock就是讀寫鎖,它是一個介面,ReentrantReadWriteLock實現了這個介面。
可以通過readLock()擷取讀鎖,通過writeLock()擷取寫鎖。
總結:
1)Lock是一個介面,而synchronized是Java中的關鍵字,synchronized是內建的語言實現;
2)synchronized在發生異常時,會自動釋放線程佔有的鎖,因此不會導致死結現象發生;而Lock在發生異常時,如果沒有主動通過unLock()去釋放鎖,則很可能造成死結現象,因此使用Lock時需要在finally塊中釋放鎖;
3)Lock可以讓等待鎖的線程響應中斷,而synchronized卻不行,使用synchronized時,等待的線程會一直等待下去,不能夠響應中斷;
4)通過Lock可以知道有沒有成功擷取鎖,而synchronized卻無法辦到。
5)Lock可以提高多個線程進行讀操作的效率。
在效能上來說,如果競爭資源不激烈,兩者的效能是差不多的,而當競爭資源非常激烈時(即有大量線程同時競爭),此時Lock的效能要遠遠優於synchronized。所以說,在具體使用時要根據適當情況選擇。
效能比較
在JDK1.5中,synchronized是效能低效的。因為這是一個重量級操作,它對效能最大的影響是阻塞的是實現,掛起線程和恢複線程的操作都需要轉入核心態中完成,這些操作給系統的並發性帶來了很大的壓力。相比之下使用Java提供的Lock對象,效能更高一些。Brian Goetz對這兩種鎖在JDK1.5、單核處理器及雙Xeon處理器環境下做了一組輸送量對比的實驗,發現多線程環境下,synchronized的輸送量下降的非常嚴重,而ReentrankLock則能基本保持在同一個比較穩定的水平上。但與其說ReetrantLock效能好,倒不如說synchronized還有非常大的最佳化餘地,於是到了JDK1.6,發生了變化,對synchronize加入了很多最佳化措施,有自適應自旋,鎖消除,鎖粗化,輕量級鎖,偏向鎖等等。導致在JDK1.6上synchronize的效能並不比Lock差。官方也表示,他們也更支援synchronize,在未來的版本中還有最佳化餘地,所以還是提倡在synchronized能實現需求的情況下,優先考慮使用synchronized來進行同步。
當需要以下進階特性時,才應該使用Lock:可定時的、可輪詢的與可中斷的鎖擷取操作,公平隊列,或者非塊結構的鎖。否則,請使用synchronized。