在线看毛片网站电影-亚洲国产欧美日韩精品一区二区三区,国产欧美乱夫不卡无乱码,国产精品欧美久久久天天影视,精品一区二区三区视频在线观看,亚洲国产精品人成乱码天天看,日韩久久久一区,91精品国产91免费

<menu id="6qfwx"><li id="6qfwx"></li></menu>
    1. <menu id="6qfwx"><dl id="6qfwx"></dl></menu>

      <label id="6qfwx"><ol id="6qfwx"></ol></label><menu id="6qfwx"></menu><object id="6qfwx"><strike id="6qfwx"><noscript id="6qfwx"></noscript></strike></object>
        1. <center id="6qfwx"><dl id="6qfwx"></dl></center>

            博客專(zhuān)欄

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

            面試官:Java中如何保證線程安全性

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

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

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

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

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

            3.有序性:一個(gè)線程觀察其他線程中的指令執(zhí)行順序,由于指令重排序,該觀察結(jié)果一般雜亂無(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 {
                // 請(qǐng)求總數(shù)
                public static int clientTotal = 5000;
                // 同時(shí)并發(fā)執(zhí)行的線程數(shù)
                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);//定義信號(hào)量
                    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();
                }
            }

            我們可以執(zhí)行看到最后結(jié)果是5000是線程安全的。

            那么看AtomicInteger的incrementAndGet()方法:

            圖片

            再看getAndAddInt()方法:

            圖片

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

            圖片

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

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

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

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

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

            compareAndSwapInt核心就是CAS核心。

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

            (2)AtomicStampedReference

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

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

            這就用到了AtomicStampedReference。

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

            圖片

            而在AtomicInteger里compareAndSet()實(shí)現(xiàn):

            圖片

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

            (3)AtomicLongArray

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

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

            圖片(4)AtomicBoolean

            看一段代碼:

            public class AtomicBooleanExample {
             
                private static AtomicBoolean isHappened = new AtomicBoolean(false);
             
                // 請(qǐng)求總數(shù)
                public static int clientTotal = 5000;
                // 同時(shí)并發(fā)執(zhí)行的線程數(shù)
                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");
                    }
                }
            }

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

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

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

            三、原子性---synchronized

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

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

            synchronized修飾的對(duì)象有四種:

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

            修飾代碼塊和方法:

            @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);
                    });
                }
            }

            執(zhí)行后可以看到對(duì)于情況一,test1內(nèi)部方法塊作用于example1,先執(zhí)行完一次0-9輸出,再執(zhí)行下一次0-9輸出;情況二,同情況一類(lèi)似,作用于example2;情況三,可以看到交叉執(zhí)行,test1分別獨(dú)立作用于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ì)鎖定調(diào)用它們的對(duì)象所屬的類(lèi),同一個(gè)時(shí)間只有一個(gè)對(duì)象在執(zhí)行。

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

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

            (1)volatile的可見(jiàn)性是通過(guò)內(nèi)存屏障和禁止重排序?qū)崿F(xiàn)的

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

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

            (2)但是volatile不是原子性的,進(jìn)行++操作不是安全的
            @Slf4j
            public class VolatileExample {
             
                // 請(qǐng)求總數(shù)
                public static int clientTotal = 5000;
             
                // 同時(shí)并發(fā)執(zhí)行的線程數(shù)
                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++;
                }
            }

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

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

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

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

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

            線程1負(fù)責(zé)初始化,線程2不斷查詢inited值,當(dāng)線程1初始化完成后,線程2就可以檢測(cè)到inited為true了。

            五、有序性

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

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

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

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

            happens-before原則:

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


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



            關(guān)鍵詞: Java

            相關(guān)推薦

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

            關(guān)閉