關於這四種鎖的各自情況,網上有很多文章做了介紹,本不想單獨開章節介紹,本章只介紹這四種鎖的一些源碼特點及注意事項。
demo 源碼:https://github.com/mantuliu/javaAdvance
首先來看公平鎖和非公平鎖,我們默認使用的鎖是非公平鎖,只有當我們顯示設置為公平鎖的情況下,才會使用公平鎖,下面我們簡單看一下公平鎖的源碼,如果等待隊列中沒有節點在等待,則占有鎖,如果已經存在等待節點,則返回失敗,由后面的程序去將此線程加入等待隊列:
/** * 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; } }
通過上面的代碼,我們可以推斷,當使用公平鎖的情況下,並且同一個線程的執行時間較長時,線程內部進行了多次的鎖的獲取和釋放,效率非常低下,可以參加Lesson8中的demo:
demo Lesson8LockIntPerform:在使用ReentrantLock加非公平鎖的情況下100個線程循環下單數為:857239882
demo Lesson8LockIntPerform:在使用ReentrantLock加非公平鎖的情況下100個線程循環下單數為:860364303
demo Lesson8LockFairIntPerform:在使用ReentrantLock加公平鎖的情況下100個線程循環下單數為:19153640
demo Lesson8LockFairIntPerform:在使用ReentrantLock加公平鎖的情況下100個線程循環下單數為:19076567
上面的demo中,在使用公平鎖的情況下性能明顯降低,非公平鎖的性能是公平鎖性能的幾十倍以上,這和公平鎖每次試圖占有鎖時,都必須先要進等待隊列,按照FIFO的順序去獲取鎖,因此在我們的實驗情景下,使用公平鎖的線程進行了頻繁切換,而頻繁切換線程,性能必然會下降的厲害,這也告誡了我們在實際的開發過程中,在需要使用公平鎖的情景下,務必要考慮線程的切換頻率。
接下來我們來看一下讀寫鎖,通過看讀寫鎖的實現源碼,我們可以發現,讀鎖和寫鎖共用同一個等待隊列,那么在采用非公平鎖的情況下,如果讀鎖的線程執行時間比較長,並且讀鎖的並發比較高,那么寫鎖的線程便永遠都拿不到鎖,那么實際的情況會不會是這樣呢?
demo Lesson3WriteReadLock:此demo的讀線程在不斷的占用讀鎖,按照推論,寫鎖的線程是沒有機會獲取到鎖的,但是實際情況是寫鎖的線程可以正常的獲取到鎖,那么是什么原因使得寫鎖的線程可以獲取到鎖的了?通過查看源代碼,會發現有這樣的一個方法:
final boolean apparentlyFirstQueuedIsExclusive() { Node h, s; return (h = head) != null && (s = h.next) != null && !s.isShared() && s.thread != null; }
上面的方法,實現了一個新的讀線程獲取鎖的中斷,它會讀取等待隊列中下一個等待鎖的線程,如果它是獲取寫鎖的線程,那么此方法返回為真,調用它的程序會把這個試圖獲取讀鎖的線程加入到等待隊列,從而終止了讀線程一直都在占有鎖的情況。