Java併發--線程安全性

一、主要內容

二、基礎知識講解

三、線程安全性

 

 

一、主要內容

二、基礎知識講解

2.1  CPU多級緩存

  緩存一致性 MESIjava

2.2 Java內存模型(JMM)

 

三、線程安全性 

 

 3.1 原子性-Atomic包    package java.util.concurrent.atomic;

給定一個模擬併發訪問的例子,不作任何的處理緩存

@Slf4j
@NotThreadSafe
public class CountExample1 {

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

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

    public static int count = 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);
    }

    private static void add() {
        count++;
    }
}
CountExample1

這裏面模擬共有5000個請求,併發請求是200個,而後一次請求就將count加一,運行的話會發現count的值比5000小安全

如今咱們對上面代碼進行修改,將count進行原子性併發

@Slf4j
@NotThreadSafe
public class AtomicExample1 {

    // 請求總數
    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);
    }

    private static void add() {
        count.incrementAndGet(); // count.getAndIncrement();
    }
}

這裏count是AtomicInteger類型,咱們使用了incrementAndGet()這個方法,這裏進行源碼分析:app

incrementAndGet()調用了unsafe類的getAndAddInt()方法,三個參數分別是count自身,count當前值,加法運算的加數ide

咱們繼續查看getAndAddInt()的源碼源碼分析

主體實現是do...while...,這裏的getIntVolatile()方法是native方法,它是從主內存中獲取count所表明的數的值;而後是compareAndSwapInt()方法,也是native方法,該方法先會取得var2的值,而後比較var2與var5,也就是當前線程工做內存中的count值 與 主內存中的值,若是相等,就將var5 + var4(也就是1),賦值給工做內存;不然,從新取var5,var2比較。而後返回var5,舊值。ui

CASatom

 1)AtomicIntegerFiledUpdater類spa

該類的核心做用是原子性更新一個類指定的一個字段的值,該字段必須是volatile修飾,且非static

@Slf4j
@ThreadSafe
public class AtomicExample2 {
    private static AtomicIntegerFieldUpdater<AtomicExample2> updater =
            AtomicIntegerFieldUpdater.newUpdater(AtomicExample2.class, "count");

    @Getter
    public volatile int count = 100;

    public static void main(String[] args) {
        AtomicExample2 example2 = new AtomicExample2();
        if(updater.compareAndSet(example2, 100, 120)) {
            log.info("success, count:{}", example2.getCount());
        }
        if(updater.compareAndSet(example2, 100, 120)) {
            log.info("success, count:{}", example2.getCount());
        } else {
            log.info("failed, count:{}", example2.getCount());
        }
    }
}
AtomicIntegerFieldUpdater

2)AtomicStampedReference類

解決CAS的ABA問題。什麼是ABA問題,就是一個線程1在拿線程中的當前值爲A與主內存的值比較的時候,另外一個線程2已經將該主存中的值改成B又改回了A,致使線程1的比較成立。但這跟不符合CAS的設計。

因此咱們在一個值每次進行修改的時候,加一個版本號,也就是Stamp,每次再加一個版本號的比較,解決此問題。

3) AtomicBoolean類

保證一段代碼只執行一次

private static AtomicBoolean isHappened = new AtomicBoolean(false);

if(isHappened.compareAndSet(false, true)) {
    // 執行的代碼
}

 

 3.2 原子性--synchronized  同步,關鍵字

做用於調用的對象,表示是某一個對象。

 

 3.3 可見性   一個線程對主內存的修改能夠及時的被其餘線程觀察到

解決方法能夠用上面提到的synchronized

還可使用volatile 關鍵字

volatile變量在每次被線程訪問時,都強迫從主內存中讀取該變量的值,在該變量發生變化的時候,會強迫線程將最新的值刷新到主內存。

 

因此的操做是指令級別的,只須要了解便可

volatile的使用場景:狀態標記量

 

3.4 有序性 

若是兩個操做的執行順序沒法從happens-before原則推導出來,就沒法保證他們的有序性,JVM就能夠對其進行重排序

相關文章
相關標籤/搜索