0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

線程安全怎么辦

科技綠洲 ? 來源:Java技術(shù)指北 ? 作者:Java技術(shù)指北 ? 2023-10-10 15:00 ? 次閱讀

線程安全一直是多線程開發(fā)中需要注意的地方,可以說,并發(fā)安全保證了所有的數(shù)據(jù)都安全。

1 線程不安全示例

線程安全其實(shí)是多線程編程里面的一個(gè)核心點(diǎn),所有的設(shè)計(jì)和代碼都是為了實(shí)現(xiàn)線程的高效與安全。

多線程中有幾個(gè)比較核心概念,即原子性,可見性,順序性。那么線程安全也會(huì)圍繞著這三個(gè)核心來展開嘍。

下面我們看一兩個(gè)簡單的問題多線程。

簡單買票的線程安全問題

public class ThreadSafeDemo3 {
    public static void main(String[] args) throws InterruptedException {
        TicketStation station = new TicketStation();
        new Thread(station,"軟軟").start();
        new Thread(station,"冰冰").start();
        new Thread(station,"指北君").start();
    }
}
class TicketStation implements Runnable{
    int ticketCount = 10;
    boolean hasTicket = true;
    @Override
    public void run() {
        while(hasTicket){buyTicket();}
    }
    private void buyTicket(){
        if (ticketCount < 1) {
            hasTicket = false;
            return;
        }
        try {
            Thread.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + " get the ticket"+ ticketCount--);
    }
}

運(yùn)行幾遍就有可能會(huì)出現(xiàn)下面的錯(cuò)誤不預(yù)期的結(jié)果。一個(gè)線程賣完了票,但是另外兩個(gè)線程都還不知道。

圖片
image-20210926221216385

多線程操作非線程安全對(duì)象問題

public class ThreadSafeDemo2 {
    public static void main(String[] args) throws InterruptedException {
        List< String > list = new ArrayList<  >();
        for (int i =0 ;i< 20 ;i++) {
            new Thread(() - > {
                for (int j = 0; j < 5; j++) {
                    list.add(Thread.currentThread().getName() + j);
                }
            }, "thread" + i).start();
        }
        Thread.sleep(1000*3);
        System.out.println(list.size());
    }
}

以上代碼多執(zhí)行幾次之后會(huì),所得list的size不會(huì)等于100。問題就在于多線程操作同一個(gè)線程不安全的List的時(shí)候,會(huì)是結(jié)果與預(yù)期不符,出現(xiàn)線程安全問題。

以上是兩個(gè)線程不安全的示例,而對(duì)于線程安全,應(yīng)該要做到如下:

當(dāng)多個(gè)線程訪問某個(gè)方法的時(shí)候,不管你通過怎樣的調(diào)用方法,或者說這些線程如何交替執(zhí)行,我們?cè)谥鞒绦蛑胁恍枰プ鋈魏蔚耐剑@個(gè)類的行為都是我們?cè)O(shè)想的正確行為,那么我們可以說這個(gè)類是線程安全的。即可以保證原子性,可見性,順序性。

2 并發(fā)安全的問題根源

線程不安全指的是多線程并發(fā)執(zhí)行某個(gè)代碼時(shí),產(chǎn)生了邏輯上的錯(cuò)誤,結(jié)果和預(yù)期值不相同。

其原因可以總結(jié)如下:

  • Java線程是搶占執(zhí)行的。
  • 有些操作不是原子的,cpu在處理某一個(gè)線程的時(shí)候,有可能被其他線程搶去做工。
  • 內(nèi)存共享可變。
  • 指令重排序:Java編譯器在編譯代碼時(shí),會(huì)對(duì)最終執(zhí)行的指令重排序,它會(huì)保證原有邏輯不變的情況下,提高程序的運(yùn)行效率。

3 線程安全不是絕對(duì)的

《深入理解JVM》中有講到如果要保證絕對(duì)線程安全,在大多數(shù)的應(yīng)用場景下是難以做到的,或者說很難做到,即使做到,也會(huì)付出很大的代價(jià)。而且在Java中標(biāo)注的某些線程安全的類也不是絕對(duì)的線程安全,也需要在調(diào)用時(shí)使用一些額外操作。

