<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線程安全(上)

          共 5477字,需瀏覽 11分鐘

           ·

          2022-04-07 11:31

          136a370eb47febab9b3d5e0213c46ba1.webp

          前言

          我們?cè)贏ndroid項(xiàng)目開(kāi)發(fā)中,經(jīng)常會(huì)使用到多線程異步處理很多事務(wù),但是在實(shí)際使用線程開(kāi)發(fā)時(shí)有些線程概念理解很模糊,再加上一些線程操作誤區(qū),導(dǎo)致應(yīng)用運(yùn)行線程混亂,“不科學(xué)的bug”越來(lái)越多,分享本篇文章的目的就是讓大家理解線程安全原理,合理使用線程,并從中受到一些設(shè)計(jì)啟發(fā)。



          01


          線程的基本介紹


          線程調(diào)度

          在講線程安全之前我們需要將線程調(diào)度相關(guān)知識(shí)作為前置條件,計(jì)算機(jī)通常只有一個(gè)CPU,在任意時(shí)刻只能執(zhí)行一條機(jī)器指令,每個(gè)線程只有獲得CPU的使用權(quán)才能執(zhí)行指令。所謂多線程的并發(fā)運(yùn)行,其實(shí)是指從宏觀上看,各個(gè)線程輪流獲得CPU的使用權(quán),分別執(zhí)行各自的任務(wù)。在運(yùn)行池中,會(huì)有多個(gè)處于就緒狀態(tài)的線程在等待CPU,JVM的一項(xiàng)任務(wù)就是負(fù)責(zé)線程的調(diào)度,線程調(diào)度是指按照特定機(jī)制為多個(gè)線程分配CPU的使用權(quán)。

          有兩種調(diào)度模型:分時(shí)調(diào)度模型和搶占式調(diào)度模型。

          分時(shí)調(diào)度模型是指讓所有的線程輪流獲得CPU的使用權(quán),并且平均分配每個(gè)線程占用的CPU的時(shí)間片這個(gè)也比較好理解。

          Java虛擬機(jī)采用搶占式調(diào)度模型,是指優(yōu)先讓可運(yùn)行池中優(yōu)先級(jí)高的線程占用CPU,如果可運(yùn)行池中的線程優(yōu)先級(jí)相同,那么就隨機(jī)選擇一個(gè)線程,使其占用CPU。處于運(yùn)行狀態(tài)的線程會(huì)一直運(yùn)行,直至它不得不放棄CPU。


          JMM

          JMM(Java Memory Model),是一種基于計(jì)算機(jī)內(nèi)存模型(定義了共享內(nèi)存系統(tǒng)中多線程程序讀寫(xiě)操作行為的規(guī)范),屏蔽了各種硬件和操作系統(tǒng)的訪問(wèn)差異的,保證了Java程序在各種平臺(tái)下對(duì)內(nèi)存的訪問(wèn)都能保證效果一致的機(jī)制及規(guī)范。保證共享內(nèi)存的原子性、可見(jiàn)性、有序性。

          fffcedb5a3e1f6397914006112595602.webp



          02


          什么是線程安全?


          線程安全問(wèn)題指的是多個(gè)線程之間對(duì)一個(gè)或多個(gè)共享可變對(duì)象交錯(cuò)操作時(shí),有可能導(dǎo)致數(shù)據(jù)異常。


          競(jìng)態(tài)條件(Race Condition)

          計(jì)算的正確性取決于多個(gè)線程的交替執(zhí)行時(shí)序時(shí),就會(huì)發(fā)生競(jìng)態(tài)條件。舉個(gè)例子,線程A和線程B同時(shí)執(zhí)行單例里的getInstance(),當(dāng)線程A執(zhí)行時(shí)發(fā)現(xiàn)getInstance()返回的是null,會(huì)立即創(chuàng)建單例對(duì)象,同時(shí)線程B執(zhí)行結(jié)果也一樣,也會(huì)創(chuàng)建一個(gè)單例對(duì)象。

          競(jìng)態(tài)不一定導(dǎo)致計(jì)算結(jié)果的不正確,而是不排除計(jì)算結(jié)果有時(shí)正確有時(shí)錯(cuò)誤的可能。和大多數(shù)并發(fā)錯(cuò)誤一樣,競(jìng)態(tài)條件不總是會(huì)產(chǎn)生問(wèn)題,還需要不恰當(dāng)?shù)膱?zhí)行時(shí)序。


          線程原子性

          線程原子性表示的是在共享變量的操作必須是功能聚合不可分的,必須要連續(xù)完成,舉個(gè)例子,線程A里有一個(gè)對(duì)共享變量x++的操作,這個(gè)操作的流程應(yīng)該是將共享內(nèi)存中讀取數(shù)據(jù)后,在線程內(nèi)創(chuàng)建一個(gè)臨時(shí)x變量,然后對(duì)臨時(shí)x變量進(jìn)行x++,最終將輸出結(jié)果同步到共享區(qū)域的x變量?jī)?nèi),這一系列的操作如果在中途有其他線程對(duì)變量a進(jìn)行重新賦值,那么就沒(méi)辦法保證線程A對(duì)x變量操作是正確的,所以我們必須要保證線程操作共享變量必須是具有原子性的,如何保證線程原子性,在后面會(huì)講到。

          0f49955a7ec70d6e1499242831741341.webp

          線程可見(jiàn)性

          線程可見(jiàn)性指的是在多線程環(huán)境下如果某一個(gè)線程對(duì)共享變量的數(shù)據(jù)進(jìn)行更新后,對(duì)于其他的線程訪問(wèn)這個(gè)共享變量是否為更新后的結(jié)果。


          線程有序性

          有序性指的是在程序執(zhí)行的時(shí)候,代碼執(zhí)行的順序和語(yǔ)句的順序是一致的。出現(xiàn)線程無(wú)序是因?yàn)樵贘ava內(nèi)存環(huán)境下,允許編譯器和處理器對(duì)指令進(jìn)行重排序,雖然不會(huì)影響單線程的執(zhí)行順序,但是會(huì)影響到多線程并發(fā)的執(zhí)行正確性。如何避免重排序,會(huì)在下文里提到。



          03


          如何實(shí)現(xiàn)線程安全?


          要實(shí)現(xiàn)線程安全就必須要保證原子性、可見(jiàn)性和有序性。其中包括鎖和原子類型。

          線程鎖

          線程鎖指的是在多線程環(huán)境下,某個(gè)線程要對(duì)共享內(nèi)存里的數(shù)據(jù)進(jìn)行操作時(shí),先將其上鎖,處理完成后,再進(jìn)行解鎖操作。舉個(gè)例子,我們?cè)诂F(xiàn)實(shí)生活中逛超市的時(shí)候需要把隨身物品寄存在寄存箱里,然后把箱子上鎖后開(kāi)始逛超市,買(mǎi)完了東西后,解鎖寄存箱來(lái)取隨身物品,然后這個(gè)寄存箱就可以提供給別人使用了。寄存箱相當(dāng)于共享區(qū)域的對(duì)象,而隨身物品則是對(duì)象的值,寄存的操作由人來(lái)完成,人相當(dāng)于線程。

          09700c57dfd8026f3d06a73899314e46.webp


          鎖的特點(diǎn)

          臨界區(qū)

          我們?cè)诮o超市的寄存箱上鎖后,將隨身物品放入寄存箱然后去逛超市,直到逛完超市后解鎖寄存箱取出隨身物品,這個(gè)上鎖和解鎖的區(qū)間就是臨界區(qū)。代碼里的表現(xiàn)就是,持有鎖的線程獲取鎖后和釋放鎖的執(zhí)行操作,這個(gè)區(qū)間執(zhí)行的代碼叫做臨界區(qū)。

          串行

          多線程開(kāi)發(fā)環(huán)境下,有多個(gè)線程并行執(zhí)行事務(wù),當(dāng)有鎖的介入時(shí),就相當(dāng)于把多個(gè)線程串行執(zhí)行,舉個(gè)例子,你在使用寄存箱的時(shí)候,如果別人要使用,必須得排隊(duì)等你解鎖后才可以使用。


          調(diào)度策略

          鎖的調(diào)度策略分為公平策略和非公平策略,對(duì)應(yīng)的鎖就叫公平鎖和非公平鎖。

          公平鎖

          就是多線程按照申請(qǐng)鎖的順序,未申請(qǐng)到鎖的線程會(huì)在線程等待隊(duì)列里進(jìn)行排隊(duì),只有隊(duì)列首位才能拿到鎖。

          優(yōu)點(diǎn):所有線程都能得到資源,不會(huì)造成線程饑餓

          缺點(diǎn):增加了上下文切換的代價(jià),增加了線程暫停和喚醒的操作,會(huì)造成吞吐量低,等待隊(duì)列里會(huì)長(zhǎng)時(shí)間阻塞大量未獲取到鎖的線程,CPU喚醒線程的開(kāi)銷也會(huì)變大。

          非公平鎖

          在線程去獲取對(duì)象鎖的時(shí)候,會(huì)直接嘗試獲取,如果獲取不到,再進(jìn)入等待隊(duì)列,如果能獲取到,就直接獲取鎖。

          優(yōu)點(diǎn):減少CPU喚醒線程的開(kāi)銷,吞吐量高

          缺點(diǎn):會(huì)導(dǎo)致等待隊(duì)列中的某些檢測(cè)長(zhǎng)時(shí)間獲取不到鎖,造成線程饑餓


          鎖的問(wèn)題

          鎖泄漏

          鎖泄漏指的是代碼的錯(cuò)誤可能導(dǎo)致一個(gè)線程在其執(zhí)行完臨界區(qū)代碼之后未能釋放引導(dǎo)這個(gè)臨界區(qū)的鎖,最終導(dǎo)致其他線程無(wú)法獲取鎖



          04


          內(nèi)部鎖


          synchronized是Java提供的內(nèi)部鎖,里邊有類鎖和對(duì)象鎖;在靜態(tài)方法中,我們一般使用類鎖,在實(shí)例方法中,我們一般使用對(duì)象鎖。使用 synchronized 實(shí)現(xiàn)的線程同步是通過(guò)監(jiān)視器(monitor)來(lái)實(shí)現(xiàn)的,所以內(nèi)部鎖也叫監(jiān)視器鎖。

          內(nèi)部鎖的臨界區(qū)

          同步代碼塊就是內(nèi)部鎖的臨界區(qū),如果某一條線程需要執(zhí)行同步代碼塊的事務(wù),必須先持有此代碼塊也就是臨界區(qū)的鎖。

          不會(huì)造成鎖泄漏

          鎖泄漏上文有提過(guò),內(nèi)部鎖不會(huì)導(dǎo)致鎖泄漏,javac編譯器把同步代碼塊編譯成字節(jié)碼時(shí),對(duì)內(nèi)部鎖的同步代碼塊中的事務(wù)做了特殊處理,即使在代碼執(zhí)行異常,也不會(huì)導(dǎo)致鎖的釋放,所以不會(huì)造成鎖泄漏。

          非公平鎖

          內(nèi)部鎖的策略走的是非公平鎖,也就是有可能會(huì)造成線程饑餓,但是不會(huì)增加線程上下文切換的開(kāi)銷


          內(nèi)部鎖的基本用法:

          Thread threadA = new Thread(new Runnable() {            @Override            public void run() {                lock1();            }        },"my-ThreadB");        threadA.start();
          Thread threadB = new Thread(new Runnable() { @Override public void run() { lock2(); } },"my-ThreadA"); threadB.start();
          private final String lockTest = "test";    private void lock1(){        Log.e("線程測(cè)試","threadA開(kāi)始獲取鎖");        synchronized (lockTest){            Log.e("線程測(cè)試","threadA拿到內(nèi)部鎖,開(kāi)始執(zhí)行臨界區(qū)事務(wù)");            try {                Thread.sleep(2000);            } catch (InterruptedException e) {                e.printStackTrace();            }            Log.e("線程測(cè)試","threadA臨界區(qū)事務(wù)執(zhí)行完成,釋放鎖");        }    }
          private void lock2(){ Log.e("線程測(cè)試","threadB開(kāi)始獲取鎖"); synchronized (lockTest){ Log.e("線程測(cè)試","threadB拿到內(nèi)部鎖,開(kāi)始執(zhí)行臨界區(qū)事務(wù)"); } Log.e("線程測(cè)試","threadB臨界區(qū)事務(wù)執(zhí)行完成,釋放鎖"); }

          程序執(zhí)行結(jié)果:

          ef6f17b6e17f9ef775e348fd89fff965.webp



          05


          顯式鎖


          想做到線程同步,方案不止內(nèi)部鎖一種,而當(dāng)內(nèi)部鎖不滿足某些特定場(chǎng)景需求的時(shí)候,則可以選擇使用顯式鎖使用更靈活的功能。

          我們正常使用顯式鎖的操作是用Lock接口來(lái)實(shí)現(xiàn),Lock接口對(duì)顯式鎖進(jìn)行了抽象,ReentrantLock則是Lock接口的實(shí)現(xiàn)類。

          ReentrantLock

          296f4fc8f52f4af584f1bd95fde93074.webp

          我們可以根據(jù)ReentrantLock的源碼重載方法分析到,其實(shí)顯式鎖是支持公平/非公平鎖,因?yàn)楣芥i的上下文開(kāi)銷比較大,所以默認(rèn)不做配置則是非公平策略

          8d522b7a27119f691e8b3cf80fc64123.webp

          顯式鎖的臨界區(qū)

          Lock接口提供了lock()與unlock()方法,代表的是鎖定和解鎖的操作,這兩個(gè)方法間的代碼塊就是顯式鎖的臨界區(qū)

          鎖泄漏

          顯式鎖和內(nèi)部鎖不一樣,如果操作不當(dāng)會(huì)造成鎖泄漏,所以必須要手動(dòng)釋放鎖

          顯式鎖的基本用法

          private final String lockTest = "test";
          private void lock1(){ Log.e("線程測(cè)試","threadA開(kāi)始獲取鎖"); synchronized (lockTest){ Log.e("線程測(cè)試","threadA拿到內(nèi)部鎖,開(kāi)始執(zhí)行臨界區(qū)事務(wù)"); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } Log.e("線程測(cè)試","threadA臨界區(qū)事務(wù)執(zhí)行完成,釋放鎖");????}}
          private void lock2(){ Log.e("線程測(cè)試","threadB開(kāi)始獲取鎖"); synchronized (lockTest){ Log.e("線程測(cè)試","threadB拿到內(nèi)部鎖,開(kāi)始執(zhí)行臨界區(qū)事務(wù)"); } Log.e("線程測(cè)試","threadB臨界區(qū)事務(wù)執(zhí)行完成,釋放鎖");}

          顯式鎖執(zhí)行結(jié)果:

          d66ccd066776c63393e60aa64b84a916.webp

          顯式鎖獲取鎖的方法

          lock()

          獲取顯式鎖,如果獲取成功則執(zhí)行臨界區(qū)的代碼,獲取失敗則線程進(jìn)入阻塞狀態(tài)。

          tryLock()

          獲取顯式鎖,獲取成功后返回true,失敗返回false,失敗之后不會(huì)讓線程進(jìn)入阻塞狀態(tài)。

          基本使用方式如下:

          //實(shí)例化Lock接口對(duì)象private final Lock lock = new ReentrantLock();//根據(jù)嘗試獲取鎖的值來(lái)判斷具體執(zhí)行的代碼if(lock.tryLock()) {     try{         //處理任務(wù)     }catch(Exception ex){     }finally{       //當(dāng)獲取鎖成功時(shí)最后一定要記住finally去關(guān)閉鎖         lock.unlock();   //釋放鎖     } }else {  //else時(shí)為未獲取鎖,則無(wú)需去關(guān)閉鎖    //如果不能獲取鎖,則直接做其他事情}

          tryLock(long time, TimeUnit unit)

          是tryLock()的重載方法,功能一致,只不過(guò)參數(shù)可控在指定時(shí)間內(nèi)沒(méi)有獲取到鎖,才返回false。

          lockInterruptibly()

          與lock()方法區(qū)別在與lock方法是不可以通過(guò)Thread.interrupt來(lái)中斷線程的,而lockInterruptibly()方法其他線程可以通過(guò)Thread.interrupt中斷線程并且立即返回,簡(jiǎn)單來(lái)說(shuō)該方法被調(diào)用后一直阻塞到獲得鎖 但是接受中斷信號(hào),而lock()不接受中斷信號(hào)。



          06


          內(nèi)部鎖和顯式鎖的區(qū)別


          靈活性

          內(nèi)部鎖是基于代碼的鎖,鎖的獲取和釋放都是在方法塊里被動(dòng)執(zhí)行,缺乏靈活性。

          顯式鎖是基于對(duì)象的鎖,鎖的獲取和釋放可以由開(kāi)發(fā)者自定義控制,會(huì)更加靈活。

          鎖的調(diào)度策略

          內(nèi)部鎖僅支持非公平鎖

          顯式鎖支持公平鎖和非公平鎖,開(kāi)發(fā)者可以根據(jù)使用場(chǎng)景來(lái)控制

          便利性

          內(nèi)部鎖簡(jiǎn)單易用,鎖泄漏的處理系統(tǒng)已經(jīng)幫你處理好了,不需要額外投入精力去做釋放操作。

          顯式鎖需要手動(dòng)獲取和釋放鎖,在某種未考慮到的特定場(chǎng)景下,就有可能會(huì)造成鎖泄漏。

          阻塞

          內(nèi)部鎖在獲取鎖的時(shí)候,如果獲取不到,則讓線程進(jìn)入等待隊(duì)列

          顯式鎖接口提供了tryLock()的方法,如果獲取不到鎖,則直接返回false,不會(huì)導(dǎo)致線程阻塞。

          適用場(chǎng)景

          在多線程環(huán)境下如果臨界區(qū)的事務(wù)耗時(shí)短則考慮使用內(nèi)部鎖

          在多線程環(huán)境下如果臨界區(qū)的事務(wù)耗時(shí)長(zhǎng)則考慮使用顯式鎖



          總結(jié)

          因篇幅原因本文主要是介紹了一部分線程的運(yùn)行環(huán)境、線程安全性的基礎(chǔ)理論以及鎖的相關(guān)知識(shí),請(qǐng)大家及時(shí)關(guān)注《Java線程安全(下)》,下文會(huì)將到讀寫(xiě)鎖、volatile、原子類型、線程活躍性、死鎖、鎖死、線程間的安全協(xié)作等。


          感謝您的閱讀,祝您工作順利81cba823e8ba9e0beb75348cd0c5e712.webp










          瀏覽 59
          點(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>
                  影音先锋男人资源站亚洲AV | 日本在线欧美 | 成人性爱片 | 成人性爱视频电影 | 香蕉大伊人 |