最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Java多線程之線程安全問(wèn)題詳解

 更新時(shí)間:2025年11月07日 08:42:17   作者:Zxxxxxy_  
Java中的多線程編程是指使用Java語(yǔ)言創(chuàng)建和控制線程的能力,這篇文章主要介紹了Java多線程之線程安全問(wèn)題的相關(guān)資料,文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下

一.線程安全概念

實(shí)際結(jié)果和預(yù)期結(jié)果不一樣,就是線程不安全

多線程帶給我們效率提升的同時(shí),也為我們帶來(lái)了風(fēng)險(xiǎn),因?yàn)槎嗑€程的搶占式執(zhí)行,帶來(lái)的隨機(jī)性。

count++看起來(lái)是一行代碼,實(shí)際上對(duì)應(yīng)了三個(gè)cpu指令

++操作本質(zhì)上要分為三步
1.load指令:先把內(nèi)存中的count值讀取到CPU的寄存器中
2. add指令:把CPU寄存器里的數(shù)值+1操作
3.save指令:把寄存器中的內(nèi)容保存回內(nèi)存上

我們想使用兩個(gè)線程將一個(gè)變量同時(shí)增加5000次

此時(shí)兩個(gè)線程的并發(fā)執(zhí)行,就相當(dāng)于兩組load   add     save指令并發(fā)執(zhí)行。所以這兩組指令的執(zhí)行順序存在了許多可能性。

package Thread;
public class Demo1{
    private static int count=0;
    public static void main(String[] args)throws InterruptedException{
        Object obj=new Object();
        Thread t1=new Thread(()->{
            for(int i=0;i<5000;i++){
                count++;
            }
        });
        Thread t2=new Thread(()->{
            for(int i=0;i<50000;i++){
                count++;
            }
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(count);
    }
}

每運(yùn)行一次,結(jié)果都是不同的,如下所示,這是我分別運(yùn)行三次的結(jié)果

推演圖(有很多種可能性)

一個(gè)線程的load必須在一個(gè)線程的save之后

二.線程安全問(wèn)題原因和如何解決線程安全問(wèn)題

最根本的原因: 搶占式執(zhí)行,隨機(jī)調(diào)度

我們上述線程代碼之所以不安全,因?yàn)樯婕暗轿覀儍蓚€(gè)線程同時(shí)去修改一個(gè)相同的變量。

一個(gè)線程修改一個(gè)變量,安全
多個(gè)線程讀取同一個(gè)變量,安全
多個(gè)線程修改多個(gè)不同的變量,安全

1.根本:操作系統(tǒng)對(duì)于線程的調(diào)度是隨機(jī)的,搶占式執(zhí)行

操作系統(tǒng)的底層設(shè)定,咱們左右不了

2.多線程同時(shí)修改同一個(gè)變量

和代碼的結(jié)構(gòu)直接相關(guān),可以通過(guò)調(diào)整代碼結(jié)構(gòu),規(guī)避一些線程不安全的代碼

(不夠通用,有的情況下,有時(shí)需要多線程修改同一個(gè)變量)

3.修改操作不是原子的

(java中解決線程安全問(wèn)題,最主要的方案)

計(jì)算機(jī)中的鎖和生活中的鎖,是同樣的概念,互斥/排他

加鎖,讓不是原子的,打包成一個(gè)原子的操作

一旦把鎖加上了,其他人要想加鎖,就得阻塞等待。

(eg:上廁所關(guān)門)不允許暴力拆鎖

加鎖操作,不是把線程鎖死到 cpu 上,禁止這個(gè)線程被調(diào)度,而是禁止其他線程重新加這個(gè)鎖,避免其他線程的操作在當(dāng)前線程執(zhí)行過(guò)程中,插隊(duì)

在代碼中如何實(shí)現(xiàn)呢?如下所示

用synchronized(){}將count++包裹起來(lái)

然后再寫一個(gè)locker對(duì)象,更改后的代碼塊如下所示

