章節(jié)目錄
- ReentrantReadWriteLock 特性
- 讀寫鎖接口示例
- 讀寫鎖的實(shí)現(xiàn)分析
- 讀寫狀態(tài)設(shè)計(jì)
- 寫鎖的釋放與獲取
- 讀鎖的釋放與獲取
- 鎖降級(jí)
1. ReentrantReadWriteLock 特性
1.1 讀寫鎖定義
讀寫鎖維護(hù)了一對(duì)鎖珍德,一個(gè)讀鎖熄云,一個(gè)寫鎖,通過分離讀鎖寫鎖,使得并發(fā)性相比一般的排他鎖有了很大提升。
1.2 讀寫鎖使用場(chǎng)景
1.讀寫鎖比較適用于讀多寫少的應(yīng)用場(chǎng)景。
2.讀寫鎖在統(tǒng)一時(shí)刻可以允許多個(gè)讀線程訪問,但是在寫線程訪問時(shí),所有的讀線程拦止、其他寫線程均被阻塞。
1.3 讀寫鎖的優(yōu)點(diǎn)
1.保證寫操作對(duì)讀操作的可見性
2.在讀多寫少的情況下的并發(fā)性的提升
3.讀寫鎖簡化可讀寫交互場(chǎng)景的編程方式
2.讀寫鎖接口示例
如下為使用讀寫鎖操作緩存的示例
package org.seckill.lock;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class ReadWriteCache {
//充當(dāng)cache
static Map<String, Object> map = new HashMap<String, Object>();
//實(shí)例化讀寫鎖對(duì)象
static ReentrantReadWriteLock reentrantReadWriteLock =
new ReentrantReadWriteLock();
//實(shí)例化讀鎖
static Lock r = reentrantReadWriteLock.readLock();
//實(shí)例化寫鎖
static Lock w = reentrantReadWriteLock.writeLock();
//獲取緩存中值
public static final Object get(String key) {
r.lock();
try {
return map.get(key);
} finally {
r.unlock();
}
}
//寫緩存中值糜颠,并返回對(duì)應(yīng)value
public static final Object set(String key, Object obj) {
w.lock();
try {
return map.put(key, obj);
} finally {
w.unlock();
}
}
//清空所有內(nèi)容
public static final void clear() {
w.lock();
try {
map.clear();
} finally {
w.unlock();
}
}
}
如上所示:
1.Cache組合一個(gè)非線程安全的HashMap做為緩存實(shí)現(xiàn)汹族,同時(shí)使用讀寫鎖的
讀鎖和寫鎖來保證Cache是線程安全的。
2.在讀操作get(String key)方法中括蝠,需要使用讀鎖鞠抑,這使得并發(fā)訪問該方法時(shí)不
會(huì)被阻塞。
3.寫鎖put(String key,Object object)方法和clear()方法忌警,在更新HashMap時(shí)必須
提前獲取寫鎖搁拙,當(dāng)獲取寫鎖后,其他線程對(duì)于讀鎖和寫鎖的獲取都被阻塞法绵,只
有寫鎖釋放之后箕速,其他的讀寫操作才能繼續(xù)操作,也就是說寫鎖其實(shí)是排他
鎖朋譬、互斥鎖盐茎。
4.最終,讀鎖提升了讀操作的并發(fā)性徙赢,也保證了每次寫操作對(duì)所有后續(xù)讀操作
的可見性字柠,同時(shí)簡化了編程方式探越,對(duì)應(yīng)1.3
3.讀寫鎖的實(shí)現(xiàn)分析
3.1 讀寫狀態(tài)設(shè)計(jì)
1.讀寫鎖同樣依賴自定義同步器實(shí)現(xiàn)同步功能
2.ReentrantLock 中同步狀態(tài)表示鎖被一個(gè)線程重復(fù)獲取的次數(shù)。
3.讀寫鎖自定義同步器需要在同步狀態(tài)上維護(hù)多個(gè)讀線程和一個(gè)寫線程的狀態(tài)窑业。
4.讀寫鎖同步器采用在一個(gè)4字節(jié)的整形變量上使用 按位切割 的方式來維護(hù)讀
寫線程的同步狀態(tài)钦幔。高16位用來表示讀,低16位用來表示寫常柄。
5.寫狀態(tài)增加1鲤氢,表示當(dāng)前線程獲取寫鎖,則 Status = S(當(dāng)前同步狀態(tài))+1,當(dāng)讀
狀態(tài)加1時(shí)卷玉,Status = S+(1<<16)
3.2 寫鎖的獲取與釋放
如下源碼所示:
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();
//獲取獨(dú)占鎖(寫鎖)的被獲取的數(shù)量
int w = exclusiveCount(c);
if (c != 0) {
// (Note: if c != 0 and w == 0 then shared count != 0)
//1.如果同步狀態(tài)不為0蚂子,且寫狀態(tài)為0,則表示當(dāng)前同步狀態(tài)被讀鎖獲取
//2.或者當(dāng)前擁有寫鎖的線程不是當(dāng)前線程
if (w == 0 || current != getExclusiveOwnerThread())
return false;
if (w + exclusiveCount(acquires) > MAX_COUNT)
throw new Error("Maximum lock count exceeded");
// Reentrant acquire
setState(c + acquires);
return true;
}
if (writerShouldBlock() ||
!compareAndSetState(c, c + acquires))
return false;
setExclusiveOwnerThread(current);
return true;
}
3.3 讀鎖的釋放與獲取
protected final int tryAcquireShared(int unused) {
for(;;) {
int c = getState();
int nextc = c + (1<<16);
if(nextc < c) {
throw new Error("Maxumum lock count exceeded");
}
if(exclusiveCount(c)!=0 && owner != Thread.currentThread())
return -1;
if(compareAndSetState(c,nextc))
return 1;
}
}
如果其他線程獲取了寫鎖,則當(dāng)前線程獲取讀鎖失敗惧互,進(jìn)入等待狀態(tài)。
如果當(dāng)前線程獲取了寫鎖或者寫鎖未被獲取稻据,則當(dāng)前線程安全,依靠CAS保證增加讀狀態(tài)算柳,成功獲取鎖。
3.4 鎖降級(jí)
鎖降級(jí)是指當(dāng)前把持住寫鎖猪杭,再獲取到讀鎖,隨后釋放(先前擁有的)寫鎖的過程。
鎖降級(jí)過程中的讀鎖的獲取是否有必要,答案是必要的。主要是為了保證數(shù)據(jù)的可見性掂器,如果當(dāng)前線程不獲取讀鎖而直接釋放寫鎖,假設(shè)此刻另一個(gè)線程獲取的寫鎖乃摹,并修改了數(shù)據(jù),那么當(dāng)前線程就步伐感知到線程T的數(shù)據(jù)更新,如果當(dāng)前線程遵循鎖降級(jí)的步驟,那么線程T將會(huì)被阻塞,直到當(dāng)前線程使數(shù)據(jù)并釋放讀鎖之后抵皱,線程T才能獲取寫鎖進(jìn)行數(shù)據(jù)更新善榛。