<dfn id="yhprb"><s id="yhprb"></s></dfn><dfn id="yhprb"><delect id="yhprb"></delect></dfn><dfn id="yhprb"></dfn><dfn id="yhprb"><delect id="yhprb"></delect></dfn><dfn id="yhprb"></dfn><dfn id="yhprb"><s id="yhprb"><strike id="yhprb"></strike></s></dfn><small id="yhprb"></small><dfn id="yhprb"></dfn><small id="yhprb"><delect id="yhprb"></delect></small><small id="yhprb"></small><small id="yhprb"></small> <delect id="yhprb"><strike id="yhprb"></strike></delect><dfn id="yhprb"></dfn><dfn id="yhprb"></dfn><s id="yhprb"><noframes id="yhprb"><small id="yhprb"><dfn id="yhprb"></dfn></small><dfn id="yhprb"><delect id="yhprb"></delect></dfn><small id="yhprb"></small><dfn id="yhprb"><delect id="yhprb"></delect></dfn><dfn id="yhprb"><s id="yhprb"></s></dfn> <small id="yhprb"></small><delect id="yhprb"><strike id="yhprb"></strike></delect><dfn id="yhprb"><s id="yhprb"></s></dfn><dfn id="yhprb"></dfn><dfn id="yhprb"><s id="yhprb"></s></dfn><dfn id="yhprb"><s id="yhprb"><strike id="yhprb"></strike></s></dfn><dfn id="yhprb"><s id="yhprb"></s></dfn>
"); //-->

博客專(zhuān)欄

EEPW首頁(yè) > 博客 > 面試官:Java中如何保證線(xiàn)程安全性

面試官:Java中如何保證線(xiàn)程安全性

發(fā)布人:編碼之外 時(shí)間:2021-09-28 來(lái)源:工程師 發(fā)布文章

來(lái)源:blog.csdn.net/weixin_40459875/
article/details/80290875

一、線(xiàn)程安全在三個(gè)方面體現

1.原子性:提供互斥訪(fǎng)問(wèn),同一時(shí)刻只能有一個(gè)線(xiàn)程對數據進(jìn)行操作,(atomic,synchronized);

2.可見(jiàn)性:一個(gè)線(xiàn)程對主內存的修改可以及時(shí)地被其他線(xiàn)程看到,(synchronized,volatile);

3.有序性:一個(gè)線(xiàn)程觀(guān)察其他線(xiàn)程中的指令執行順序,由于指令重排序,該觀(guān)察結果一般雜亂無(wú)序,(happens-before原則)。

接下來(lái),依次分析。

二、原子性---atomic

JDK里面提供了很多atomic類(lèi),AtomicInteger,AtomicLong,AtomicBoolean等等。

它們是通過(guò)CAS完成原子性。

我們一次來(lái)看AtomicInteger,AtomicStampedReference,AtomicLongArray,AtomicBoolean。

(1)AtomicInteger

先來(lái)看一個(gè)AtomicInteger例子:

public class AtomicIntegerExample1 {
    // 請求總數
    public static int clientTotal = 5000;
    // 同時(shí)并發(fā)執行的線(xiàn)程數
    public static int threadTotal = 200;
 
    public static AtomicInteger count = new AtomicInteger(0);
 
    public static void main(String[] args) throws Exception {
        ExecutorService executorService = Executors.newCachedThreadPool();//獲取線(xiàn)程池
        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();
    }
}

我們可以執行看到最后結果是5000是線(xiàn)程安全的。

那么看AtomicInteger的incrementAndGet()方法:

圖片

再看getAndAddInt()方法:

圖片

這里面調用了compareAndSwapInt()方法:

圖片

它是native修飾的,代表是java底層的方法,不是通過(guò)java實(shí)現的 。

再重新看getAndAddInt(),傳來(lái)第一個(gè)值是當前的一個(gè)對象 ,比如是count.incrementAndGet(),那么在getAndAddInt()中,var1就是count,而var2第二個(gè)值是當前的值,比如想執行的是2+1=3操作,那么第二個(gè)參數是2,第三個(gè)參數是1 。

