???????看到這個(gè)春寿,忍不住有點(diǎn)激動(dòng)人心。沒有人喜歡在代碼中使用同步忽孽,它會(huì)讓你的程序效率更低绑改,而且嚴(yán)重的還可能會(huì)引起程序崩潰。盡管如此扒腕,有時(shí)候我們還是不得不使用它绢淀。
???????當(dāng)多個(gè)進(jìn)程訪問一個(gè)資源的時(shí)候,有多種方法可以進(jìn)行同步瘾腰。其中用得最多的一種是ReadWriteLock以及基于它的幾種實(shí)現(xiàn)皆的。它通過阻塞寫線程的方式來允許多個(gè)線程并發(fā)的讀,這樣減少了線程之間的競(jìng)爭(zhēng)蹋盆。聽起來還不錯(cuò)费薄,但實(shí)際上這個(gè)鎖實(shí)在是太慢了,尤其是當(dāng)有許多寫線程的時(shí)候栖雾。
???????值得高興的是楞抡,現(xiàn)在Java 8中推出了一個(gè)新的讀寫鎖,名字叫StampedLock析藕。StampedLock不僅讀寫更快召廷,而且還提供了很多強(qiáng)大的API來創(chuàng)建樂觀鎖。這樣如果沒有寫操作在訪問臨界區(qū)域的話账胧,你只需很低的開銷就能獲取到一個(gè)讀鎖竞慢。訪問結(jié)束后你可以查詢鎖來判斷這期間是否發(fā)生了寫操作,如果有的話再選擇進(jìn)行重試治泥,升級(jí)鎖筹煮,或者放棄這個(gè)操作。
???????ReentrantReadWriteLock 在沒有任何讀寫鎖時(shí)居夹,才可以取得寫入鎖败潦,這可用于實(shí)現(xiàn)了悲觀讀取(Pessimistic Reading)准脂,即如果執(zhí)行中進(jìn)行讀取時(shí)劫扒,經(jīng)常可能有另一執(zhí)行要寫入的需求狸膏,為了保持同步粟关,ReentrantReadWriteLock 的讀取鎖定就可派上用場(chǎng)。
???????然而环戈,如果讀取執(zhí)行情況很多闷板,寫入很少的情況下,使用 ReentrantReadWriteLock 可能會(huì)使寫入線程遭遇饑餓(Starvation)問題院塞,也就是寫入線程吃吃無法競(jìng)爭(zhēng)到鎖定而一直處于等待狀態(tài)遮晚。
???????StampedLock控制鎖有三種模式(寫,讀拦止,樂觀讀)县遣,一個(gè)StampedLock狀態(tài)是由版本和模式兩個(gè)部分組成,鎖獲取方法返回一個(gè)數(shù)字作為票據(jù)stamp汹族,它用相應(yīng)的鎖狀態(tài)表示并控制訪問萧求,數(shù)字0表示沒有寫鎖被授權(quán)訪問。在讀鎖上分為悲觀鎖和樂觀鎖顶瞒。
???????所謂的樂觀讀模式夸政,也就是若讀的操作很多,寫的操作很少的情況下榴徐,你可以樂觀地認(rèn)為守问,寫入與讀取同時(shí)發(fā)生幾率很少,因此不悲觀地使用完全的讀取鎖定坑资,程序可以查看讀取資料之后耗帕,是否遭到寫入執(zhí)行的變更,再采取后續(xù)的措施(重新讀取變更信息袱贮,或者拋出異常) 仿便,這一個(gè)小小改進(jìn),可大幅度提高程序的吞吐量T芪 嗽仪!
下面是java doc提供的StampedLock一個(gè)例子
class Point {
private double x, y;
private final StampedLock sl = new StampedLock();
void move(double deltaX, double deltaY) { // an exclusively locked method
long stamp = sl.writeLock();
try {
x += deltaX;
y += deltaY;
} finally {
sl.unlockWrite(stamp);
}
}
//下面看看樂觀讀鎖案例
double distanceFromOrigin() { // A read-only method
long stamp = sl.tryOptimisticRead(); //獲得一個(gè)樂觀讀鎖
double currentX = x, currentY = y; //將兩個(gè)字段讀入本地局部變量
if (!sl.validate(stamp)) { //檢查發(fā)出樂觀讀鎖后同時(shí)是否有其他寫鎖發(fā)生?
stamp = sl.readLock(); //如果沒有窑业,我們?cè)俅潍@得一個(gè)讀悲觀鎖
try {
currentX = x; // 將兩個(gè)字段讀入本地局部變量
currentY = y; // 將兩個(gè)字段讀入本地局部變量
} finally {
sl.unlockRead(stamp);
}
}
return Math.sqrt(currentX * currentX + currentY * currentY);
}
/**
* 下面是悲觀讀鎖案例
*/
void moveIfAtOrigin(double newX, double newY) { // upgrade
// Could instead start with optimistic, not read mode
long stamp = sl.readLock();
try {
while (x == 0.0 && y == 0.0) { //循環(huán)钦幔,檢查當(dāng)前狀態(tài)是否符合
long ws = sl.tryConvertToWriteLock(stamp); //將讀鎖轉(zhuǎn)為寫鎖
if (ws != 0 L) { //這是確認(rèn)轉(zhuǎn)為寫鎖是否成功
stamp = ws; //如果成功 替換票據(jù)
x = newX; //進(jìn)行狀態(tài)改變
y = newY; //進(jìn)行狀態(tài)改變
break;
} else { //如果不能成功轉(zhuǎn)換為寫鎖
sl.unlockRead(stamp); //我們顯式釋放讀鎖
stamp = sl.writeLock(); //顯式直接進(jìn)行寫鎖 然后再通過循環(huán)再試
}
}
} finally {
sl.unlock(stamp); //釋放讀鎖或?qū)戞i
}
}
}