以前在學習一篇文章的時候,就看到「競態」,可是不知道什麼意思,文章中也沒有對「競態」作更多的解釋,後來通過一番的探索,終於弄的差很少明白了,今天寫點總結。 首先,咱們要明白「競態」是什麼。先說個人結論吧,「競態」就是在多線程的編程中,你在同一段代碼裏輸入了相同的條件,可是會輸出不肯定的結果的狀況。我不知道這個解釋是否是夠清楚,咱們接着往下看,下面咱們用一段代碼來解釋一下啊。 出現競態條件的代碼:java
public class MineRaceConditionDemo {
private int sharedValue = 0;
private final static int MAX = 1000;
private int raceCondition() {
if (sharedValue < MAX) {
sharedValue++;
} else {
sharedValue = 0;
}
return sharedValue;
}
public static void main(String[] args) {
MineRaceConditionDemo m = new MineRaceConditionDemo();
ExecutorService es = new ThreadPoolExecutor(10,
10,
5,
TimeUnit.MINUTES,
new ArrayBlockingQueue<Runnable>(1000),
new ThreadPoolExecutor.CallerRunsPolicy());
ConcurrentHashMap<Integer, Integer> map = new ConcurrentHashMap<>();
for (int i = 0; i < 1000; i++) {
es.execute(() -> {
try {
// 這是精髓所在啊,若是沒有這個,那麼要跑好幾回纔會出現競態條件。
// 這個用來模擬程序中別的代碼的處理時間。
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
int num = m.raceCondition();
if (map.get(num) != null) {
System.out.println("the repeat num: " + num);
System.out.println("happen.");
} else {
map.put(num, 0);
}
});
}
es.shutdown();
}
}
複製代碼
以上的代碼是我本身的設計的一段會出現競態條件的代碼,比較簡陋,可是能夠說明問題了,你只要運行上面這段代碼,每次的輸出的結果級大機率都是不一樣的,可是也有之外,好比你的電腦的性能很強,這段代碼也會出現執行正確的狀況,也就是啥也不輸出。 好比有的時候輸出這個:編程
the repeat num: 78
happen.
the repeat num: 229
happen.
the repeat num: 267
happen.
the repeat num: 267
happen.
the repeat num: 498
happen.
複製代碼
有點時候輸出這個:多線程
the repeat num: 25
happen.
the repeat num: 157
happen.
複製代碼
固然,以上的是個人輸出的,大家的輸出確定也是不一樣的。 對於上面這些,同一段代碼,對於一樣的輸出,可是程序的輸出有的時候是正確,有的時候是錯誤的,這種狀況,咱們稱之爲「競態」。最要命的就是,代碼每次輸出不是每次都錯誤,而是你不知道他何時會正確,何時會錯誤。 固然,若是以上的代碼執行的狀況就是,啥都不輸出,全部的值都是惟一的。app
「競態」的發生主要是由於多個線程都對一個共享變量(好比上面的 sharedValue
就屬於共享變量)有讀取-修改的操做。在某個線程讀取共享變量以後,進行相關操做的時候,別的線程把這個變量給改了,從而致使結果出現了錯誤。工具
這部分知識主要是來自《Java多線程編程實戰指南 核心篇》。 這裏書中提到,會發生競態條件就是兩個模式:read-modify-write(讀-改-寫)和 check-than-act(檢測然後行動)。 固然,這裏面的都有一個相同的操做過程:某個有讀取這個「共享變量」的操做,而後別的線程有個修改這個變量的操做。這裏有個重點,在多個線程中,起碼有一個線程有更新操做;若是全部的線程都是讀操做,那麼就不存在什麼競態條件。 整體來講,就是要thread1#load - thread2#update。 這種的模式,起碼是是要有兩個線程的,並且其中某個線程確定是要有更新「共享變量」操做的,另外一個線程不論是讀取變量仍是更新變量都會出現錯誤(要麼讀取髒數據、要麼丟失更新結果)。性能
單以上面的操做來講,通常來講有兩種解法方式,學習
加上synchronized
關鍵字,保證每次只能有一個線程獲取共享變量的使用權。spa
private synchronized int raceCondition() {
if (sharedValue < MAX) {
sharedValue++;
} else {
sharedValue = 0;
}
return sharedValue;
}
複製代碼
利用java的工具包裏的 AtomicInteger
,代替int
,利用原子操做消除「競態」。線程
private AtomicInteger sharedValue = new AtomicInteger(0);
private final static int MAX = 1000;
private int raceCondition() {
if (sharedValue.get() < MAX) {
return sharedValue.getAndIncrement();
} else {
sharedValue.set(0);
return sharedValue.get();
}
}
複製代碼
以上兩種方法的要義就是保證每一個線程在操做「共享變量」的都是原子操做。設計