欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

深入探究Java線程不安全的原因與解決

 更新時(shí)間:2022年04月26日 09:18:26   作者:淡沫初夏Zz  
線程不安全這個(gè)問(wèn)題,一般在學(xué)Java時(shí),我們老師會(huì)讓我們背誦一段長(zhǎng)長(zhǎng)的話。"當(dāng)不同線程同時(shí)能訪問(wèn)同一個(gè)變量時(shí),可能會(huì)導(dǎo)致線程不安全"。實(shí)際上,這句話重點(diǎn)想突出的只有原子性。而我們往往考慮線程不安全的原因,會(huì)從三方面進(jìn)行考慮:就是原子性,可見(jiàn)性,有序性

一、什么是線程安全

想給出一個(gè)線程安全的確切定義是復(fù)雜的,但我們可以這樣認(rèn)為:

如果多線程環(huán)境下代碼運(yùn)行的結(jié)果是符合我們預(yù)期的,即在單線程環(huán)境應(yīng)該的結(jié)果,則說(shuō)這個(gè)程序是線程安全的

二、線程不安全的原因

1、修改共享數(shù)據(jù)

static class Counter {
    public int count = 0;
    void increase() {
        count++;
    }
}
    public static void main(String[] args) throws InterruptedException {
        final Counter counter = new Counter();
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 50000; i++) {
                counter.increase();
            }
        });
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 50000; i++) {
                counter.increase();
            }
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(counter.count);
}

上面的線程不安全的代碼中, 涉及到多個(gè)線程針對(duì) counter.count 變量進(jìn)行修改.此時(shí)這個(gè) counter.count 是一個(gè)多個(gè)線程都能訪問(wèn)到的 “共享數(shù)據(jù)”

2、原子性

原子性就是 提供互斥訪問(wèn),同一時(shí)刻只能有一個(gè)線程對(duì)數(shù)據(jù)進(jìn)行操作,有時(shí)也把這個(gè)現(xiàn)象叫做同步互斥,表示操作是互相排斥的

不保證原子性會(huì)給多線程帶來(lái)什么問(wèn)題 如果一個(gè)線程正在對(duì)一個(gè)變量操作,中途其他線程插入進(jìn)來(lái)了,如果這個(gè)操作被打斷了,結(jié)果就可能是錯(cuò)誤的。 這點(diǎn)也和線程的搶占式調(diào)度密切相關(guān). 如果線程不是 “搶占” 的, 就算沒(méi)有原子性, 也問(wèn)題不大

3、內(nèi)存可見(jiàn)性

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

Java 內(nèi)存模型 (JMM): Java虛擬機(jī)規(guī)范中定義了Java內(nèi)存模型. 目的是屏蔽掉各種硬件和操作系統(tǒng)的內(nèi)存訪問(wèn)差異,以實(shí)現(xiàn)讓Java程序在各種平臺(tái)下都能達(dá)到一致的并發(fā)效果.

   private static int count = 0;
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            while (count == 0) {
            }
            System.out.println(Thread.currentThread().getName() +
                    "執(zhí)?完成");
        });
        t1.start();
        Scanner scanner = new Scanner(System.in);
        System.out.print("->");
        count = scanner.nextInt();
    }

4、指令重排序

一個(gè)線程觀察其他線程中的指令執(zhí)行順序,由于指令重排序,該觀察結(jié)果一般雜亂無(wú)序,(happens-before原則)。

編譯器對(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à)

三、解決線程安全方案

  • volatile解決內(nèi)存可見(jiàn)性和指令重排序

代碼在寫(xiě)入 volatile 修飾的變量的時(shí)候:

改變線程?作內(nèi)存中volatile變量副本的值,將改變后的副本的值從?作內(nèi)存刷新到主內(nèi)存

  • 直接訪問(wèn)工作內(nèi)存,速度快,但是可能出現(xiàn)數(shù)據(jù)不?致的情況
  • 加上 volatile , 強(qiáng)制讀寫(xiě)內(nèi)存. 速度是慢了, 但是數(shù)據(jù)變的更準(zhǔn)確了

代碼示例:

/**
 * 內(nèi)存可見(jiàn)性
 * 線程1沒(méi)感受到flag的變化,實(shí)際線程2已經(jīng)改變了flag的值
 * 使用volatile,解決內(nèi)存可見(jiàn)性和指令重排序
 */