變量5(var5)是我們調用底層的方法而得到的底層當前的值,如果沒(méi)有別的線(xiàn)程過(guò)來(lái)處理我們count變量的時(shí)候,那么它正常返回值是2。

因此傳到compareAndSwapInt方法里的參數是(count對象,當前值2,當前從底層傳過(guò)來(lái)的2,從底層取出來(lái)的值加上改變量var4)。

compareAndSwapInt()希望達到的目標是對于var1對象,如果當前的值var2和底層的值var5相等,那么把它更新成后面的值(var5+var4).

compareAndSwapInt核心就是CAS核心。

關(guān)于count值為什么和底層值不一樣:count里面的值相當于存在于工作內存的值,底層就是主內存。

(2)AtomicStampedReference

接下來(lái)我們看一下AtomicStampedReference。

關(guān)于CAS有一個(gè)ABA問(wèn)題:開(kāi)始是A,后來(lái)改為B,現在又改為A。解決辦法就是:每次變量改變的時(shí)候,把變量的版本號加1。

這就用到了AtomicStampedReference。

我們來(lái)看AtomicStampedReference里的compareAndSet()實(shí)現:

圖片

而在A(yíng)tomicInteger里compareAndSet()實(shí)現:

圖片

可以看到AtomicStampedReference里的compareAndSet()中多了 一個(gè)stamp比較(也就是版本),這個(gè)值是由每次更新時(shí)來(lái)維護的。

(3)AtomicLongArray

這種維護數組的atomic類(lèi),我們可以選擇性地更新其中某一個(gè)索引對應的值,也是進(jìn)行原子性操作。這種對數組的操作的各種方法,會(huì )多處一個(gè)索引。

比如,我們看一下compareAndSet():

圖片(4)AtomicBoolean

看一段代碼:

public class AtomicBooleanExample {
 
    private static AtomicBoolean isHappened = new AtomicBoolean(false);
 
    // 請求總數
    public static int clientTotal = 5000;
    // 同時(shí)并發(fā)執行的線(xiàn)程數
    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(falsetrue)) {
            log.info("execute");
        }
    }
}

執行之后發(fā)現,log.info("execute");只執行了一次,且isHappend值為true。

原因就是當它第一次compareAndSet()之后,isHappend變?yōu)閠rue,沒(méi)有別的線(xiàn)程干擾。

通過(guò)使用AtomicBoolean,我們可以使某段代碼只執行一次。

三、原子性---synchronized

synchronized是一種同步鎖,通過(guò)鎖實(shí)現原子操作。

JDK提供鎖分兩種:一種是synchronized,依賴(lài)JVM實(shí)現鎖,因此在這個(gè)關(guān)鍵字作用對象的作用范圍內是同一時(shí)刻只能有一個(gè)線(xiàn)程進(jìn)行操作;另一種是LOCK,是JDK提供的代碼層面的鎖,依賴(lài)CPU指令,代表性的是ReentrantLock。

synchronized修飾的對象有四種:

  • 修飾代碼塊,作用于調用的對象;
  • 修飾方法,作用于調用的對象;
  • 修飾靜態(tài)方法,作用于所有對象;
  • 修飾類(lèi),作用于所有對象。

修飾代碼塊和方法:

@Slf4j
public class SynchronizedExample1 {
 
    // 修飾一個(gè)代碼塊
    public void test1(int j) {
        synchronized (this) {
            for (int i = 0; i < 10; i++) {
                log.info("test1 {} - {}", j, i);
            }
        }
    }
 
    // 修飾一個(gè)方法
    public synchronized void test2(int j) {
        for (int i = 0; i < 10; i++) {
            log.info("test2 {} - {}", j, i);
        }
    }
 