我們大多時(shí)候都是盡量保證線程的相對(duì)安全,對(duì)一個(gè)對(duì)象單獨(dú)操作的時(shí)候保證線程安全,而對(duì)于一些特殊的調(diào)用情況,我們則需要采取一些同步操作付諸。

4 線程安全的實(shí)現(xiàn)方法

我們?cè)趯懘a的時(shí)候,保證線程安全的方法有多種,下面我們介紹幾種方式。

4.1 互斥同步

互斥的特點(diǎn)是在同一時(shí)刻只有一個(gè)線程獲得執(zhí)行權(quán)利,其余線程則會(huì)等待。( 同一時(shí)刻,只有一個(gè)線程在操作共享數(shù)據(jù) ) 互斥是實(shí)現(xiàn)同步的一種手段, 臨界區(qū)、互斥量、信號(hào)量都是主要的互斥實(shí)現(xiàn)方式。即通過實(shí)現(xiàn)互斥來最終完成同步的目的。

4.1.1 Synchronized

synchronized是同步鎖,主要用來控制線程同步,保證某個(gè)鎖住的內(nèi)容不被多個(gè)線程同步執(zhí)行。上述買票的例子中,在buyTicket方法上加上synchronized關(guān)鍵字,就可以使線程同步執(zhí)行了。

private synchronized  void  buyTicket(){}

其中synchronized 使用有幾點(diǎn)注意:

  1. 加到非靜態(tài)方法前,表示鎖this,即當(dāng)前對(duì)象
  2. 加到靜態(tài)方法前,表示鎖當(dāng)前類的所有類對(duì)象
4.1.2 Lock

Lock 是Java1.6之后引入的。使用Lock可以對(duì)鎖進(jìn)行多種操作,可以手動(dòng)的獲取鎖,釋放鎖。

我們用ReentrantLock (ReentrantLock傳送門。。。)改寫上述購票行為。

