鎖——Lock、Condition、ReadWriteLock、LockSupport
該系列依舊是參照android api27原始碼來學習。
關於鎖的種類概念,可以參考 Java中的鎖分類
Lock介面
該介面位於java.util.concurrent.locks
包下。
相對synchronized而言,Lock介面提供了更多可擴充套件的鎖操作,有更靈活的結構,完全不同的屬性,以及支援多個相關聯的Condition物件。
鎖是控制多執行緒訪問共享變數的工具。通常鎖提供對共享變數的獨佔訪問,即同一時間只有一個執行緒可以獲得鎖,並且想要訪問共享變數就要先得到鎖。然而,有些鎖允許併發訪問共享變數,比如ReadWriteLock中的讀鎖。
synchronized語法是塊結構,可以自動完成鎖的釋放;如果synchronized有巢狀使用,則鎖的釋放順序和獲取順序是相反的。
而Lock必須手動控制鎖的釋放,但可以靈活控制釋放順序。語法結構如下:
Lock l = ...;
l.lock();
try {
// access the resource protected by this lock
} finally {
//確保鎖一定會被釋放
l.unlock();
}
Lock相對synchronized還提供了以下功能:
- 提供了非阻塞嘗試獲取鎖的方法
tryLock()
。 - 提供了嘗試獲取可被中斷的鎖的方法
lockInterruptibly()
。 - 提供了獲取具有時效的鎖的方法
tryLock(long, TimeUnit)
。
Lock還可以提供其他的行為,比如保證順序、非重入、死鎖檢測等。
Lock物件也可以作為synchronized語法中被鎖住的物件,這和呼叫Lock物件自身的lock()方法沒有任何關係。不過為了避免混淆,不推薦這樣使用,除非是在Lock實現類的內部。
注:除非特別說明,否則傳入null作為任何的引數將導致NullPointerException。
該介面的方法如下:
void lock()
阻塞方法,獲得一個鎖。如果鎖暫時不可用,那麼當前執行緒會休眠(被阻塞,進入WAITING狀態),直到獲取鎖。
void lockInterruptibly() throws InterruptedException
阻塞方法,獲取鎖,除非當前執行緒被中斷。若當前鎖不可用,則執行緒阻塞,直到以下任意條件成立:
- 當前執行緒獲得鎖。
- 當前執行緒被中斷,並且支援鎖的獲取過程被中斷。
如果當前執行緒滿足以下任意條件,則丟擲InterruptedException,並且清除當前執行緒的中斷狀態:
- 在呼叫該方法之前,已經處於中斷狀態。
- 在獲取鎖的時候被中斷,並且支援在獲取鎖的時候被中斷。
boolean tryLock()
非阻塞方法。若鎖可用,則獲取鎖,返回true;否則返回false。通常用法如下:
Lock lock = ...;
if (lock.tryLock()) {
try {
// manipulate protected state
} finally {
lock.unlock();
}
} else {
// perform alternative actions
}
boolean tryLock(long time, TimeUnit unit) throws InterruptedException
如果鎖在等待時間內可用,並且當前執行緒未被中斷,則獲取鎖。
鎖可用,則立即返回true。否則,執行緒阻塞,知道以下任意條件觸發:
- 當前執行緒獲得鎖.
- 當前執行緒被中斷,並且支援鎖的獲取過程被中斷。
- 等待超時。
若當前執行緒滿足如下任意條件,則丟擲InterruptedException,並且清除執行緒的中斷狀態:
- 在呼叫該方法之前,已經處於中斷狀態。
- 在獲取鎖的時候被中斷,並且支援在獲取鎖的時候被中斷。
若等待時間不大於0,則不會等待。
void unlock()
釋放鎖。
Condition newCondition()
返回一個繫結到該Lock例項的Condition例項。
當前執行緒在呼叫Condition的等待方法之前,需要先獲取鎖。呼叫Condition的await()方法會釋放鎖,並在等待結束時重新獲取鎖。
Condition介面
Condition從Lock中獲取,且每個Lock可以有多個Condition,彼此互不影響。await、signal方法等同於Object的wait、notify方法。Lock可以代替synchronizedde的使用,Condition可以代替Object的wait/notify方法的使用。
Condition需要與Lock繫結使用,通過Lock.newCondition方法獲取。
舉個例子。假設有一個支援put和take阻塞方法的有界buffer,所實現的生產者/消費者模型如下:
class BoundedBuffer {
final Lock lock = new ReentrantLock();
final Condition notFull = lock.newCondition();
final Condition notEmpty = lock.newCondition();
final Object[] items = new Object[100];
int putptr, takeptr, count;
public void put(Object x) throws InterruptedException {
lock.lock();
try {
while (count == items.length)
notFull.await();
items[putptr] = x;
if (++putptr == items.length) putptr = 0;
++count;
notEmpty.signal();
} finally {
lock.unlock();
}
}
public Object take() throws InterruptedException {
lock.lock();
try {
while (count == 0)
notEmpty.await();
Object x = items[takeptr];
if (++takeptr == items.length) takeptr = 0;
--count;
notFull.signal();
return x;
} finally {
lock.unlock();
}
}
}
ArrayBlockingQueue類正是這樣的實現。
void await() throws InterruptedException
阻塞當前執行緒,直到被喚醒或者中斷。與Condition相關聯的Lock會被釋放。在該方法返回之前,當前執行緒必須重新獲取與Condition關聯的鎖。
當前執行緒滿足以下任意條件,則丟擲InterruptedException,並且清除中斷狀態:
- 呼叫await之前,已處於中斷狀態。
- 阻塞過程中被中斷,並且支援執行緒掛起時被中斷。
void awaitUninterruptibly()
與await()方法相比,不受中斷影響,也不影響中斷狀態,只能被喚醒。
long awaitNanos(long nanosTimeout) throws InterruptedException
與await()相比,新增了超時時間。該方法返回剩餘等待時間,若超時,則返回值不大於0。通常用法如下:
boolean aMethod(long timeout, TimeUnit unit) {
long nanos = unit.toNanos(timeout);
lock.lock();
try {
while (!conditionBeingWaitedFor()) {
if (nanos <= 0L)
return false;
nanos = theCondition.awaitNanos(nanos);
}
// ...
} finally {
lock.unlock();
}
}
boolean await(long time, TimeUnit unit) throws InterruptedException
該方法在效果上等同於awaitNanos(unit.toNanos(time)) > 0
boolean awaitUntil(Date deadline) throws InterruptedException
與await()相比,新增了等待截止時間,返回值表示是否超過了截止時間。通常用法如下:
boolean aMethod(Date deadline) {
boolean stillWaiting = true;
lock.lock();
try {
while (!conditionBeingWaitedFor()) {
if (!stillWaiting)
return false;
stillWaiting = theCondition.awaitUntil(deadline);
}
// ...
} finally {
lock.unlock();
}
}
void signal()
喚醒一個等待執行緒,效果與Object.notify()類似。
void signalAll()
喚醒所有等待執行緒,效果與Object.notifyAll()類似。
ReadWriteLock
ReadWriteLock維護一對相關聯的Lock,一個針對讀操作,一個針對寫操作。讀鎖可以被多個執行緒同時持有,只要沒有其他執行緒寫入。而寫鎖是獨享的。
ReadWriteLock的實現類必須保證讀寫操作的記憶體同步,也即成功獲得讀鎖後,將可以看到上次釋放寫鎖後的所有更新。
讀寫鎖比互斥鎖有更大的吞吐量。在只有一個執行緒寫資料的時候,大多數情況下可以有任意數量的執行緒併發讀取資料,讀寫鎖正是利用了這一點。當然這是針對多核處理器而言。
讀寫鎖相對於互斥鎖是否提升效能,依賴於對資料讀和寫的頻率、持續時間,以及資料爭用情況,即同一時間訪問資料的執行緒數。
雖然讀寫鎖的操作簡單直接,但實現類必須做出一些權衡,這會影響到效能:
- 在讀操作和寫操作都在等待的時候,恰逢一個寫操作釋放了寫鎖,這時是否授予讀鎖或者寫鎖?寫操作擁有高優先順序,因為讀寫鎖本身適用於讀多寫少的情況,所以寫操作佔用時間少,頻率低;讀操作擁有低優先順序,因為讀可能需要很久,導致寫操作進入漫長的等待;當然,公平起見,也可以按照請求的順序來實現。
- 在一個讀操作正在執行和一個寫操作正在等待的情況下,是否授予讀鎖給其他的讀操作。讀操作的效能可能無限期延後寫操作,而寫操作會降低併發執行的能力。
- 鎖是否可以重入?一個擁有寫鎖的執行緒可以再次請求寫鎖嗎?或者可以同時獲取讀寫鎖嗎?讀鎖可重入嗎?
- 在不允許介於中間的寫操作時,寫鎖可以降級成讀鎖嗎?擁有讀鎖的讀操作,可以優先於其他正在等待的讀或寫操作,將讀鎖升級為寫鎖嗎?
Lock readLock()
獲取讀鎖。
Lock writeLock()
獲取寫鎖。
LockSupport
不多介紹了,參考java併發包系列---LockSupport
相關文章
- 深入學習Lock鎖(2)——LockSupport工具類
- Java多執行緒/併發06、執行緒鎖Lock與ReadWriteLockJava執行緒
- Java讀寫鎖ReadWriteLockJava
- Lock介面之Condition介面
- 多執行緒中使用Lock鎖定多個條件Condition的使用執行緒
- Lock物件Condition介面實現等待/通知物件
- oracle lock鎖_v$lock_轉Oracle
- LockSupport
- lock鎖和monitor.enter鎖
- 【JavaSE】Lock鎖和synchronized鎖的比較,lock鎖的特性,讀寫鎖的實現。Javasynchronized
- mysql innodb lock鎖之record lock之一MySql
- Synchronized、lock、volatile、ThreadLocal、原子性總結、Conditionsynchronizedthread
- 【Mysql】metadata lock鎖MySql
- MYSQL 鎖:metadata lockMySql
- LockSupport工具
- C++11中的mutex, lock, condition variable實現分析C++Mutex
- Java併發程式設計之鎖機制之LockSupport工具Java程式設計
- MySQL 共享鎖 (lock in share mode),排他鎖 (for update)MySql
- 帶你理解Lock鎖原理
- Lock鎖相關以及AQSAQS
- Lock、Synchronized鎖區別解析synchronized
- TX鎖(Transaction Lock)分析 (zt)
- mysql表鎖與lock tablesMySql
- ReadWriteLock用法
- Java 讀寫鎖 ReadWriteLock 原理與應用場景詳解Java
- StampedLock:JDK1.8中新增,比ReadWriteLock還快的鎖JDK
- JUC工具(LockSupport)
- 停個車真的不是一般的難ReentrantLock.lock之LockSupport.parkReentrantLock
- java的乾兒子鎖LockJava
- MySQL-lock(鎖)-v2.0MySql
- ThunderSoft File Lock for Mac檔案鎖Mac
- MySQL 的next-lock 鎖MySql
- 【鎖】Latch、lock、 pin的區別
- 分散式鎖-Redission-Lock鎖的使用與原理分散式Redis
- mysql metadata lock後設資料鎖之鎖狀態lock_status流轉圖MySql
- Java JUC LockSupport概述Java
- 同步控制和鎖,ReenterLock和Condition的詳細使用
- Lock的獨佔鎖和共享鎖的比較分析