標籤:ISE 技術分享 要求 stat shared ace writer 重入 queue
重入鎖ReentrantLock
可以代替synchronized, 但synchronized更靈活.
但是, 必須必須必須要手動釋放鎖.
try { lock.lock();} finally { lock.unlock();}
重入鎖是指任意線程在擷取到鎖之後能夠再次擷取該鎖而不會被阻塞.
對於ReentrantLock而言, 釋放鎖時, 鎖定調用了n次lock()方法, 那麼釋放時就需要調用n次unlock()方法.
- tryLock()方法, tryLock(long timeout, TimeUnit unit)方法
嘗試鎖定,
此方法有傳回值, 鎖定返回true, 否則返回false.
如果無法鎖定或者在一定時間內無法鎖定, 線程可以決定是否等待.
- lockInterruptibly()方法
線程在請求鎖定的時候被阻塞, 如果被interrupt, 則此線程會被喚醒並被要求處理InterruptedException.
再次擷取同步狀態邏輯:
通過判斷當前線程是否為擷取鎖的線程來決定擷取操作是否成功, 如果是擷取鎖的線程, 則將同步狀態增加並返回true, 表示擷取同步狀態成功.
如果是公平鎖, 則還需要判斷同步隊列中當前節點是否有前驅節點, 如果有, 則需要等待前驅線程擷取鎖並釋放之後才能繼續擷取鎖.
公平鎖和非公平鎖
公平與否是針對擷取鎖而言的, 公平的是指擷取鎖的順序是按請求時間順序的, 也就是FIFO.
ReentrantLock預設為非公平鎖, 構造對象時調用有參構造方法並參入true, 即為公平鎖.
讀寫鎖ReentrantReadWriteLock
讀寫鎖在同一時刻允許多個讀線程訪問, 但是在寫線程訪問時, 所有的讀線程和寫線程都會被阻塞.
讀寫鎖維護了一個讀鎖, 一個寫鎖. 並且, 遵循擷取寫鎖再擷取讀鎖, 再釋放寫鎖的次序, 寫鎖可以階級為讀鎖(即鎖降級)
ReentrantReadWriteLock也支援重入和公平性選擇
ReentrantReadWriteLock實現了ReadWriteLock介面, 此介面中只定義了擷取讀鎖和寫鎖的方法, 即readLock()和writeLock()兩個方法. ReentrantReadWriteLock還提供了如下方法:
| 方法名稱 | 描述 |
| :-------- | :-------- |
| int getReadLockCount() | 返回當前讀鎖被擷取的次數. 該次數不等於擷取讀鎖的線程數, 一個線程可多次擷取讀鎖 |
| int getReadHoldCount() | 返回當前線程擷取讀鎖的次數, 並使用ThreadLocal儲存 |
| boolean isWriteLocked() | 判斷寫鎖是否被擷取 |
| int getWriteHoldCount() | 返回當前寫鎖被擷取的次數 |
ReentrantReadWriteLock通過將AQS的同步狀態, 分為高16位(讀)和低16位(寫)來維護讀寫鎖的擷取狀態,
圖中狀態表示, 當前線程已經擷取了寫鎖, 並且重入了兩次, 同時擷取了兩次讀鎖.
讀寫鎖是通過位元運算來確定讀寫狀態的.
設當前同步狀態為S, 那麼:
寫狀態為 S & 0x0000FFFF, 即把高位16位(讀)抹去
讀狀態為 S >>> 16, 即右移16位
寫狀態增加1時, S + 1
讀狀態增加1時, S + (1 << 16), 即 S + 0x00010000
推論: 同步狀態S不等於0時, 當寫狀態(S & 0x0000FFFF)等於0時, 則讀狀態(S >>> 16)大於0, 即讀鎖已被擷取.
寫鎖的擷取與釋放
寫鎖支援重入, 但是它是排它鎖.
當前線程在擷取寫鎖時:
- 如果當前線程已經擷取了寫鎖, 則寫狀態增加
- 如果讀鎖已經被擷取或者該線程不是已經擷取寫鎖的線程, 則當前線程進行等待狀態
protected final boolean tryAcquire(int acquires) { /* * Walkthrough: * 1. if read count nonzero or write count nonzero * and owner is a different thread, fail. * 2. If count would saturate, fail. (This can only * happen if count is already nonzero.) * 3. Otherwise, this thread is eligible for lock if * it is either a reentrant acquire or * queue policy allows it. If so, update state * and set owner. */ Thread current = Thread.currentThread(); int c = getState(); int w = exclusiveCount(c); // 擷取寫狀態 if (c != 0) { // (Note: if c != 0 and w == 0 then shared count != 0), 即上面的推論 if (w == 0 || current != getExclusiveOwnerThread()) // 後面是重入條件 return false; if (w + exclusiveCount(acquires) > MAX_COUNT) throw new Error("Maximum lock count exceeded"); } if ((w == 0 && writerShouldBlock(current)) || !compareAndSetState(c, c + acquires)) return false; setExclusiveOwnerThread(current); return true;}
如果存在讀鎖, 寫鎖不能被擷取, 因為:
讀寫鎖要確保寫鎖的操作對讀鎖可見, 如果允許讀鎖在已經被擷取的情況下對寫鎖的擷取, 那麼正在啟動並執行讀線程就無法感知到寫線程的操作.
寫鎖的釋放, 就是每次釋放寫狀態減小, 寫狀態為0時表示寫鎖已釋放.
讀鎖的擷取與釋放
讀鎖是支援重入的共用鎖定.
讀鎖可以同時被多個線程擷取, 在沒有寫線程訪問的情況下, 讀鎖總是能被成功擷取(讀狀態加增加).
Condition介面
在Java5之前, 等待/通知模式的實現可以採用wait()/notify()/notifyAll()與synchronized配合來實現.
任意Java對象上都有上述方法, 稱為監視器方法
Condition介面也提供了類似的方法:
| 方法名稱 |
描述 |
| await() |
當前線程進入等待狀態, 直到被通知或中斷 |
| awaitUninterruptibly() |
當前線程進入等待狀態, 直到被通知 |
| awaitNanos(long nanosTimeout) |
當前線程進入等待狀態, 直到被通知或中斷或逾時, 傳回值為剩餘時間 |
| awaitUtil(Date deadLine) |
當前線程進入等待狀態, 直到被通知或中斷或到達某個時間, 沒到某個時間被通知返回true |
| signal() |
喚醒一個等待在Condition上的線程, 該線程從等待方法返回前必須獲得與Condition相關的鎖 |
| signalAll() |
喚醒所有等待在Condition上的線程, 可以從等待方法返回的線程必須獲得與Condition相關的鎖 |
擷取Condition對象必須通過Lock的newCondition()方法.
Lock lock = new ReentrantLock();Condition con1 = lock.newCondition();Condition con2 = lock.newCondition();
使用時必須先擷取鎖, 再調用condition的方法, 下面是使用Condition實現一個生產者/消費者情境:
public class MyContainer2<T> { final private LinkedList<T> lists = new LinkedList<>(); final private int MAX = 10; //最多10個元素 private int count = 0; private Lock lock = new ReentrantLock(); private Condition producer = lock.newCondition(); private Condition consumer = lock.newCondition(); public void put(T t) { try { lock.lock(); while(lists.size() == MAX) { //想想為什麼用while而不是用if? producer.await(); } lists.add(t); ++count; consumer.signalAll(); //通知消費者線程進行消費 } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } public T get() { T t = null; try { lock.lock(); while(lists.size() == 0) { consumer.await(); } t = lists.removeFirst(); count --; producer.signalAll(); //通知生產者進行生產 } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } return t; } public static void main(String[] args) { MyContainer2<String> c = new MyContainer2<>(); //啟動消費者線程 for(int i=0; i<10; i++) { new Thread(()->{ for(int j=0; j<5; j++) System.out.println(c.get()); }, "c" + i).start(); } try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } //啟動生產者線程 for(int i=0; i<2; i++) { new Thread(()->{ for(int j=0; j<25; j++) c.put(Thread.currentThread().getName() + " " + j); }, "p" + i).start(); } }}
參考資料: 《Java並發編程的藝術》
Java並發編程之Lock