    public static void main(String[] args) {
        SynchronizedExample1 example1 = new SynchronizedExample1();
        SynchronizedExample1 example2 = new SynchronizedExample1();
        ExecutorService executorService = Executors.newCachedThreadPool();
        //一
        executorService.execute(() -> {
            example1.test1(1);
        });
        executorService.execute(() -> {
            example1.test1(2);
        });
        //二
        executorService.execute(() -> {
            example2.test2(1);
        });
        executorService.execute(() -> {
            example2.test2(2);
        });
        //三
        executorService.execute(() -> {
            example1.test1(1);
        });
        executorService.execute(() -> {
            example2.test1(2);
        });
    }
}

執行后可以看到對于情況一,test1內部方法塊作用于example1,先執行完一次0-9輸出,再執行下一次0-9輸出;情況二,同情況一類(lèi)似,作用于example2;情況三,可以看到交叉執行,test1分別獨立作用于example1和example2,互不影響。

修飾靜態(tài)方法和類(lèi):

@Slf4j
public class SynchronizedExample2 {
 
    // 修飾一個(gè)類(lèi)
    public static void test1(int j) {
        synchronized (SynchronizedExample2.class{
            for (int i = 0; i < 10; i++) {
                log.info("test1 {} - {}", j, i);
            }
        }
    }
 
    // 修飾一個(gè)靜態(tài)方法
    public static synchronized void test2(int j) {
        for (int i = 0; i < 10; i++) {
            log.info("test2 {} - {}", j, i);
        }
    }
 
    public static void main(String[] args) {
        SynchronizedExample2 example1 = new SynchronizedExample2();
        SynchronizedExample2 example2 = new SynchronizedExample2();
        ExecutorService executorService = Executors.newCachedThreadPool();
        executorService.execute(() -> {
            example1.test1(1);
        });
        executorService.execute(() -> {
            example2.test1(2);
        });
    }
}

test1和test2會(huì )鎖定調用它們的對象所屬的類(lèi),同一個(gè)時(shí)間只有一個(gè)對象在執行。

四、可見(jiàn)性---volatile

對于可見(jiàn)性,JVM提供了synchronized和volatile。這里我們看volatile。

(1)volatile的可見(jiàn)性是通過(guò)內存屏障和禁止重排序實(shí)現的

volatile會(huì )在寫(xiě)操作時(shí),會(huì )在寫(xiě)操作后加一條store屏障指令,將本地內存中的共享變量值刷新到主內存:

volatile在進(jìn)行讀操作時(shí),會(huì )在讀操作前加一條load指令,從內存中讀取共享變量:

(2)但是volatile不是原子性的,進(jìn)行++操作不是安全的
@Slf4j
public class VolatileExample {
 
    // 請求總數
    public static int clientTotal = 5000;
 
    // 同時(shí)并發(fā)執行的線(xiàn)程數
    public static int threadTotal = 200;
 
    public static volatile 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.erro("exception", e);
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        executorService.shutdown();
        log.info("count:{}", count);
    }
 
    private static void add() {
        count++;
    }
}

執行后發(fā)現線(xiàn)程不安全,原因是 執行conut++ 時(shí)分成了三步,第一步是取出當前內存 count 值,這時(shí) count 值時(shí)最新的,接下來(lái)執行了兩步操作,分別是 +1 和重新寫(xiě)回主存。假設有兩個(gè)線(xiàn)程同時(shí)在執行 count++ ,兩個(gè)內存都執行了第一步,比如當前 count 值為 5 ,它們都讀到了,然后兩個(gè)線(xiàn)程分別執行了 +1 ,并寫(xiě)回主存,這樣就丟掉了一次加一的操作。

(3)volatile適用的場(chǎng)景

既然volatile不適用于計數,那么volatile適用于哪些場(chǎng)景呢:

  1. 對變量的寫(xiě)操作不依賴(lài)于當前值
  2. 該變量沒(méi)有包含在具有其他變量不變的式子中

因此,volatile適用于狀態(tài)標記量:

線(xiàn)程1負責初始化,線(xiàn)程2不斷查詢(xún)inited值,當線(xiàn)程1初始化完成后,線(xiàn)程2就可以檢測到inited為true了。

