<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          線程安全問題的 3 種解決方案!

          共 1324字,需瀏覽 3分鐘

           ·

          2022-04-14 08:48

          作者 | 磊哥

          來源 | Java面試真題解析(ID:aimianshi666)

          轉(zhuǎn)載請聯(lián)系授權(quán)(微信ID:GG_Stone)

          線程安全是指某個(gè)方法或某段代碼,在多線程中能夠正確的執(zhí)行,不會出現(xiàn)數(shù)據(jù)不一致或數(shù)據(jù)污染的情況,我們把這樣的程序稱之為線程安全的,反之則為非線程安全的。在 Java 中,解決線程安全問題有以下 3 種手段:

          1. 使用線程安全類,比如 AtomicInteger。
          2. 加鎖排隊(duì)執(zhí)行
            1. 使用 synchronized 加鎖。
            2. 使用 ReentrantLock 加鎖。
          3. 使用線程本地變量 ThreadLocal。

          接下來我們逐個(gè)來看它們的實(shí)現(xiàn)。

          線程安全問題演示

          我們創(chuàng)建一個(gè)變量 number 等于 0,之后創(chuàng)建線程 1,執(zhí)行 100 萬次 ++ 操作,同時(shí)再創(chuàng)建線程 2 執(zhí)行 100 萬次 -- 操作,等線程 1 和線程 2 都執(zhí)行完之后,打印 number 變量的值,如果打印的結(jié)果為 0,則說明是線程安全的,否則則為非線程安全的,示例代碼如下:

          public?class?ThreadSafeTest?{
          ????//?全局變量
          ????private?static?int?number?=?0;
          ????//?循環(huán)次數(shù)(100W)
          ????private?static?final?int?COUNT?=?1_000_000;

          ????public?static?void?main(String[]?args)?throws?InterruptedException?{
          ????????//?線程1:執(zhí)行 100W 次?++?操作
          ????????Thread?t1?=?new?Thread(()?->?{
          ????????????for?(int?i?=?0;?i?????????????????number++;
          ????????????}
          ????????});
          ????????t1.start();

          ????????//?線程2:執(zhí)行 100W 次?--?操作
          ????????Thread?t2?=?new?Thread(()?->?{
          ????????????for?(int?i?=?0;?i?????????????????number--;
          ????????????}
          ????????});
          ????????t2.start();

          ????????//?等待線程?1?和線程?2,執(zhí)行完,打印?number?最終的結(jié)果
          ????????t1.join();
          ????????t2.join();
          ????????System.out.println("number 最終結(jié)果:"?+?number);
          ????}
          }

          以上程序的執(zhí)行結(jié)果如下圖所示:從上述執(zhí)行結(jié)果可以看出,number 變量最終的結(jié)果并不是 0,和預(yù)期的正確結(jié)果不相符,這就是多線程中的線程安全問題。

          解決線程安全問題

          1.原子類AtomicInteger

          AtomicInteger 是線程安全的類,使用它可以將 ++ 操作和 -- 操作,變成一個(gè)原子性操作,這樣就能解決非線程安全的問題了,如下代碼所示:

          import?java.util.concurrent.atomic.AtomicInteger;

          public?class?AtomicIntegerExample?{
          ????//?創(chuàng)建?AtomicInteger
          ????private?static?AtomicInteger?number?=?new?AtomicInteger(0);
          ????//?循環(huán)次數(shù)
          ????private?static?final?int?COUNT?=?1_000_000;

          ????public?static?void?main(String[]?args)?throws?InterruptedException?{
          ????????//?線程1:執(zhí)行 100W 次?++?操作
          ????????Thread?t1?=?new?Thread(()?->?{
          ????????????for?(int?i?=?0;?i?????????????????//?++?操作
          ????????????????number.incrementAndGet();
          ????????????}
          ????????});
          ????????t1.start();

          ????????//?線程2:執(zhí)行 100W 次?--?操作
          ????????Thread?t2?=?new?Thread(()?->?{
          ????????????for?(int?i?=?0;?i?????????????????//?--?操作
          ????????????????number.decrementAndGet();
          ????????????}
          ????????});
          ????????t2.start();

          ????????//?等待線程?1?和線程?2,執(zhí)行完,打印?number?最終的結(jié)果
          ????????t1.join();
          ????????t2.join();
          ????????System.out.println("最終結(jié)果:"?+?number.get());
          ????}
          }

          以上程序的執(zhí)行結(jié)果如下圖所示:

          2.加鎖排隊(duì)執(zhí)行

          Java 中有兩種鎖:synchronized 同步鎖和 ReentrantLock 可重入鎖。

          2.1 同步鎖synchronized

          synchronized 是 JVM 層面實(shí)現(xiàn)的自動(dòng)加鎖和自動(dòng)釋放鎖的同步鎖,它的實(shí)現(xiàn)代碼如下:

          public?class?SynchronizedExample?{
          ????//?全局變量
          ????private?static?int?number?=?0;
          ????//?循環(huán)次數(shù)(100W)
          ????private?static?final?int?COUNT?=?1_000_000;

          ????public?static?void?main(String[]?args)?throws?InterruptedException?{
          ????????//?線程1:執(zhí)行 100W 次?++?操作
          ????????Thread?t1?=?new?Thread(()?->?{
          ????????????for?(int?i?=?0;?i?????????????????//?加鎖排隊(duì)執(zhí)行
          ????????????????synchronized?(SynchronizedExample.class)?{
          ????????????????????number++;
          ????????????????}
          ????????????}
          ????????});
          ????????t1.start();

          ????????//?線程2:執(zhí)行 100W 次?--?操作
          ????????Thread?t2?=?new?Thread(()?->?{
          ????????????for?(int?i?=?0;?i?????????????????//?加鎖排隊(duì)執(zhí)行
          ????????????????synchronized?(SynchronizedExample.class)?{
          ????????????????????number--;
          ????????????????}
          ????????????}
          ????????});
          ????????t2.start();

          ????????//?等待線程?1?和線程?2,執(zhí)行完,打印?number?最終的結(jié)果
          ????????t1.join();
          ????????t2.join();
          ????????System.out.println("number 最終結(jié)果:"?+?number);
          ????}
          }

          以上程序的執(zhí)行結(jié)果如下圖所示:

          2.2 可重入鎖ReentrantLock

          ReentrantLock 可重入鎖需要程序員自己加鎖和釋放鎖,它的實(shí)現(xiàn)代碼如下:

          import?java.util.concurrent.locks.ReentrantLock;

          /**
          ?*?使用?ReentrantLock?解決非線程安全問題
          ?*/

          public?class?ReentrantLockExample?{
          ????//?全局變量
          ????private?static?int?number?=?0;
          ????//?循環(huán)次數(shù)(100W)
          ????private?static?final?int?COUNT?=?1_000_000;
          ????//?創(chuàng)建?ReentrantLock
          ????private?static?ReentrantLock?lock?=?new?ReentrantLock();

          ????public?static?void?main(String[]?args)?throws?InterruptedException?{
          ????????//?線程1:執(zhí)行 100W 次?++?操作
          ????????Thread?t1?=?new?Thread(()?->?{
          ????????????for?(int?i?=?0;?i?????????????????lock.lock();????//?手動(dòng)加鎖
          ????????????????number++;???????//?++?操作
          ????????????????lock.unlock();??//?手動(dòng)釋放鎖
          ????????????}
          ????????});
          ????????t1.start();

          ????????//?線程2:執(zhí)行 100W 次?--?操作
          ????????Thread?t2?=?new?Thread(()?->?{
          ????????????for?(int?i?=?0;?i?????????????????lock.lock();????//?手動(dòng)加鎖
          ????????????????number--;???????//?--?操作
          ????????????????lock.unlock();??//?手動(dòng)釋放鎖
          ????????????}
          ????????});
          ????????t2.start();

          ????????//?等待線程?1?和線程?2,執(zhí)行完,打印?number?最終的結(jié)果
          ????????t1.join();
          ????????t2.join();
          ????????System.out.println("number 最終結(jié)果:"?+?number);
          ????}
          }

          以上程序的執(zhí)行結(jié)果如下圖所示:

          3.線程本地變量ThreadLocal

          使用 ThreadLocal 線程本地變量也可以解決線程安全問題,它是給每個(gè)線程獨(dú)自創(chuàng)建了一份屬于自己的私有變量,不同的線程操作的是不同的變量,所以也不會存在非線程安全的問題,它的實(shí)現(xiàn)代碼如下:

          public?class?ThreadSafeExample?{
          ????//?創(chuàng)建?ThreadLocal(設(shè)置每個(gè)線程中的初始值為?0)
          ????private?static?ThreadLocal?threadLocal?=?ThreadLocal.withInitial(()?->?0);
          ????//?全局變量
          ????private?static?int?number?=?0;
          ????//?循環(huán)次數(shù)(100W)
          ????private?static?final?int?COUNT?=?1_000_000;

          ????public?static?void?main(String[]?args)?throws?InterruptedException?{
          ????????//?線程1:執(zhí)行 100W 次?++?操作
          ????????Thread?t1?=?new?Thread(()?->?{
          ????????????try?{
          ????????????????for?(int?i?=?0;?i?????????????????????//?++?操作
          ????????????????????threadLocal.set(threadLocal.get()?+?1);
          ????????????????}
          ????????????????//?將?ThreadLocal?中的值進(jìn)行累加
          ????????????????number?+=?threadLocal.get();
          ????????????}?finally?{
          ????????????????threadLocal.remove();?//?清除資源,防止內(nèi)存溢出
          ????????????}
          ????????});
          ????????t1.start();

          ????????//?線程2:執(zhí)行 100W 次?--?操作
          ????????Thread?t2?=?new?Thread(()?->?{
          ????????????try?{
          ????????????????for?(int?i?=?0;?i?????????????????????//?--?操作
          ????????????????????threadLocal.set(threadLocal.get()?-?1);
          ????????????????}
          ????????????????//?將?ThreadLocal?中的值進(jìn)行累加
          ????????????????number?+=?threadLocal.get();
          ????????????}?finally?{
          ????????????????threadLocal.remove();?//?清除資源,防止內(nèi)存溢出
          ????????????}
          ????????});
          ????????t2.start();

          ????????//?等待線程?1?和線程?2,執(zhí)行完,打印?number?最終的結(jié)果
          ????????t1.join();
          ????????t2.join();
          ????????System.out.println("最終結(jié)果:"?+?number);
          ????}
          }

          以上程序的執(zhí)行結(jié)果如下圖所示:

          總結(jié)

          在 Java 中,解決線程安全問題的手段有 3 種:

          1.使用線程安全的類,如 AtomicInteger 類;

          2.使用鎖 synchronized 或 ReentrantLock 加鎖排隊(duì)執(zhí)行;

          3.使用線程本地變量 ThreadLocal 來處理。

          是非審之于己,毀譽(yù)聽之于人,得失安之于數(shù)。

          公眾號:Java面試真題解析

          面試合集:https://gitee.com/mydb/interview


          往期推薦

          面試突擊36:線程安全問題是如何產(chǎn)生的?


          每周匯總 | Java面試題(共35篇)2022版


          面試突擊35:如何判斷線程池已經(jīng)執(zhí)行完所有任務(wù)了?


          瀏覽 20
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  A片在线观看免费观看 | 逼视频免费 | 99这里有精品 | 葵司视频在线观看 | 操逼视频素材大全网站直接看 |