 Thread t1=new Thread(()->{
            for(int i=0;i<50000;i++){
                synchronized(locker){
                    count++;
                }
            }
        });
        Thread t2=new Thread(()->{
            for(int i=0;i<50000;i++){
                synchronized(locker){
                    count++;
                }
            }
        });

兩個(gè)線程,針對(duì)同一個(gè)對(duì)象加鎖,才會(huì)產(chǎn)生互斥效果,(一個(gè)線程加上鎖了,另一個(gè)線程就得阻塞等待,等到第一個(gè)線程釋放鎖,才有機(jī)會(huì))

運(yùn)行結(jié)果為

三.Synchronized監(jiān)管鎖monitor lock

synchronized的核心作用就是確保??同一時(shí)刻只有一個(gè)線程??能夠執(zhí)行被它修飾的代碼(方法或代碼塊),其他線程必須等待當(dāng)前線程釋放鎖后才能繼續(xù)執(zhí)行。

在 Java 中,synchronized 關(guān)鍵字的底層實(shí)現(xiàn)依賴于監(jiān)視器鎖(Monitor Lock),它是一種基于 JVM 內(nèi)置的同步機(jī)制,用于保證多線程環(huán)境下共享資源的原子性、可見(jiàn)性和有序性。當(dāng)使用 synchronized 時(shí)若出現(xiàn)異常,錯(cuò)誤信息中常提到 “監(jiān)視器鎖”(如 IllegalMonitorStateException),這與它的特性和使用規(guī)則直接相關(guān)。

如圖所示

使用鎖的時(shí)候拋出一些異常,可能就會(huì)看到監(jiān)視器鎖這樣的報(bào)錯(cuò)信息

監(jiān)視器鎖(Monitor Lock)的核心特性

1. 可重入性(Reentrancy)

  • 定義:一個(gè)線程已經(jīng)獲取了某個(gè)對(duì)象的監(jiān)視器鎖后,再次請(qǐng)求該對(duì)象的鎖時(shí)可以直接獲取(無(wú)需重新競(jìng)爭(zhēng)),即允許同一線程多次持有同一把鎖。
  • 作用:避免死鎖(例如遞歸調(diào)用中,線程不會(huì)因重復(fù)獲取自己已持有的鎖而阻塞)。

代碼示例

(線程調(diào)用 methodA 獲取鎖后,調(diào)用 methodB 時(shí)可直接重入,無(wú)需等待。)

package Threadtest;
//synichronized 的可重入性代碼舉例
    class Counter2 {
        private int count = 0;
        public void add() {
            synchronized (this) { // 第一把鎖:this
                    count++;
            }
        }
        public int get() {
            return count;
        }
    }
    public class demo3 {
        public static void main(String[] args) throws InterruptedException {
            Counter2 counter = new Counter2();
            Thread t1 = new Thread(() -> {
                for (int i = 0; i < 50000; i++) {
                    synchronized(counter){
                        counter.add();
                    }
                }
            });
            t1.start();
            t1.join();
            System.out.println("count = " + counter.get());
        }
    }

2. 其他關(guān)鍵特性

