ReentrantReadWriteLock
概述
嚴格來說 ReentrantReadWriteLock 是鎖,不應該在這篇文章里,但是為了篇幅,還是將它放入。 ReentrantReadWriteLock 實現 ReadWriteLock 接口:
public interface ReadWriteLock {
//獲得讀鎖
Lock readLock();
//獲得寫鎖
Lock writeLock();
}
其中寫鎖是獨占鎖,讀鎖是共享鎖。
使用場景
一個資源能夠被多個讀線程訪問,或者被一個寫線程訪問,但是不能同時存在讀寫線程。
適用于一個共享資源被大量讀取操作,而只有少量的寫操作(修改數據)。
特點
- 公平性: 讀鎖之間沒有公平性,但是寫鎖是獨占鎖,它的公平性規則和 ReentrantLock 一樣分為 公平鎖 和 非公平鎖(默認);
- 重入性
- 讀寫鎖允許讀線程和寫線程按照請求鎖的順序重新獲取讀取鎖或者寫入鎖。當然只有寫線程釋放了鎖,讀線程才能重新獲取重入鎖;
- 寫線程獲取寫入鎖,可以再次獲取讀取鎖,但是讀線程獲取讀取鎖后卻不能獲取寫入鎖
- 讀寫鎖最多支持 65535 個重入的寫入鎖和讀取鎖。因為讀寫鎖的同步器繼承 AQS,AQS 的 state 用來表示重入的線程數,它的高 16 位用來表示寫線程重入數,低 16 位 用來表示讀線程重入數,所以最多支持 65535。
- 鎖降級。寫線程獲取寫入鎖后可以獲取讀寫鎖,然后釋放寫入鎖,這樣就從寫入鎖變成了讀取鎖,實現鎖降級;
- 不支持鎖升級。
- 條件變量(Condition)
- 寫入鎖是獨占鎖。提供條件變量支持;
- 讀取鎖是共享鎖,不允許獲取條件變量,否則會拋出 UnsupportedOperationException
實例代碼
class CachedData {
private Object data;
private volatile boolean cacheValid;
private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
public void processCachedData() {
rwl.readLock().lock();
if (!cacheValid) {
// Must release read lock before acquiring write lock
rwl.readLock().unlock();
rwl.writeLock().lock();
// Recheck state because another thread might have acquired
// write lock and changed state before we did.
if (!cacheValid) {
data = ...
cacheValid = true;
}
// Downgrade by acquiring read lock before releasing write lock
rwl.readLock().lock();
rwl.writeLock().unlock(); // Unlock write, still hold read
}
use(data);
rwl.readLock().unlock();
}
}
Semaphore
概述
Semaphore(信號量)用來控制同時訪問某個特定資源的操作數量,或者同時執行某個指定操作的數量。常用來實現某種資源池,或者對容器施加邊界。
Semaphore 管理者一組虛擬的許可(permit),許可的初始數量可以通過構造函數制定。具體應用中,執行操作之前首先獲得許可,并在使用以后釋放許可,如果許可不夠,線程將被掛起。如果許可初始數量為 1,則 Semaphore 被稱作計算信號量,可以用作互斥體(Mutex),并且擁有了不可重入的加鎖語義。
Semaphore 內部的 Sync 繼承自 AQS,使用了其共享模式的接口,所以 Semaphore 并不支持 Condition 條件變量。
實例代碼
class BoundHashSet<T> {
private final Set<T> set;
private final Semaphore sem;
public BoundHashSet(int bound) {
set = new HashSet<T>();
sem = new Semaphore(bound);
}
public boolean add(T o) throws InterruptedException {
sem.acquire();
boolean wasAdded = false;
try {
wasAdded = set.add(o);
return wasAdded;
} finally {
if (!wasAdded) {
sem.release();
}
}
}
public boolean remove(T o) {
boolean wsRemoved = set.remove(o);
if (wsRemoved) {
sem.release();
}
return wsRemoved;
}
}
CountDownLatch
概述
CountDownLatch 是一種靈活的閉鎖實現,閉鎖 是一種同步工具,可以延遲線程的進度直到其到達終止狀態。它的作用相當于一扇門:在閉鎖到達結束狀態之前,這扇門一直關閉,沒有任何線程通過,當到達結束狀態時,這扇門會打開允許所有線程通過。
使用場景
閉鎖(CountDownLatch)可以用來確保某些活動直到其他活動都完成了才繼續執行。比如:
- 確保某個計算在其需要的所有資源都被初始化之后才繼續執行;
- 確保某個服務在其依賴的所有其他服務都已經啟動后才啟動;
- 等待知道某個操作的所有參與者都就緒再繼續執行。
主要接口
CountDownLatch 有兩類主要接口:
//供等待線程調用
public void await() throws InterruptedException
public boolean await(long timeout, TimeUnit unit)
throws InterruptedException
//共通知線程調用
public void countDown()
CountDownLatch 內部的 Sync 繼承自 AQS,私用的 state 代等待線程的計數器 count。調用CountDownLatch.await 的線程會阻塞掛起,一直到計數器減到 0。其他線程調用 CountDownLatch.countDown,每次都會遞減計數器,計時器到 0 時,會喚醒所有阻塞在 await 的線程。他們的調用時序圖如下所示:
特點
CountDownLatch 注意以下特點:
- 等待計數器 count 只有 CountDownLatch 初始化時才能設置,沒有其他接受改變它,所以 CountDownLatch 是一次性的同步類;
- 調用 await 和 countDown 的線程不可能是同一個;
- 內部 Sync 重載的是 AQS 共享模式接口,所以不支持 Condition。
編程實踐
// 一個CountDownLatch實例是不能重復使用的,也就是說它是一次性的,鎖一經被打開就不能再關閉使用了,如果想重復使用,請考慮使用CyclicBarrier。
public class CountDownLatchTest {
// 模擬了100米賽跑,10名選手已經準備就緒,只等裁判一聲令下。當所有人都到達終點時,比賽結束。
public static void main(String[] args) throws InterruptedException {
// 開始的倒數鎖
final CountDownLatch begin = new CountDownLatch(1);
// 結束的倒數鎖
final CountDownLatch end = new CountDownLatch(10);
// 十名選手
final ExecutorService exec = Executors.newFixedThreadPool(10);
for (int index = 0; index < 10; index++) {
final int NO = index + 1;
Runnable run = new Runnable() {
public void run() {
try {
// 如果當前計數為零,則此方法立即返回。
// 等待
begin.await();
Thread.sleep((long) (Math.random() * 10000));
System.out.println("No." + NO + " arrived");
} catch (InterruptedException e) {
} finally {
// 每個選手到達終點時,end就減一
end.countDown();
}
}
};
exec.submit(run);
}
System.out.println("Game Start");
// begin減一,開始游戲
begin.countDown();
// 等待end變為0,即所有選手到達終點
end.await();
System.out.println("Game Over");
exec.shutdown();
}
}
CyclicBarrier
概述
CyclicBarrier 的字面意思是可循環使用(Cyclic)的柵欄(Barrier)。柵欄類似于閉鎖,它能阻塞一組線程直到某個事件發生。柵欄和閉鎖的關鍵區別在于,所有線程必須都到達柵欄位置,才能繼續執行。閉鎖用于等待事件,柵欄用于等待線程。
使用場景
CyclicBarrier 在并行迭代算法中非常有用,這種算法通常將一個問題拆分成一系列相互獨立的子問題,各個子問題由不同線程計算,單個線程計算完成后調用 await 等候在柵欄處,所有線程計算完畢后都等候在柵欄時,柵欄再打開釋放所有線程。
如果某個線程的 await 超時或者被中斷,其他已經在 await 的線程會終止等待并拋出 BrokenBarrierException。
特點
- 可以循環使用,柵欄釋放所有線程后,重新初始化 count 計數器。
- 提供 reset 接口重置 count 計數器,已經 await 的線程會拋出 BrokenBarrierException。
- 初始化時可以傳入 Runnable 接口的實現,所有線程都到達柵欄后,在最后一個到達線程上執行這個 Runnable。
- 內部使用 ReentrantLock 和 Condition 控制同步,而不是用繼承 AQS 的 Sync。
編程實踐
class CyclicBarrierTest {
static CyclicBarrier c = new CyclicBarrier(2);
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
try {
c.await();
} catch (Exception e) {}
System.out.println(1);
}
}).start();
try {
c.await();
} catch (Exception e) {}
System.out.println(2);
}
}
內容來源
Java 并行編程實戰
https://my.oschina.net/adan1/blog/158107
http://www.cnblogs.com/liuling/archive/2013/08/21/2013-8-21-03.html
http://ifeve.com/concurrency-semaphore/
http://blog.csdn.net/lipeng_bigdata/article/details/52165426
http://www.itzhai.com/the-introduction-and-use-of-a-countdownlatch.html#read-more
http://developer.51cto.com/art/201403/432095.htm
http://www.importnew.com/15731.html
http://ifeve.com/concurrency-cyclicbarrier/
http://www.itzhai.com/the-introduction-and-use-of-cyclicbarrier.html#read-more