ReentrantLock是一個重入鎖,可以支援一個線程對資源重複加鎖,他還支援公平加鎖和非公平加鎖。synchronized關鍵字也隱式的支援重進入,比如一個synchronized修飾的遞迴方法,在方法執行時,執行線程在擷取了鎖之後仍能連續多次地獲得該鎖ReentrantLock雖然沒能像synchronized關鍵字一樣支援隱式的重進入,但是在調用lock()方法時,已經擷取到鎖的線程,能夠再次調用lock()方法擷取鎖而不被阻塞。 公平鎖 定義
公平加鎖是在絕對時間上先對鎖擷取的請求一定先被滿足,公平的擷取鎖也就是等待時間最長的線程最優先擷取鎖,也可以說擷取鎖時順序的。ReentrantLock提供了可以控制是否公平鎖的建構函式。
優點和缺點
沒有非公平鎖效率高,但是能夠減少饑餓發生的機率,等待越久的線程越容易擷取到鎖。 實現重進入
重進入是指任意線程在擷取到鎖之後能夠再次擷取該鎖而不會被鎖所阻塞
線程再次擷取鎖:鎖需要去識別擷取鎖的線程是否為當前佔據鎖的線程,如果是,則再次成功擷取。
鎖的最終釋放:線程重複n次擷取了鎖,隨後在第n次釋放該鎖後,其他線程能夠擷取到該鎖。鎖的最終釋放要求鎖對於擷取進行計數自增,計數表示當前鎖被重複擷取的次數,而鎖被釋放時,計數自減,當計數等於0時表示鎖已經成功釋放。
下面我們看看非公平擷取同步狀態的代碼執行個體:
/** * Performs non-fair tryLock. tryAcquire is * implemented in subclasses, but both need nonfair * try for trylock method. */ final boolean nonfairTryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); // 如果沒有並發線程訪問 if (c == 0) { // 如果同步狀態更新成功,加鎖成功 if (compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } // 如果已經有線程加鎖,判斷加鎖的線程是否是當前線程,如果是再次加鎖成功 else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; if (nextc < 0) // overflow throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false; }
成功擷取鎖的線程再次擷取鎖,只是增加了同步狀態值,這也就要求ReentrantLock在釋放同步狀態時減少同步狀態,釋放鎖代碼如下
protected final boolean tryRelease(int releases) { int c = getState() - releases; // 如果當前請求線程不是上次加鎖的線程拋出異常 if (Thread.currentThread() != getExclusiveOwnerThread()) throw new IllegalMonitorStateException(); boolean free = false; // 如果同步狀態為0說明鎖完全釋放 if (c == 0) { free = true; setExclusiveOwnerThread(null); } // 減少同步狀態,減到0釋放鎖 setState(c); return free; }
如果該鎖被擷取了n次,那麼前(n-1)次tryRelease(int releases)方法必須返回false,而只有同步狀態完全釋放了,才能返回true。可以看到,該方法將同步狀態是否為0作為最終釋放的條件,當同步狀態為0時,將佔有線程設定為null,並返回true,表示釋放成功。 公平鎖和非公平鎖區別
如果一個鎖是公平的,那麼鎖的擷取順序就應該符合請求的絕對時間順序,也就是FIFO
對於非公平鎖只要CAS設定同步狀態成功就表示當前線程擷取了鎖,對公平鎖肯定不同,你還得考慮那些等待了更久的線程,讓我們來看下公平鎖的源碼:
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; } }
下面我們寫一個公平鎖和非公平鎖區別的代碼執行個體
public class FairAndUnfairTest { /** * public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); } */ private static Lock fairLock = new ReentrantLockTest(true); private static Lock unfairLock = new ReentrantLockTest(false); public static void main(String[] args) { testLock(unfairLock);// testLock(fairLock); } public static void testLock(Lock lock){ for(int i=0;i<10;i++){ new Thread(new Job(lock),i+"").start(); } } private static class Job extends Thread{ private Lock lock; public Job(Lock lock){ this.lock = lock; } public void run(){ lock.lock(); try { // 連續多次列印當前Tread和隊列中的Thread System.out.println("Lock by ['" + Thread.currentThread().getName() + "'],and waiting "+((ReentrantLockTest)lock).getQueuedTheads()); } finally { lock.unlock(); } } } @SuppressWarnings("serial") private static class ReentrantLockTest extends ReentrantLock{ public ReentrantLockTest(boolean fair) { super(fair); } public Collection<Thread> getQueuedTheads(){ List<Thread> list = new ArrayList<Thread>(super.getQueuedThreads()); // 翻轉集合順序 Collections.reverse(list); return list; } }}
非公平鎖運行結果如下,線程沒有擷取鎖沒有按照順序
Lock by ['0'],and waiting [Thread[1,5,main], Thread[2,5,main]]Lock by ['7'],and waiting [Thread[1,5,main], Thread[2,5,main], Thread[3,5,main], Thread[4,5,main], Thread[5,5,main], Thread[6,5,main]]Lock by ['1'],and waiting [Thread[2,5,main], Thread[3,5,main], Thread[4,5,main], Thread[5,5,main], Thread[6,5,main], Thread[8,5,main], Thread[9,5,main]]Lock by ['2'],and waiting [Thread[3,5,main], Thread[4,5,main], Thread[5,5,main], Thread[6,5,main], Thread[8,5,main], Thread[9,5,main]]Lock by ['3'],and waiting [Thread[4,5,main], Thread[5,5,main], Thread[6,5,main], Thread[8,5,main], Thread[9,5,main]]Lock by ['4'],and waiting [Thread[5,5,main], Thread[6,5,main], Thread[8,5,main], Thread[9,5,main]]Lock by ['5'],and waiting [Thread[6,5,main], Thread[8,5,main], Thread[9,5,main]]Lock by ['6'],and waiting [Thread[8,5,main], Thread[9,5,main]]Lock by ['8'],and waiting [Thread[9,5,main]]Lock by ['9'],and waiting []
公平鎖的運行結果如下,線程按照順序擷取鎖
Lock by ['0'],and waiting [Thread[1,5,main], Thread[2,5,main]]Lock by ['1'],and waiting [Thread[2,5,main], Thread[3,5,main], Thread[4,5,main], Thread[5,5,main], Thread[6,5,main], Thread[7,5,main]]Lock by ['2'],and waiting [Thread[3,5,main], Thread[4,5,main], Thread[5,5,main], Thread[6,5,main], Thread[7,5,main], Thread[8,5,main], Thread[9,5,main]]Lock by ['3'],and waiting [Thread[4,5,main], Thread[5,5,main], Thread[6,5,main], Thread[7,5,main], Thread[8,5,main], Thread[9,5,main]]Lock by ['4'],and waiting [Thread[5,5,main], Thread[6,5,main], Thread[7,5,main], Thread[8,5,main], Thread[9,5,main]]Lock by ['5'],and waiting [Thread[6,5,main], Thread[7,5,main], Thread[8,5,main], Thread[9,5,main]]Lock by ['6'],and waiting [Thread[7,5,main], Thread[8,5,main], Thread[9,5,main]]Lock by ['7'],and waiting [Thread[8,5,main], Thread[9,5,main]]Lock by ['8'],and waiting [Thread[9,5,main]]Lock by ['9'],and waiting []
公平鎖多次運行偶爾會出現個別不按照順序的線程,有時會出現下面結果,有知道原因可以留言,謝謝
Lock by ['1'],and waiting [Thread[0,5,main], Thread[2,5,main]]Lock by ['0'],and waiting [Thread[2,5,main], Thread[3,5,main], Thread[5,5,main], Thread[4,5,main], Thread[7,5,main], Thread[6,5,main], Thread[9,5,main], Thread[8,5,main]]Lock by ['2'],and waiting [Thread[3,5,main], Thread[5,5,main], Thread[4,5,main], Thread[7,5,main], Thread[6,5,main], Thread[9,5,main], Thread[8,5,main]]Lock by ['3'],and waiting [Thread[5,5,main], Thread[4,5,main], Thread[7,5,main], Thread[6,5,main], Thread[9,5,main], Thread[8,5,main]]Lock by ['5'],and waiting [Thread[4,5,main], Thread[7,5,main], Thread[6,5,main], Thread[9,5,main], Thread[8,5,main]]Lock by ['4'],and waiting [Thread[7,5,main], Thread[6,5,main], Thread[9,5,main], Thread[8,5,main]]Lock by ['7'],and waiting [Thread[6,5,main], Thread[9,5,main], Thread[8,5,main]]Lock by ['6'],and waiting [Thread[9,5,main], Thread[8,5,main]]Lock by ['9'],and waiting [Thread[8,5,main]]Lock by ['8'],and waiting []
理論上非公平鎖會出現同一個線程連續擷取鎖,我這裡暫時沒有類比出來,如果有知道好的辦法可以留言,謝謝
公平性鎖保證了鎖的擷取按照FIFO原則,而代價是進行大量的線程切換。非公平性鎖雖然可能造成線程“饑餓”,但極少的線程切換,保證了其更大的輸送量