java併發編程筆記(三)——線程安全性

java併發編程筆記(三)——線程安全性

線程安全性:java

​ 當多個線程訪問某個類時,無論運行時環境採用何種調度方式或者這些進程將如何交替執行,而且在主調代碼中不須要任何額外的同步或協同,這個類都能表現出正確的行爲,那麼就稱這個類是線程安全的。編程

線程安全體如今三個方面:數組

  • 原子性:提供了互斥訪問,同一時刻只能有一個線程來對它進行操做
  • 可見性:一個線程對主內存的修改能夠及時的被其餘線程觀察到
  • 有序性:一個線程觀察其餘線程中的指令執行順序,因爲指令重排序的存在,該觀察結果通常雜亂無序。

原子性:Atomic包

使用AtomicInteger保證該變量操做的原子性安全

public class CountExample2 {

    // 請求總數
    public static int clientTotal = 5000;

    // 同時併發執行的線程數
    public static int threadTotal = 200;

    public static AtomicInteger count = new AtomicInteger(0);

    public static void main(String[] args) throws Exception {
        ExecutorService executorService = Executors.newCachedThreadPool();
        final Semaphore semaphore = new Semaphore(threadTotal);
        final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
        for (int i = 0; i < clientTotal ; i++) {
            executorService.execute(() -> {
                try {
                    semaphore.acquire();
                    add();
                    semaphore.release();
                } catch (Exception e) {
                    log.error("exception", e);
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        executorService.shutdown();
        log.info("count:{}", count.get());
    }

    private static void add() {
        count.incrementAndGet();  //至關於++x;
        // count.getAndIncrement();    //至關於x++
    }
}

原理:AtomicInteger的incrementAndGet()方法裏邊用到了一個unsafe的類多線程

public final int incrementAndGet() {
        return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
    }

繼續深刻點進去看getAndAddInt的實現:併發

//
public final int getAndAddInt(Object var1, long var2, int var4) {
        int var5;
        do {
            var5 = this.getIntVolatile(var1, var2);
        } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

        return var5;
    }

這裏最重要的一個方法是:compareAndSwapInt(),這是java底層的一個方法,它不是經過java實現的:app

public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);

參數解釋:編輯器

Object var1:所操做的對象,好比本次案例中,這個Obect是AtomicInteger count;高併發

long var2:這個對象當前的值;性能

int var4:當前對象要增長的值,好比本次案例中作+1操做,那麼var4就是1;

int var5:調用底層獲得的一個值,若是沒有其餘線程過來操做,這個值應該是等於var2

getAndAddInt()方法中compareAndSwapInt()方法執行解釋:若是對於var1這個對象,若是var2與從底層獲取的值var5是相同的,那麼就執行var5 + var4;

進一步解釋:count的當前值,是當前線程中的值,屬於線程中的工做內存中的值,而底層獲取的值是主存中值,只有當工做內存中的值和主存中的值是一致的時候,才能夠修改。

AtomicLong、LongAdder

在上邊的例子中,把AtomicInteger 替換成AtomicLong,整個方法依然是線程安全的。

第二種方式是使用LongAdder:

public class AtomicExample3 {

    // 請求總數
    public static int clientTotal = 5000;

    // 同時併發執行的線程數
    public static int threadTotal = 200;

    public static LongAdder count = new LongAdder();

    public static void main(String[] args) throws Exception {
        ExecutorService executorService = Executors.newCachedThreadPool();
        final Semaphore semaphore = new Semaphore(threadTotal);
        final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
        for (int i = 0; i < clientTotal ; i++) {
            executorService.execute(() -> {
                try {
                    semaphore.acquire();
                    add();
                    semaphore.release();
                } catch (Exception e) {
                    log.error("exception", e);
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        executorService.shutdown();
        log.info("count:{}", count);
    }

    private static void add() {
        count.increment();
    }
}

AtomicLong和LongAdder的對比:

AtomicLong:該類底層實現是在一個死循環內,不斷的嘗試修改目標值,直到修改爲功,在競爭不激烈的狀況下,修改爲功機率很大,在競爭激烈狀況下修改失敗的機率較大,這種狀況下會有損性能。

LongAdder:因爲Long、Double類型的值JVM容許將他們64位的讀寫操做分拆成32位的讀寫操做,根據此原理 LongAdder將操做的數值分拆成數組,而後最終獲得的是數組的加和,經過分拆均衡操做壓力,所以其性能相對較好

使用場景的選擇:在高併發計數的情景下優先使用LongAdder,其餘狀況使用AtomicLong

**AtomicBoolean **

底層實現的方法是:

public final boolean compareAndSet(boolean expect, boolean update) {
        int e = expect ? 1 : 0;
        int u = update ? 1 : 0;
        return unsafe.compareAndSwapInt(this, valueOffset, e, u);
    }

這個方法是指某個代碼塊邏輯值執行一次。

使用案例(該案例演示了某一段代碼在多線程狀況下,只執行了一次):

public class AtomicExample6 {

    private static AtomicBoolean isHappened = new AtomicBoolean(false);

    // 請求總數
    public static int clientTotal = 5000;

    // 同時併發執行的線程數
    public static int threadTotal = 200;

    public static void main(String[] args) throws Exception {
        ExecutorService executorService = Executors.newCachedThreadPool();
        final Semaphore semaphore = new Semaphore(threadTotal);
        final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
        for (int i = 0; i < clientTotal ; i++) {
            executorService.execute(() -> {
                try {
                    semaphore.acquire();
                    test();
                    semaphore.release();
                } catch (Exception e) {
                    log.error("exception", e);
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        executorService.shutdown();
        log.info("isHappened:{}", isHappened.get());
    }

    private static void test() {
        if (isHappened.compareAndSet(false, true)) {
            log.info("execute");
        }
    }
}

AtomicReference、AtomicReferenceFieldUpdater

AtomicReference使用示例:

public class AtomicExample4 {

    private static AtomicReference<Integer> count = new AtomicReference<>(0);

    public static void main(String[] args) {
        count.compareAndSet(0, 2); // 2
        count.compareAndSet(0, 1); // no
        count.compareAndSet(1, 3); // no
        count.compareAndSet(2, 4); // 4
        count.compareAndSet(3, 5); // no
        log.info("count:{}", count.get());   //4
    }
}

AtomicReferenceFieldUpdater使用示例:

public class AtomicExample5 {

    private static AtomicIntegerFieldUpdater<AtomicExample5> updater =
            AtomicIntegerFieldUpdater.newUpdater(AtomicExample5.class, "count");

    @Getter
    public volatile int count = 100;

    public static void main(String[] args) {

        AtomicExample5 example5 = new AtomicExample5();

        if (updater.compareAndSet(example5, 100, 120)) {
            log.info("update success 1, {}", example5.getCount());
        }

        if (updater.compareAndSet(example5, 100, 120)) {
            log.info("update success 2, {}", example5.getCount());
        } else {
            log.info("update failed, {}", example5.getCount());
        }
    }
}

AtomicStampedReference:解決CAS的ABA問題

ABA問題:在CAS操做的時候,其餘線程將變量的值A改爲了B,可是又改回了A,本線程使用指望值A與當前變量進行比較的時候,發現變量A沒有變,因而CAS將A值進行了交換操做。

解決思路:每次變量更新的時候,把版本號+1

核心類:

AtomicStampedReference

其中的核心方法:compareAndSet()

public boolean compareAndSet(V   expectedReference,
                                 V   newReference,
                                 int expectedStamp,
                                 int newStamp) {
        Pair<V> current = pair;
        return
            expectedReference == current.reference &&
            expectedStamp == current.stamp &&
            ((newReference == current.reference &&
              newStamp == current.stamp) ||
             casPair(current, Pair.of(newReference, newStamp)));
    }

AtomicLongArray

這個類維護的是一個數組

這個類與AtomicLong比較,方法 裏多了一個索引值讓咱們指定。

原子性——鎖

  • synchronized:依賴JVM去實現鎖
  • Lock:依賴特殊的cpu指令,代碼實現,ReenteantLock

synchronized:

  • 修飾代碼塊:大括號括起來的代碼,做用於調用的對象

  • 修飾方法:整個方法,做用於調用的對象

  • 修飾靜態方法:整個靜態方法,做用於全部對象

  • 修飾類,括號括起來的部分,做用於全部對象

原子性——對比

synchronized:不可中斷鎖,適合競爭不激烈,可讀性好

Lock:可中斷鎖,多樣化同步,競爭激烈時能維持常態

Atomic:競爭激烈時能維持常態,比Lock性能好;只能同步一個值

可見性

致使共享變量在線程間不可見的緣由

  • 線程交叉執行
  • 重排序結合線程交叉執行
  • 共享變量更新後的值沒有在工做內存與主內存間及時更新

**可見性——**synchronized

JMM關於synchronized的兩條規定:

  • 線程解鎖前,必須把共享變量的最新值刷新到主內存
  • 線程加鎖時,將清空工做內存中共享變量的值,從而使用共享變量時須要從主內存中從新讀取最新的值(注意:加鎖和解鎖是同一把鎖)

**可見性——**volatile

經過假如內存屏障和禁止重排序優化來實現

  • 對volatile變量寫操做時,會在寫操做後加入一條store屏障指令,將本地內存中的共享變量值刷新到主內
  • 對volatileb變量讀操做時,會在讀操做前加入一條load屏障指令,從主內存中讀取共享變量

volatile關鍵字不具備原子性

適合的場景:

  • 對變量的寫操做不依賴與當前值;
  • 該變量沒有包含在具備其餘變量的不變式中。

所以volatile特別適合狀態標記量

有序性

java內存模型中,容許編輯器和處理器對指令進行重排序,可是重排序過程不會影響到單線程程序的執行,卻會影響到多線程併發執行的正確性。

一般狀況下能夠經過如下三個關鍵字來保證有序性:

  • volatile
  • synchronized
  • Lock

happens-before原則

若是兩個操做的執行次序沒法從happens-before原則推導出來,那麼就不能保證他們的有序性,虛擬機就能夠對他們隨意的進行重排序。

也就是除了下面這些規則規定的場景,其餘場景,虛擬機能夠對其進行重排序。

  • 程序次序規則:一個線程內,按照代碼順序,書寫在前面的操做先行發生於書寫在後面的操做

  • 鎖定規則:一個unLock操做先行發生於後面對同一個鎖的lock操做

  • volatile變量規則:對一個變量的寫操做先行發生於後面對這個變量的讀操做

  • 傳遞規則:若是操做A先行發生於操做B,而操做B又先行發生於操做C,則能夠得出操做A先行發生於操做C

  • 線程啓動規則:Thread對象的start()方法先行發生於此線程的每個動做

  • 線程中斷原則:對線程interrupt()方法的調用先行發生於被中斷線程的代碼檢測到中斷事件的發生。

  • 線程終結規則:線程中全部的操做都先行發生於線程的終止檢測,咱們能夠經過Thread.join()方法結束、Thread.isAlive()的返回值手段檢測到線程已經終止執行。

  • 對象終結規則:一個對象的初始化完成先行發生於它的finalize()方法的開始

相關文章
相關標籤/搜索