public class ThreadSeeVolatile {
    //全局變量
    private volatile static boolean flag = true;
    public static void main(String[] args) {
        //創(chuàng)建子線程
        Thread t1 = new Thread(() ->{
            System.out.println("1開(kāi)始執(zhí)行:" + LocalDateTime.now());
            while(flag){
            }
            System.out.println("2結(jié)束執(zhí)行" + LocalDateTime.now());
        });
        t1.start();
        Thread t2 = new Thread(() ->{
            //休眠1s
            try {
                Thread.sleep(10000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("修改flag=false"+ LocalDateTime.now());
            flag = false;
        });
        t2.start();
    }
}

volatile的缺點(diǎn)

volatile 雖然可以解決內(nèi)存可見(jiàn)性和指令重排序的問(wèn)題,但是解決不了原子性問(wèn)題,因此對(duì)于 ++ 和 --操作的線程非安全問(wèn)題依然解決不了

  • 通過(guò)synchronized鎖實(shí)現(xiàn)原子性操作

JDK提供鎖分兩種:

①一種是synchronized,依賴JVM實(shí)現(xiàn)鎖,因此在這個(gè)關(guān)鍵字作用對(duì)象的作用范圍內(nèi)是同一時(shí)刻只能有一個(gè)線程進(jìn)行操作;

②另一種是LOCK,是JDK提供的代碼層面的鎖,依賴CPU指令,代表性的是ReentrantLock。

  • synchronized 會(huì)起到互斥效果, 某個(gè)線程執(zhí)行到某個(gè)對(duì)象的synchronized 中時(shí), 其他線程如果也執(zhí)行到同一個(gè)對(duì)象 synchronized 就會(huì)阻塞等待.
  • 進(jìn)入 synchronized 修飾的代碼塊, 相當(dāng)于 加鎖
  • 退出 synchronized 修飾的代碼塊, 相當(dāng)于 解鎖

synchronized修飾的對(duì)象有四種:

(1)修飾代碼塊,作用于調(diào)用的對(duì)象

(2)修飾方法,作用于調(diào)用的對(duì)象

(3)修飾靜態(tài)方法,作用于所有對(duì)象

(4)修飾類,作用于所有對(duì)象

   // 修飾一個(gè)代碼塊: 明確指定鎖哪個(gè)對(duì)象
    public void test1(int j) {
        synchronized (this) {
        }
    }
    // 修飾一個(gè)方法
    public synchronized void test2(int j) {
    }
    // 修飾一個(gè)類
    public static void test1(int j) {
        synchronized (SynchronizedExample2.class) {
        }
    }
    // 修飾一個(gè)靜態(tài)方法
    public static synchronized void test2(int j) {
    }

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

相關(guān)文章

  • 詳談異步log4j2中的location信息打印問(wèn)題

    詳談異步log4j2中的location信息打印問(wèn)題

    這篇文章主要介紹了詳談異步log4j2中的location信息打印問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-12-12
  • java對(duì)象轉(zhuǎn)化成String類型的四種方法小結(jié)

    java對(duì)象轉(zhuǎn)化成String類型的四種方法小結(jié)

    在java項(xiàng)目的實(shí)際開(kāi)發(fā)和應(yīng)用中,常常需要用到將對(duì)象轉(zhuǎn)為String這一基本功能。本文就詳細(xì)的介紹幾種方法,感興趣的可以了解一下
    2021-08-08
  • Spring與Struts整合之讓Spring管理控制器操作示例

    Spring與Struts整合之讓Spring管理控制器操作示例

    這篇文章主要介紹了Spring與Struts整合之讓Spring管理控制器操作,結(jié)合實(shí)例形式詳細(xì)分析了Spring管理控制器相關(guān)配置、接口實(shí)現(xiàn)與使用技巧,需要的朋友可以參考下
    2020-01-01
  • springmvc+spring+mybatis實(shí)現(xiàn)用戶登錄功能(下)

    springmvc+spring+mybatis實(shí)現(xiàn)用戶登錄功能(下)

    這篇文章主要為大家詳細(xì)介紹了springmvc+spring+mybatis實(shí)現(xiàn)用戶登錄功能的第二篇,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-07-07
  • B/S與C/S架構(gòu)的區(qū)別介紹

    B/S與C/S架構(gòu)的區(qū)別介紹

    本文詳細(xì)講解了B/S與C/S架構(gòu)的區(qū)別,對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2021-12-12
  • Java基礎(chǔ)精講方法的使用

    Java基礎(chǔ)精講方法的使用

    方法,也稱函數(shù),如果想要重復(fù)一段或者多段代碼塊的使用,可以將這些代碼封裝成一個(gè)方法,方法具體表現(xiàn)為某種行為,使用方法可以提高代碼的復(fù)用性
    2022-05-05
  • Java面試題沖刺第二十四天--并發(fā)編程

    Java面試題沖刺第二十四天--并發(fā)編程

    這篇文章主要為大家分享了最有價(jià)值的三道關(guān)于數(shù)據(jù)庫(kù)的面試題,涵蓋內(nèi)容全面,包括數(shù)據(jù)結(jié)構(gòu)和算法相關(guān)的題目、經(jīng)典面試編程題等,感興趣的小伙伴們可以參考一下
    2021-08-08
  • SpringBoot集成RabbitMQ和概念介紹

    SpringBoot集成RabbitMQ和概念介紹

    這篇文章主要介紹了SpringBoot集成RabbitMQ和概念介紹,RabbitMQ即一個(gè)消息隊(duì)列,主要是用來(lái)實(shí)現(xiàn)應(yīng)用程序的異步和解耦,同時(shí)也能起到消息緩沖,消息分發(fā)的作用。更多相關(guān)內(nèi)容需要的小伙伴可以參考一下下面文章內(nèi)容
    2022-05-05
  • Java中CountDownLatch進(jìn)行多線程同步詳解及實(shí)例代碼

    Java中CountDownLatch進(jìn)行多線程同步詳解及實(shí)例代碼

    這篇文章主要介紹了Java中CountDownLatch進(jìn)行多線程同步詳解及實(shí)例代碼的相關(guān)資料,需要的朋友可以參考下
    2017-03-03
  • spring data jpa使用詳解(推薦)

    spring data jpa使用詳解(推薦)

    這篇文章主要介紹了spring data jpa使用詳解(推薦),小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2018-04-04

最新評(píng)論