<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>

          Java 線程安全問(wèn)題的本質(zhì)

          共 4065字,需瀏覽 9分鐘

           ·

          2020-12-18 15:24

          點(diǎn)擊上方藍(lán)色字體,選擇“標(biāo)星公眾號(hào)”

          優(yōu)質(zhì)文章,第一時(shí)間送達(dá)

          ? 作者?|??Arnold-zhao

          來(lái)源 |? urlify.cn/ryQFJn

          66套java從入門(mén)到精通實(shí)戰(zhàn)課程分享

          出現(xiàn)線程安全的問(wèn)題本質(zhì)是因?yàn)椋?/span>

          主內(nèi)存和工作內(nèi)存數(shù)據(jù)不一致性以及編譯器重排序?qū)е隆?/span>

          所以理解上述兩個(gè)問(wèn)題的核心,對(duì)認(rèn)知多線程的問(wèn)題則具有很高的意義;

          簡(jiǎn)單理解CPU

          CPU除了控制器、運(yùn)算器等器件還有一個(gè)重要的部件就是寄存器。其中寄存器的作用就是進(jìn)行數(shù)據(jù)的臨時(shí)存儲(chǔ)。

          CPU的運(yùn)算速度是非??斓模瑸榱诵阅蹸PU在內(nèi)部開(kāi)辟一小塊臨時(shí)存儲(chǔ)區(qū)域,并在進(jìn)行運(yùn)算時(shí)先將數(shù)據(jù)從內(nèi)存復(fù)制到這一小塊臨時(shí)存儲(chǔ)區(qū)域中,運(yùn)算時(shí)就在這一小快臨時(shí)存儲(chǔ)區(qū)域內(nèi)進(jìn)行。我們稱這一小塊臨時(shí)存儲(chǔ)區(qū)域?yàn)榧拇嫫鳌?/span>

          CPU讀取指令是往內(nèi)存里面去讀取的,讀一條指令放到CPU中,CPU去執(zhí)行,對(duì)內(nèi)存的讀取速度比較慢,所以從內(nèi)存讀取的速度去決定了這個(gè)CPU的執(zhí)行速度的。所以無(wú)論我們的CPU怎么去升級(jí),但是如果這方面速度沒(méi)有解決的話,其的性能也不會(huì)得到多大的提升。

          為了彌補(bǔ)這個(gè)缺陷,所以添加了高速緩存的機(jī)制,如ARM A11的處理器,它的1級(jí)緩存中的容量是64KB,2級(jí)緩存中的容量是8M,
          通過(guò)增加cpu高速緩存的機(jī)制,以此彌補(bǔ)服務(wù)器內(nèi)存讀寫(xiě)速度的效率問(wèn)題;

          JVM虛擬機(jī)類比于操作系統(tǒng)

          JVM虛擬計(jì)算機(jī)平臺(tái)就類似于一個(gè)操作系統(tǒng)的角色,所以在具體實(shí)現(xiàn)上JVM虛擬機(jī)也的確是借鑒了很多操作系統(tǒng)的特點(diǎn);

          JAVA中線程的工作空間(working memory)就是CPU的寄存器和高速緩存的抽象描述,cpu在計(jì)算的時(shí)候,并不總是從內(nèi)存讀取數(shù)據(jù),它的數(shù)據(jù)讀取順序優(yōu)先級(jí) 是:寄存器-高速緩存-內(nèi)存;
          而在JAVA的內(nèi)存模型中也是同等的,Java內(nèi)存模型中規(guī)定了所有的變量都存儲(chǔ)在主內(nèi)存中,每條線程還有自己的工作內(nèi)存(類似于CPU的高速緩存),線程的工作內(nèi)存中保存了該線程使用到的變量到主內(nèi)存副本拷貝,線程對(duì)變量的所有操作(讀取、賦值)都必須在工作內(nèi)存中進(jìn)行,而不能直接讀寫(xiě)主內(nèi)存中的變量,操作完成后再將變量寫(xiě)回主內(nèi)存。不同線程之間無(wú)法直接訪問(wèn)對(duì)方工作內(nèi)存中的變量,線程間變量值的傳遞均需要在主內(nèi)存來(lái)完成?;娟P(guān)系如下圖:

          注意:這里的Java內(nèi)存模型,主內(nèi)存、工作內(nèi)存與Java內(nèi)存區(qū)域模型的Java堆、棧、方法區(qū)不是同一層次內(nèi)存劃分,這兩者基本上沒(méi)有關(guān)系。

          重排序

          在執(zhí)行程序時(shí),為了提高性能,編譯器和處理器常常會(huì)對(duì)指令進(jìn)行重排序。一般重排序可以分為如下三種:

          舉例如下:

          public?class?Singleton?{
          ????public?static??Singleton?singleton;

          ????/**
          ?????*?構(gòu)造函數(shù)私有,禁止外部實(shí)例化
          ?????*/
          ????private?Singleton()?{};

          ????public?static?Singleton?getInstance()?{
          ????????if?(singleton?==?null)?{
          ????????????singleton?=?new?Singleton();
          ????????}
          ????????return?singleton;
          ????}
          }

          如上,一個(gè)簡(jiǎn)單的單例模式,按照對(duì)象的構(gòu)造過(guò)程,實(shí)例化一個(gè)對(duì)象1、可以分為三個(gè)步驟(指令):
          1、 分配內(nèi)存空間。
          2、 初始化對(duì)象。
          3、 將內(nèi)存空間的地址賦值給對(duì)應(yīng)的引用。
          但是由于操作系統(tǒng)可以對(duì)指令進(jìn)行重排序,所以上面的過(guò)程也可能變?yōu)槿缦碌倪^(guò)程:
          1、 分配內(nèi)存空間。
          2、 將內(nèi)存空間的地址賦值給對(duì)應(yīng)的引用。
          3、 初始化對(duì)象 。

          所以,如果出現(xiàn)并發(fā)訪問(wèn)getInstance()方法時(shí),則可能會(huì)出現(xiàn),線程二判斷singleton是否為空,此時(shí)由于當(dāng)前該singleton已經(jīng)分配了內(nèi)存地址,但其實(shí)并沒(méi)有初始化對(duì)象,則會(huì)導(dǎo)致return 一個(gè)未初始化的對(duì)象引用暴露出來(lái),以此可能會(huì)出現(xiàn)一些不可預(yù)料的代碼異常;

          當(dāng)然,指令重排序的問(wèn)題并非每次都會(huì)進(jìn)行,在某些特殊的場(chǎng)景下,編譯器和處理器是不會(huì)進(jìn)行重排序的,但上述的舉例場(chǎng)景則是大概率會(huì)出現(xiàn)指令重排序問(wèn)題(關(guān)于指令重排序的概念后續(xù)給出詳細(xì)的地址)

          原創(chuàng)聲明:作者:Arnold.zhao 博客園地址:https://www.cnblogs.com/zh94

          匯總

          所以,如上可知,多線程在執(zhí)行過(guò)程中,數(shù)據(jù)的不可見(jiàn)性,原子性,以及重排序所引起的指令有序性 三個(gè)問(wèn)題基本是多線程并發(fā)問(wèn)題的三個(gè)重要特性,也就是我們常說(shuō)的:

          并發(fā)的三大特性:原子性,有序性,可見(jiàn)性;

          原子性:代碼操作是否是原子操作(如:i++ 看似一個(gè)代碼片段,實(shí)際的執(zhí)行中將會(huì)分為三步執(zhí)行,則必然是非原子化的操作,在多線程的場(chǎng)景中則會(huì)出現(xiàn)異常)
          有序性:CPU執(zhí)行代碼指令時(shí)的有序性;
          可見(jiàn)性:由于工作線程的內(nèi)存與主內(nèi)存的數(shù)據(jù)不同步,而導(dǎo)致的數(shù)據(jù)可見(jiàn)性問(wèn)題;

          一些解釋

          但是,問(wèn)題就真的有那么復(fù)雜嗎?如果按照上面所說(shuō)的問(wèn)題,i++是非原子操作,就會(huì)出現(xiàn)并發(fā)異常的問(wèn)題,new Object() 就會(huì)出現(xiàn)重排序的并發(fā)問(wèn)題,那么Java開(kāi)發(fā)還能做嗎。。我隨便寫(xiě)個(gè)方法代碼,豈不是就會(huì)出現(xiàn)并發(fā)問(wèn)題?但是為什么我開(kāi)發(fā)了這么久的代碼,也沒(méi)有出現(xiàn)過(guò)方法并發(fā)導(dǎo)致的異常問(wèn)題啊?
          燒的麻袋;
          這里就要說(shuō)明另外一個(gè)問(wèn)題,JVM的線程棧,JVM線程棧中是線程獨(dú)有的內(nèi)存空間(如:程序計(jì)數(shù)器以線程棧幀)而線程棧幀中的局部變量表則用來(lái)存儲(chǔ)當(dāng)前所執(zhí)行方法的基本數(shù)據(jù)類型(包含 reference, returnAddress等),所以當(dāng)方法在被線程執(zhí)行的過(guò)程中,相關(guān)的對(duì)象引用信息,以及基本類型的數(shù)據(jù)都是線程獨(dú)有的,并不會(huì)出現(xiàn)多個(gè)線程訪問(wèn)時(shí)的并發(fā)問(wèn)題,也就是簡(jiǎn)單來(lái)說(shuō):一個(gè)方法內(nèi)的變量定義以及方法內(nèi)的業(yè)務(wù)代碼,是不會(huì)出現(xiàn)并發(fā)問(wèn)題的。多個(gè)線程并不會(huì)共享一個(gè)方法內(nèi)的變量數(shù)據(jù),而是每個(gè)方法內(nèi)的定義都屬于當(dāng)前該執(zhí)行線程的獨(dú)有棧空間中。(所以通過(guò)Java線程棧的這一獨(dú)特特性自然當(dāng)中則為我們省了很多事項(xiàng);)

          但是由于我們的線程的數(shù)據(jù)操作不可能每次都去訪問(wèn)主存中的數(shù)據(jù),對(duì)于線程所使用到的變量需要copy至線程內(nèi)存中以增加我們的執(zhí)行速度,所以就引出了我們上述所提到的并發(fā)問(wèn)題的本質(zhì)問(wèn)題,線程工作空間和主內(nèi)存的數(shù)據(jù)不同步而導(dǎo)致的數(shù)據(jù)共享時(shí)的可見(jiàn)性問(wèn)題;

          如:此時(shí)定義一個(gè)簡(jiǎn)單的類

          class?Person{
          ????int?a?=?1;
          ????int?b?=?2;

          ????public?void?change()?{
          ????????a?=?3;
          ????????b?=?a;
          ????}

          ????public?void?print()?{
          ????????String?result?=?"b="?+?b?+?";a="?+?a;
          ????????System.out.println(result);
          ????}
          ????
          ????public?static?void?main(String[]?args)?{
          ????????while?(true)?{
          ????????????final?Person?test?=?new?Person();
          ????????????new?Thread(()?->?{
          ????????????????Thread.sleep(10);
          ????????????????test.change();
          ????????????}).start();

          ????????????new?Thread(()?->?{
          ????????????????Thread.sleep(10);
          ????????????????test.print();
          ????????????}).start();
          ????????}
          ????}
          }

          如上,假設(shè)此時(shí)多個(gè)線程同時(shí)訪問(wèn)change()以及print() 方法,則可能會(huì)出現(xiàn)print所輸出的結(jié)果是:b=2;a=1或者b=3;a=3;這兩種都是正常現(xiàn)象,但還有可能是會(huì)輸出結(jié)果是:b=2;a=3以及b=3;a=1;

          Person類所定義的變量a和b,按照J(rèn)VM內(nèi)存區(qū)域劃分,在對(duì)象實(shí)例化后則都是存儲(chǔ)到數(shù)據(jù)堆中;
          按照我們上述關(guān)于線程工作內(nèi)存的解釋來(lái)看,此時(shí)線程在執(zhí)行change()方法和print()方法時(shí),由于兩個(gè)方法都有關(guān)于外部變量的引用,所以需要copy主內(nèi)存中的這兩個(gè)變量副本到對(duì)應(yīng)的線程工作內(nèi)存中進(jìn)行操作,執(zhí)行完以后再同步至主內(nèi)存中。

          此時(shí)在A線程執(zhí)行完change()方法后,a=3,b=3;但此時(shí)a=3在執(zhí)行完成后還沒(méi)有同步到主內(nèi)存,但b=3此時(shí)已經(jīng)提供至主內(nèi)存了,那么此時(shí)B線程執(zhí)行print()數(shù)據(jù)輸出后,則得到的是結(jié)果是:b=3;a=1;同理也可以得到b=2;a=3的可能性結(jié)果;所以此處則由于線程共享變量的可見(jiàn)性問(wèn)題,而導(dǎo)致了上述的問(wèn)題;

          正是由于存在上述所提到的線程并發(fā)所可能引起的種種問(wèn)題,所以JDK則也有了后續(xù)的一系列多線程玩法:ThreadLocal,CountDownLatch,ReentrantLock,Unsafe,synchronized,volatile,Executor,Future 這些供開(kāi)發(fā)者在開(kāi)發(fā)程序時(shí)用來(lái)對(duì)多線程保駕護(hù)航的助手類,以及JDK已經(jīng)自身開(kāi)發(fā)好的支持線程安全的一些工具類,StringBuffer,CopyOnWriteArrayList, ConcurrentHashMap,AtomicInteger等,供開(kāi)發(fā)者開(kāi)箱即用;后續(xù)針對(duì)這些JDK自身所提供的一些類的玩法會(huì)做進(jìn)一步說(shuō)明,順便系統(tǒng)整理下腦中的信息,形成有效的知識(shí)結(jié)構(gòu);End;





          粉絲福利:Java從入門(mén)到入土學(xué)習(xí)路線圖

          ???

          ?長(zhǎng)按上方微信二維碼?2 秒


          感謝點(diǎn)贊支持下哈?

          瀏覽 123
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

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

          手機(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>
                  亚洲伊人久久久 | 青娱乐爱爱视频 | 性欧美成人播放77777 | 日韩高清无码专区 | 狠狠操成人免费黄色视频 |