臨界資源: 指并發(fā)環(huán)境中多個(gè)進(jìn)程/線程共享的資源.
在并發(fā)編程中對(duì)臨界資源的處理不當(dāng), 往往會(huì)導(dǎo)致數(shù)據(jù)不一致的問題. 例如有一份賬戶數(shù)據(jù) account = 6
, 它作為一份臨界資源被兩個(gè)線程同時(shí)消費(fèi).
public class Test {
private volatile int account = 6;
public void consume(int amount) {
// 1. 讀取賬戶
int currentAccount = account;
// 2. 消費(fèi)賬戶
if (currentAccount >= amount) {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
currentAccount -= amount;
System.out.println(String.format("消費(fèi)了%s元", amount));
// 3. 更新賬戶
account = currentAccount;
}
}
public int getAccount() {
return account;
}
public static void main(String[] args) {
final Test testObj = new Test();
Thread threadA = new Thread(() -> testObj.consume(5));
Thread threadB = new Thread(() -> testObj.consume(6));
threadA.start();
threadB.start();
try {
threadA.join();
threadB.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(String.format("賬戶剩余: %s元", testObj.getAccount()));
}
}
運(yùn)行程序后, 你會(huì)發(fā)現(xiàn)兩個(gè)線程都消費(fèi)成功了, 總共消費(fèi)了 11 元. 究其原因, 是因?yàn)?strong>一個(gè)線程將臨界資源置于中間狀態(tài)后, 另一個(gè)線程訪問了這個(gè)中間狀態(tài)并基于此中間狀態(tài)做了進(jìn)一步的處理. 結(jié)合上面的例子, 當(dāng)線程 A 以寫的目的讀取 account
時(shí), 就已經(jīng)將 account
置于中間狀態(tài)了. 直至線程 A 完成對(duì) account
的所有操作前, account
都處于中間狀態(tài), 而這個(gè)狀態(tài)對(duì)其他線程應(yīng)該是不可見的. 而上例中的線程 B 因?yàn)樽x取了 account
的中間狀態(tài), 并基于這個(gè)中間狀態(tài)做了一系列的處理, 從而導(dǎo)致了數(shù)據(jù)最終不一致的問題.
總結(jié):
- 純粹的讀操作并不會(huì)將臨界資源置于中間狀態(tài);
- 以寫為目的的讀操作會(huì)將臨界資源置于中間狀態(tài);
- 處于中間狀態(tài)的臨界資源不支持其他線程以寫為目的的讀操作, 更不支持寫操作;
- 根據(jù)實(shí)際情況 (可否忍受臟讀), 處于中間狀態(tài)的臨界資源可以支持其他線程純粹的讀操作;
正確理解臨界資源的中間狀態(tài), 對(duì)于維護(hù)數(shù)據(jù)的一致性問題非常重要.