  • 獨(dú)占性:同一時(shí)間只能有一個(gè)線程持有監(jiān)視器鎖,其他線程嘗試獲取時(shí)會(huì)被阻塞(進(jìn)入等待隊(duì)列)。
  • 釋放機(jī)制:鎖的釋放是自動(dòng)的 —— 當(dāng)同步代碼塊執(zhí)行完畢、拋出未捕獲異常或執(zhí)行 wait() 時(shí),線程會(huì)釋放鎖。
  • 關(guān)聯(lián)對(duì)象:監(jiān)視器鎖與具體對(duì)象綁定(synchronized 修飾非靜態(tài)方法時(shí)鎖是 this,修飾靜態(tài)方法時(shí)鎖是類對(duì)象 Class)。

四.死鎖的出現(xiàn)原因

兩次加鎖

死鎖(間接與監(jiān)視器鎖相關(guān))

死鎖代碼示例是在synichronized可重入性的代碼示例上的做的修改,可以通過(guò)觀察兩者的區(qū)別,更好的學(xué)習(xí)死鎖

觸發(fā)原因:多個(gè)線程相互持有對(duì)方需要的鎖,且都不釋放,導(dǎo)致無(wú)限阻塞。

本質(zhì):監(jiān)視器鎖的獨(dú)占性導(dǎo)致資源競(jìng)爭(zhēng)無(wú)法化解。

代碼觸發(fā)死鎖,線程就卡住了

構(gòu)成死鎖的原因

(1)鎖是互斥的

(2)鎖不可搶占/剝奪  

注:1/2都是鎖的基本特性,所以只能通過(guò)破壞3/4打破死鎖

(3)請(qǐng)求和保持--保持鎖狀態(tài)的前提下,再請(qǐng)求另一把鎖

(4)循環(huán)等待

死鎖是怎么解決的

(1)加鎖的時(shí)候,不要嵌套,即避免鎖嵌套(打破3

(2)約定加鎖順序(打破4,如何約定,下文中有舉例解釋

(3)銀行家算法

class Counter2 {
    private int count = 0;
    // 新增第二把鎖
    private final Object lock2 = new Object();

    public void add() {
        synchronized (this) { // 第一把鎖:this
            synchronized (lock2) { // 第二把鎖:lock2
                count++;
            }
        }
    }

    public int get() {
        return count;
    }
}

public class Demo19 {
    public static void main(String[] args) {
        Counter2 counter = new Counter2();

        // 線程1:先拿this鎖,再等lock2鎖
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 50000; i++) {
                counter.add();
            }
        });

        // 線程2:先拿lock2鎖,再等this鎖
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 50000; i++) {
                synchronized (counter.lock2) { // 先獲取lock2鎖
                    synchronized (counter) { // 再嘗試獲取this鎖
                        // 此處可執(zhí)行任意操作(如空邏輯),關(guān)鍵是鎖的順序
                    }
                }
            }
        });

        t1.start();
        t2.start();

        // 等待線程執(zhí)行(實(shí)際死鎖時(shí)此代碼不會(huì)執(zhí)行到,但用于演示結(jié)構(gòu))
        try {
            t1.join();
            t2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("count = " + counter.get());
    }
}

死鎖經(jīng)典問(wèn)題:哲學(xué)家就餐問(wèn)題

描述 5 位哲學(xué)家圍坐在圓桌旁,每人左右各有一根筷子,思考時(shí)放下筷子,就餐時(shí)需要同時(shí)拿起左右兩根筷子。若每位哲學(xué)家同時(shí)拿起左手筷子,再等待右手筷子,會(huì)導(dǎo)致所有人都持有一根筷子并等待另一根,形成死鎖。

解決該問(wèn)題的方案(破壞死鎖條件)

死鎖的 4 個(gè)必要條件:互斥、持有并等待、不可剝奪、循環(huán)等待。只需破壞其中一個(gè)即可避免死鎖,以下是兩種經(jīng)典方案:

方案 1:固定筷子獲取順序(破壞 “循環(huán)等待”)

讓所有哲學(xué)家先拿編號(hào)小的筷子,再拿編號(hào)大的筷子,避免循環(huán)等待。

方案 2:最多允許 4 位哲學(xué)家同時(shí)拿筷子(破壞 “持有并等待”)

限制同時(shí)拿筷子的哲學(xué)家數(shù)量,保證至少有一位能拿到兩根筷子,用完后釋放資源。

五.什么是原子性?

我們把一段代碼想象成一個(gè)房間,每個(gè)線程就是要進(jìn)入這個(gè)房間的人。如果沒(méi)有任何機(jī)制保證,A進(jìn)入房間之后,還沒(méi)有出來(lái);B 是不是也可以進(jìn)入房間,打斷 A 在房間里的隱私。這個(gè)就是不具備原子性的。那我們應(yīng)該如何解決這個(gè)問(wèn)題呢?是不是只要給房間加一把鎖,A 進(jìn)去就把門鎖上,其他人是不是就進(jìn)不來(lái)了。這樣就保證了這段代碼的原子性了。

不保證原子性會(huì)給多線程帶來(lái)什么問(wèn)題?

一個(gè)線程正在對(duì)一個(gè)變量操作,中途其他線程插進(jìn)來(lái)了,對(duì)這個(gè)操作造成了打斷,可能會(huì)造成結(jié)果的錯(cuò)誤。這和線程的搶占式調(diào)度有關(guān),如果不是搶占式,就算不是原子性,也問(wèn)題不大。

六.內(nèi)存可見(jiàn)性

ava內(nèi)存模型(JMM): java虛擬機(jī)規(guī)定了java內(nèi)存模型。