五、有序性

有序性是指,在JMM中,允許編譯器和處理器對指令進(jìn)行重排序,但是重排序過(guò)程不會(huì )影響到單線(xiàn)程程序的執行,卻會(huì )影響到多線(xiàn)程并發(fā)執行的正確性。

可以通過(guò)volatile、synchronized、lock保證有序性。

另外,JMM具有先天的有序性,即不需要通過(guò)任何手段就可以得到保證的有序性。這稱(chēng)為happens-before原則。

如果兩個(gè)操作的執行次序無(wú)法從happens-before原則推導出來(lái),那么它們就不能保證它們的有序性。虛擬機可以隨意地對它們進(jìn)行重排序。

happens-before原則:

  1. 程序次序規則:在一個(gè)單獨的線(xiàn)程中,按照程序代碼書(shū)寫(xiě)的順序執行。
  2. 鎖定規則:一個(gè)unlock操作happen—before后面對同一個(gè)鎖的lock操作。
  3. volatile變量規則:對一個(gè)volatile變量的寫(xiě)操作happen—before后面對該變量的讀操作。
  4. 線(xiàn)程啟動(dòng)規則:Thread對象的start()方法happen—before此線(xiàn)程的每一個(gè)動(dòng)作。
  5. 線(xiàn)程終止規則:線(xiàn)程的所有操作都happen—before對此線(xiàn)程的終止檢測,可以通過(guò)Thread.join()方法結束、Thread.isAlive()的返回值等手段檢測到線(xiàn)程已經(jīng)終止執行。
  6. 線(xiàn)程中斷規則:對線(xiàn)程interrupt()方法的調用happen—before發(fā)生于被中斷線(xiàn)程的代碼檢測到中斷時(shí)事件的發(fā)生。
  7. 對象終結規則:一個(gè)對象的初始化完成(構造函數執行結束)happen—before它的finalize()方法的開(kāi)始。
  8. 傳遞性:如果操作A happen—before操作B,操作B happen—before操作C,那么可以得出A happen—before操作C。


*博客內容為網(wǎng)友個(gè)人發(fā)布,僅代表博主個(gè)人觀(guān)點(diǎn),如有侵權請聯(lián)系工作人員刪除。



關(guān)鍵詞: Java

相關(guān)推薦

技術(shù)專(zhuān)區

關(guān)閉
国产精品自在自线亚洲|国产精品无圣光一区二区|国产日产欧洲无码视频|久久久一本精品99久久K精品66|欧美人与动牲交片免费播放
<dfn id="yhprb"><s id="yhprb"></s></dfn><dfn id="yhprb"><delect id="yhprb"></delect></dfn><dfn id="yhprb"></dfn><dfn id="yhprb"><delect id="yhprb"></delect></dfn><dfn id="yhprb"></dfn><dfn id="yhprb"><s id="yhprb"><strike id="yhprb"></strike></s></dfn><small id="yhprb"></small><dfn id="yhprb"></dfn><small id="yhprb"><delect id="yhprb"></delect></small><small id="yhprb"></small><small id="yhprb"></small> <delect id="yhprb"><strike id="yhprb"></strike></delect><dfn id="yhprb"></dfn><dfn id="yhprb"></dfn><s id="yhprb"><noframes id="yhprb"><small id="yhprb"><dfn id="yhprb"></dfn></small><dfn id="yhprb"><delect id="yhprb"></delect></dfn><small id="yhprb"></small><dfn id="yhprb"><delect id="yhprb"></delect></dfn><dfn id="yhprb"><s id="yhprb"></s></dfn> <small id="yhprb"></small><delect id="yhprb"><strike id="yhprb"></strike></delect><dfn id="yhprb"><s id="yhprb"></s></dfn><dfn id="yhprb"></dfn><dfn id="yhprb"><s id="yhprb"></s></dfn><dfn id="yhprb"><s id="yhprb"><strike id="yhprb"></strike></s></dfn><dfn id="yhprb"><s id="yhprb"></s></dfn>