class TicketLockStation implements Runnable{
    private Lock lock = new ReentrantLock();
    int ticketCount = 10;
    boolean hasTicket = true;
    @Override
    public void run() {
        while(hasTicket){buyTicket();}
    }
    private void  buyTicket(){
        lock.lock();
        try {
        if (ticketCount < 1) {
            hasTicket = false;
            return;
        }
        Thread.sleep(1000);
        System.out.println(Thread.currentThread().getName() + " get the ticket"+ ticketCount--);

        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
}

以上為簡單Lock示例,其中Lock中還有tryLock()(如果獲取不到鎖立即返回), tryLock(long time, TimeUnit unit)(一段時(shí)間后獲取不到鎖則返回)等方法。

上邊就是Lock的簡單示例。

4.2 非阻塞同步

非阻塞同步可以描述為 基于沖突檢測的樂觀并發(fā)策略 ,關(guān)鍵點(diǎn)就是沖突檢測以及樂觀的并發(fā)策略。

沖突檢測是指當(dāng)發(fā)生共享數(shù)據(jù)搶奪的話,我們會(huì)進(jìn)行重試檢測,直到成功為止。而樂觀的并發(fā)策略的實(shí)現(xiàn)大多時(shí)候都不需要掛起線程。

4.2.1 CAS

CAS(Compare And Swap)是非阻塞的一個(gè)實(shí)現(xiàn),其核心指令有3個(gè)操作數(shù),分別為內(nèi)存地址V, 舊值A(chǔ),新值B。當(dāng)CAS執(zhí)行時(shí)當(dāng) V的內(nèi)存地址對(duì)應(yīng)的值與A匹配時(shí),本操作就會(huì)用B來更新V對(duì)應(yīng)的值,否則不執(zhí)行更新。但無論是否更新了V對(duì)應(yīng)的值,都會(huì)返回V處對(duì)應(yīng)的舊值。而且此操作為原子操作。

CAS有一個(gè)缺點(diǎn)就是ABA問題,V處的值原來是A,后來變成了B,然后又變成了A。使用CAS檢查的時(shí)候發(fā)現(xiàn)其值沒變化,然而實(shí)際上已經(jīng)發(fā)生了變化。對(duì)于解決ABA問題,可以使用版本號(hào)的思路來解決,在更新變量的時(shí)候把版本號(hào)加一。然后對(duì)比的時(shí)候也對(duì)比版本號(hào),版本號(hào)與值全都相等則執(zhí)行更新。

4.3 無同步方案

保證線程安全的方法中,也并不是一定要使用同步。同步只是在保證共享數(shù)據(jù)在有競爭條件的時(shí)候使用。如果有方法可以避免共享數(shù)據(jù)的競爭,那么自然就不需要任何同步操作去保證數(shù)據(jù)的正確。所以在有一些場景下,代碼自身就已經(jīng)保證了線程安全,而無須使用同步方法。

4.3.1 棧封閉

其實(shí)主要就是盡量保證數(shù)據(jù)的操作在一個(gè)棧幀中,也就是局部變量,避免過多的去操作共享內(nèi)存數(shù)據(jù)。

4.3.2 線程本地存儲(chǔ)

如果代碼中所需的數(shù)據(jù)必須與其他代碼共享,那么就可以看看這些共享數(shù)據(jù)的代碼是否能保證在同一個(gè)線程中執(zhí)行。如果可以保證共享數(shù)據(jù)在同一個(gè)線程之內(nèi)是可見的,那么線程之間也就不會(huì)出現(xiàn)數(shù)據(jù)的競爭。

ThreadLocal就是一個(gè)最典型的例子,Web應(yīng)用中的Request也是這樣的思路。

4.3.3 可重入代碼 (Reentrant Code)

可重入代碼指在代碼執(zhí)行的任何時(shí)刻中斷它,轉(zhuǎn)而去執(zhí)行另外一段代碼,而控制權(quán)返回后,原來的程序不會(huì)出現(xiàn)任何錯(cuò)誤。所有的可重入代碼都是線程安全的。一般而言可重入代碼不依賴存儲(chǔ)在堆上的數(shù)據(jù)以及公共的系統(tǒng)資源,用到的狀態(tài)量都是有參數(shù)傳入,或者說不調(diào)用,非可重入的方法等。

總結(jié)

關(guān)于線程安全,我們可以總結(jié)以下的一些思路。

  1. 使用互斥同步的方法。
    • 使用Synchronized
    • 使用Lock
  2. 使用非阻塞同步方案。
    • CAS 等。
  3. 無同步方案
    其實(shí)就是在設(shè)計(jì)上盡量避免共享變量的使用,這樣也就可以避免線程安全問題的發(fā)生。
聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • 數(shù)據(jù)
    +關(guān)注

    關(guān)注

    8

    文章

    6713

    瀏覽量

    88300
  • 程序
    +關(guān)注

    關(guān)注

    115

    文章

    3719

    瀏覽量

    80355
  • 多線程
    +關(guān)注

    關(guān)注

    0

    文章

    275

    瀏覽量

    19850
  • 代碼
    +關(guān)注

    關(guān)注

    30

    文章

    4670

    瀏覽量

    67760
  • 線程
    +關(guān)注

    關(guān)注

    0

    文章

    501

    瀏覽量

    19580
收藏 人收藏

    評(píng)論

    相關(guān)推薦

    諾基亞n70白屏怎么辦

    諾基亞n70白屏怎么辦
    發(fā)表于 09-01 15:58 ?3505次閱讀
    諾基亞n70白屏<b class='flag-5'>怎么辦</b>

    手機(jī)進(jìn)水了怎么辦?

    手機(jī)進(jìn)水了怎么辦? 進(jìn)水和摔機(jī)是手機(jī)使用的兩大忌,手機(jī)進(jìn)水尤為嚴(yán)重,下面就將手機(jī)進(jìn)水后的正確處理方法和大家分享。 &
    發(fā)表于 10-26 16:47 ?8759次閱讀

    顯示桌面沒了怎么辦

    顯示桌面沒了怎么辦 我的windows xp的顯示桌面的圖標(biāo)沒有了怎么辦。下載一個(gè)放到系統(tǒng)目
    發(fā)表于 01-18 19:00 ?3805次閱讀

    筆記本風(fēng)扇噪音很大怎么辦

    筆記本風(fēng)扇噪音很大怎么辦 教,我的筆記本的風(fēng)扇噪音很大,怎么辦?  可以嘗試一下給風(fēng)扇加一點(diǎn)“油”——鐘表油!首先
    發(fā)表于 01-21 10:51 ?1872次閱讀

    文件或目錄損壞怎么辦

    文件或目錄損壞怎么辦 我的D盤分區(qū)是NTFS格式的,但現(xiàn)在變成RAW。而且雙擊D盤就提示:無法訪問D:/ 文件或目錄損壞且無法讀取。怎么辦
    發(fā)表于 02-25 10:16 ?1078次閱讀

    內(nèi)存報(bào)警怎么辦

    內(nèi)存報(bào)警怎么辦    近日,我的電腦無法啟動(dòng),同時(shí)在開機(jī)時(shí)發(fā)出表示內(nèi)存出問題的“嘀嘀”警報(bào)聲,請(qǐng)問是什么原因,應(yīng)該如
    發(fā)表于 02-25 11:37 ?2043次閱讀

    電池?fù)Q新無法可依怎么辦