目的: 屏蔽各種硬件和操作系統(tǒng)的內(nèi)存訪問(wèn)差異,實(shí)現(xiàn)java程序在各平臺(tái)下都達(dá)成一致的并發(fā)效果

這個(gè)時(shí)候代碼就會(huì)出現(xiàn)問(wèn)題。

七.指令重排序

指令重排序?qū)嶋H上也是編譯器優(yōu)化,簡(jiǎn)單的來(lái)說(shuō),就是我們把一個(gè)東西寫的太爛了,JVM.CPU指令集會(huì)對(duì)其進(jìn)行優(yōu)化。

編譯器對(duì)于指令重排序的前提是 “保持邏輯不發(fā)生變化”. 這一點(diǎn)在單線程環(huán)境下比較容易判斷, 但是在多線程環(huán)境下就沒(méi)那么容易了, 多線程的代碼執(zhí)行復(fù)雜程度更高, 編譯器很難在編譯階段對(duì)代碼的執(zhí)行效果進(jìn)行預(yù)測(cè), 因此激進(jìn)的重排序很容易導(dǎo)致優(yōu)化后的邏輯和之前不等價(jià)

八.內(nèi)存可見(jiàn)性

什么是可見(jiàn)性

可見(jiàn)性:一個(gè)線程對(duì)共享變量值的修改,能夠及時(shí)地被其他線程看到

共享變量:如果一個(gè)變量在多個(gè)線程的工作內(nèi)存中都存在副本,那么這個(gè)變量就是這幾個(gè)線程的共享變量

volatile如何實(shí)現(xiàn)內(nèi)存的可見(jiàn)性

當(dāng)編譯器優(yōu)化處bug,使用這個(gè)關(guān)鍵字修飾的變量,就屬于“易失”,必須每次重新讀取內(nèi)存

深入來(lái)說(shuō):通過(guò)加入內(nèi)存屏障和禁止重排序優(yōu)化來(lái)實(shí)現(xiàn)的

對(duì)volatile變量執(zhí)行寫操作時(shí),會(huì)在寫操作后加入一條store屏障指令
對(duì)volatile變量執(zhí)行讀操作時(shí),會(huì)在讀操作前加入一條load屏障指令

總結(jié)volatile核心作用:

  1. 可見(jiàn)性:一個(gè)線程修改 volatile 變量后,其他線程能立即看到最新值。
  2. 有序性:禁止指令重排,保證代碼執(zhí)行順序與預(yù)期一致。
  3. 不保證原子性:不能替代鎖解決多線程并發(fā)修改問(wèn)題。
public class VolatileDemo {
    // 用 volatile 修飾共享變量,保證多線程可見(jiàn)性
    private static volatile boolean flag = false;

    public static void main(String[] args) {
        // 線程1:等待 flag 變?yōu)?true
        new Thread(() -> {
            System.out.println("線程1啟動(dòng),等待 flag 變?yōu)?true...");
            while (!flag) {
                // 循環(huán)等待,直到 flag 被修改
            }
            System.out.println("線程1檢測(cè)到 flag 為 true,退出循環(huán)");
        }).start();

        // 主線程:休眠1秒后修改 flag
        try {
            Thread.sleep(1000); // 確保線程1先啟動(dòng)
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        flag = true;
        System.out.println("主線程已將 flag 修改為 true");
    }
}

九.synchronized和volatile比較

volatile不需要加鎖,比synchronized更輕量級(jí),不會(huì)阻塞線程

從內(nèi)存可見(jiàn)性角度講,volatile讀相當(dāng)于加鎖,volatile寫相當(dāng)于解鎖

synchronized既能保證可見(jiàn)性,又能保證原子性,而volatile只能保證可見(jiàn)性,無(wú)法保證原子性;

總結(jié)

到此這篇關(guān)于Java多線程之線程安全問(wèn)題詳解的文章就介紹到這了,更多相關(guān)Java線程安全問(wèn)題內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • RocketMQ順序消息的原理與特點(diǎn)

    RocketMQ順序消息的原理與特點(diǎn)

    RocketMQ作為一款純java、分布式、隊(duì)列模型的開(kāi)源消息中間件,支持事務(wù)消息、順序消息、批量消息、定時(shí)消息、消息回溯等,本篇我們了解如何實(shí)現(xiàn)順序消息的原理與特點(diǎn)
    2023-02-02
  • Java 實(shí)戰(zhàn)項(xiàng)目之小說(shuō)在線閱讀系統(tǒng)的實(shí)現(xiàn)流程

