1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        Java中如何保證線程安全性

        共 17611字,需瀏覽 36分鐘

         ·

        2021-08-28 00:30

        點擊下方“IT牧場”,選擇“設(shè)為星標(biāo)”

        來源:blog.csdn.net/weixin_40459875/

        article/details/80290875

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

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

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

        3.有序性:一個線程觀察其他線程中的指令執(zhí)行順序,由于指令重排序,該觀察結(jié)果一般雜亂無序,(happens-before原則)。

        接下來,依次分析。

        二、原子性---atomic

        JDK里面提供了很多atomic類,AtomicInteger,AtomicLong,AtomicBoolean等等。

        它們是通過CAS完成原子性。

        我們一次來看AtomicInteger,AtomicStampedReference,AtomicLongArrayAtomicBoolean。

        (1)AtomicInteger

        先來看一個AtomicInteger例子:

        public class AtomicIntegerExample1 {
            // 請求總數(shù)
            public static int clientTotal = 5000;
            // 同時并發(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);//定義信號量
                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底層的方法,不是通過java實現(xiàn)的 。

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

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

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

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

        compareAndSwapInt核心就是CAS核心。

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

        (2)AtomicStampedReference

        接下來我們看一下AtomicStampedReference。

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

        這就用到了AtomicStampedReference

        我們來看AtomicStampedReference里的compareAndSet()實現(xiàn):

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

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

        (3)AtomicLongArray

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

        比如,我們看一下compareAndSet()

        (4)AtomicBoolean

        看一段代碼:

        public class AtomicBooleanExample {
         
            private static AtomicBoolean isHappened = new AtomicBoolean(false);
         
            // 請求總數(shù)
            public static int clientTotal = 5000;
            // 同時并發(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,沒有別的線程干擾。

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

        三、原子性---synchronized

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

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

        synchronized修飾的對象有四種:

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

        修飾代碼塊和方法:

        @Slf4j
        public class SynchronizedExample1 {
         
            // 修飾一個代碼塊
            public void test1(int j) {
                synchronized (this) {
                    for (int i = 0; i < 10; i++) {
                        log.info("test1 {} - {}", j, i);
                    }
                }
            }
         
            // 修飾一個方法
            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í)行后可以看到對于情況一,test1內(nèi)部方法塊作用于example1,先執(zhí)行完一次0-9輸出,再執(zhí)行下一次0-9輸出;情況二,同情況一類似,作用于example2;情況三,可以看到交叉執(zhí)行,test1分別獨立作用于example1和example2,互不影響。

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

        @Slf4j
        public class SynchronizedExample2 {
         
            // 修飾一個類
            public static void test1(int j) {
                synchronized (SynchronizedExample2.class{
                    for (int i = 0; i < 10; i++) {
                        log.info("test1 {} - {}", j, i);
                    }
                }
            }
         
            // 修飾一個靜態(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會鎖定調(diào)用它們的對象所屬的類,同一個時間只有一個對象在執(zhí)行。

        四、可見性---volatile

        對于可見性,JVM提供了synchronized和volatile。這里我們看volatile。

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

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

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

        (2)但是volatile不是原子性的,進行++操作不是安全的

        @Slf4j
        public class VolatileExample {
         
            // 請求總數(shù)
            public static int clientTotal = 5000;
         
            // 同時并發(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.error("exception", e);
                        }
                        countDownLatch.countDown();
                    });
                }
                countDownLatch.await();
                executorService.shutdown();
                log.info("count:{}", count);
            }
         
            private static void add() {
                count++;
            }
        }

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

        (3)volatile適用的場景

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

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

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

        線程1負責(zé)初始化,線程2不斷查詢inited值,當(dāng)線程1初始化完成后,線程2就可以檢測到inited為true了。另外,關(guān)注Java知音公眾號,回復(fù)“后端面試”,送你一份面試題寶典!

        五、有序性

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

        可以通過volatile、synchronized、lock保證有序性。

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

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

        happens-before原則:

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

        干貨分享

        最近將個人學(xué)習(xí)筆記整理成冊,使用PDF分享。關(guān)注我,回復(fù)如下代碼,即可獲得百度盤地址,無套路領(lǐng)取!

        ?001:《Java并發(fā)與高并發(fā)解決方案》學(xué)習(xí)筆記;?002:《深入JVM內(nèi)核——原理、診斷與優(yōu)化》學(xué)習(xí)筆記;?003:《Java面試寶典》?004:《Docker開源書》?005:《Kubernetes開源書》?006:《DDD速成(領(lǐng)域驅(qū)動設(shè)計速成)》?007:全部?008:加技術(shù)群討論

        加個關(guān)注不迷路

        喜歡就點個"在看"唄^_^

        瀏覽 46
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        評論
        圖片
        表情
        推薦
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            婷婷涩嫩草鲁丝久久午夜精品 | 九九久久久久久 | 久久99精品久久只有精品 | 国产乱伦黄片 | 国产一级内谢一级一内高请 | AAA级片 | 日本一级少妇免费视频乌克兰裸体 | 中文字幕成人乱码熟女 | 成人交性视频免费看 | 精品一区在线播放 |