    電池壞了怎么辦?修。修不好怎么辦?換。
    發(fā)表于 03-19 11:23 ?1331次閱讀

    日常運(yùn)營中網(wǎng)站受到安全威脅時(shí)該怎么辦

    很多站長辛辛苦苦做站,卻因?yàn)?b class='flag-5'>安全措施不到位導(dǎo)致網(wǎng)站被掛馬,點(diǎn)進(jìn)去都是灰色鏈接,如果不及時(shí)處理,很容易招致搜索引擎懲罰,那么網(wǎng)站被掛馬怎么辦?出現(xiàn)這種棘手的問題該怎么處理?
    發(fā)表于 11-16 11:17 ?538次閱讀

    linux無法識(shí)別U盤怎么辦

    linux無法識(shí)別U盤怎么辦?
    發(fā)表于 05-19 09:08 ?1.7w次閱讀
    linux無法識(shí)別U盤<b class='flag-5'>怎么辦</b>

    linux下telnet不能使用怎么辦

     linux下telnet不能使用怎么辦?yum安裝方式處理
    發(fā)表于 05-26 09:34 ?5719次閱讀
    linux下telnet不能使用<b class='flag-5'>怎么辦</b>

    網(wǎng)絡(luò)安全密鑰忘記了怎么辦

    網(wǎng)絡(luò)安全密鑰忘了怎么辦呢?由于我們的日常生活中并不是會(huì)用到網(wǎng)絡(luò)安全密鑰。因此很多情況下我們?cè)谠O(shè)置之后都會(huì)忘記。那么網(wǎng)絡(luò)安全密鑰忘記了怎么辦
    的頭像 發(fā)表于 01-11 16:58 ?2.2w次閱讀

    鍵槽滾鍵了怎么辦

    鍵槽滾鍵了怎么辦?
    發(fā)表于 03-07 16:37 ?7次下載

    電機(jī)過熱怎么辦

    電機(jī)過熱怎么辦?WAYON維安PPTC有方案
    的頭像 發(fā)表于 11-01 15:08 ?597次閱讀
    電機(jī)過熱<b class='flag-5'>怎么辦</b>?

    pcb鉆孔偏孔了怎么辦?

    pcb鉆孔偏孔了怎么辦?
    的頭像 發(fā)表于 11-22 11:10 ?2639次閱讀
    pcb鉆孔偏孔了<b class='flag-5'>怎么辦</b>?

    風(fēng)機(jī)軸磨損怎么辦

    電子發(fā)燒友網(wǎng)站提供《風(fēng)機(jī)軸磨損怎么辦.docx》資料免費(fèi)下載
    發(fā)表于 01-07 11:04 ?0次下載