在併發編程中咱們常說的「競態」是什麼?

1 何謂「競態」

以前在學習一篇文章的時候,就看到「競態」,可是不知道什麼意思,文章中也沒有對「競態」作更多的解釋,後來通過一番的探索,終於弄的差很少明白了,今天寫點總結。 首先,咱們要明白「競態」是什麼。先說個人結論吧,「競態」就是在多線程的編程中,你在同一段代碼裏輸入了相同的條件,可是會輸出不肯定的結果的狀況。我不知道這個解釋是否是夠清楚,咱們接着往下看,下面咱們用一段代碼來解釋一下啊。 出現競態條件的代碼: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

2 「競態」爲何會發生?

「競態」的發生主要是由於多個線程都對一個共享變量(好比上面的 sharedValue 就屬於共享變量)有讀取-修改的操做。在某個線程讀取共享變量以後,進行相關操做的時候,別的線程把這個變量給改了,從而致使結果出現了錯誤。工具

什麼樣的代碼模式會發生「競態」

這部分知識主要是來自《Java多線程編程實戰指南 核心篇》。 這裏書中提到,會發生競態條件就是兩個模式:read-modify-write(讀-改-寫)和 check-than-act(檢測然後行動)。 固然,這裏面的都有一個相同的操做過程:某個有讀取這個「共享變量」的操做,而後別的線程有個修改這個變量的操做。這裏有個重點,在多個線程中,起碼有一個線程有更新操做;若是全部的線程都是讀操做,那麼就不存在什麼競態條件。 整體來講,就是要thread1#load - thread2#update。 這種的模式,起碼是是要有兩個線程的,並且其中某個線程確定是要有更新「共享變量」操做的,另外一個線程不論是讀取變量仍是更新變量都會出現錯誤(要麼讀取髒數據、要麼丟失更新結果)。性能

3 如何消除「競態」?

單以上面的操做來講,通常來講有兩種解法方式,學習

3.1 加鎖

加上synchronized關鍵字,保證每次只能有一個線程獲取共享變量的使用權。spa

private synchronized int raceCondition() {
    if (sharedValue < MAX) {
        sharedValue++;
    } else {
        sharedValue = 0;
    }
    return sharedValue;
}
複製代碼

3.2 利用原子操做

利用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();
    }
}
複製代碼

以上兩種方法的要義就是保證每一個線程在操做「共享變量」的都是原子操做。設計

相關文章
相關標籤/搜索