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

        共 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)
        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>
            黄色一级操逼 | 插插色欲综合网 | 大子在卫生间做爰电影 | 精品乱码一区二区三四区视频 | 操逼视频www | 欧美一区二区三 | 免费 成 人 黄 色 | 逼逼操逼 | 各种姿势一级A片免费看 | 午夜丰满寂寞少妇精品 |