    Java 實(shí)戰(zhàn)項(xiàng)目之小說(shuō)在線閱讀系統(tǒng)的實(shí)現(xiàn)流程

    讀萬(wàn)卷書(shū)不如行萬(wàn)里路,只學(xué)書(shū)上的理論是遠(yuǎn)遠(yuǎn)不夠的,只有在實(shí)戰(zhàn)中才能獲得能力的提升,本篇文章手把手帶你用java+SSM+jsp+mysql+maven實(shí)現(xiàn)前臺(tái)閱讀后臺(tái)管理的小說(shuō)在線閱讀系統(tǒng),大家可以在過(guò)程中查缺補(bǔ)漏,提升水平
    2021-11-11
  • SpringBoot集成Liquibase的詳細(xì)步驟

    SpringBoot集成Liquibase的詳細(xì)步驟

    Liquibase 是一個(gè)強(qiáng)大的數(shù)據(jù)庫(kù)版本控制工具,能夠與 Spring Boot
    無(wú)縫集成,以簡(jiǎn)化數(shù)據(jù)庫(kù)遷移和管理的過(guò)程,以下是從開(kāi)始集成到最終運(yùn)行的詳細(xì)步驟,需要的朋友可以參考下
    2024-12-12
  • Mybatis中的config.xml配置文件詳細(xì)解析

    Mybatis中的config.xml配置文件詳細(xì)解析

    這篇文章主要介紹了詳解Mybatis-config.xml配置文件,需要的朋友可以參考下
    2017-12-12
  • SWT(JFace)體驗(yàn)之ApplicationWindow

    SWT(JFace)體驗(yàn)之ApplicationWindow

    SWT(JFace)體驗(yàn)之ApplicationWindow
    2009-06-06
  • Java?retainAll()方法的超詳細(xì)講解

    Java?retainAll()方法的超詳細(xì)講解

    這篇文章主要介紹了Java?retainAll()方法的相關(guān)資料,retainAll()是Java集合接口中的一個(gè)方法,用于保留集合中與指定集合交集的元素,文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2025-03-03
  • Spring如何處理表單提交

    Spring如何處理表單提交

    這篇文章主要介紹了Spring如何處理表單提交,幫助大家更好的理解和學(xué)習(xí)spring框架,感興趣的朋友可以了解下
    2020-10-10
  • SpringBoot中使用Swagger的最全方法詳解

    SpringBoot中使用Swagger的最全方法詳解

    Swagger是一個(gè)規(guī)范和完整的框架,用于生成、描述、調(diào)用和可視化Restful風(fēng)格的Web服務(wù),這篇文章主要給大家介紹了關(guān)于SpringBoot中使用Swagger的相關(guān)資料,文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2023-12-12
  • java發(fā)送短信系列之限制發(fā)送頻率

    java發(fā)送短信系列之限制發(fā)送頻率

    這篇文章主要為大家詳細(xì)介紹了java發(fā)送短信系列之限制發(fā)送頻率,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2016-02-02
  • mybatis教程之查詢緩存(一級(jí)緩存二級(jí)緩存和整合ehcache)

    mybatis教程之查詢緩存(一級(jí)緩存二級(jí)緩存和整合ehcache)

    這篇文章主要介紹了mybatis教程之查詢緩存(一級(jí)緩存二級(jí)緩存和整合ehcache),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-05-05

最新評(píng)論

治县。| 西昌市| 会理县| 盐边县| 英吉沙县| 铜鼓县| 富蕴县| 舒城县| 林甸县| 松阳县| 德兴市| 岳普湖县| 开封县| 古交市| 英超| 宁强县| 喜德县| 嘉峪关市| 清镇市| 绥宁县| 开鲁县| 伊吾县| 英山县| 深州市| 江达县| 泉州市| 拉孜县| 永胜县| 张家港市| 嘉义县| 砀山县| 当雄县| 金坛市| 来凤县| 阳信县| 禄丰县| 晋宁县| 锡林郭勒盟| 桐乡市| 泽州县| 安徽省|