Java多線程之內(nèi)存可見性

可見性

  • 可見性: 一個(gè)線程對(duì)共享變量值的修改,能夠及時(shí)地被其他線程看到.
  • 共享變量: 如果一個(gè)變量在多個(gè)線程的工作內(nèi)存中都存在副本,那么這個(gè)變量就是這幾個(gè)線程的共享變量.

Java 內(nèi)存模型(JMM)

Java內(nèi)存模型(Java Memory Model)描述了Java程序中各種變量(線程共享變量)的訪問(wèn)規(guī)則,以及在JVM中將變量存儲(chǔ)到內(nèi)存和從內(nèi)存中讀取出變量這樣的底層細(xì)節(jié).

  • 所有變量都存儲(chǔ)在主內(nèi)存中

  • 每個(gè)線程都有自己獨(dú)立的工作內(nèi)存,里面保存該線程使用到的變量副本(主內(nèi)存中該變量的拷貝)

  • 線程對(duì)共享變量的所有操作都必須在自己的工作內(nèi)存中進(jìn)行,不能直接從主內(nèi)存中讀寫.

  • 不同線程池之間無(wú)法直接訪問(wèn)其他線程工作內(nèi)存中的變量,線程間的變量值傳遞需要主內(nèi)存來(lái)完成.

共享變量可見性實(shí)現(xiàn)的原理

線程1對(duì)共享變量的修改要想被線程2及時(shí)看到,必須要經(jīng)過(guò)如下2步驟:

  • 把工作內(nèi)存1中更新過(guò)的共享變量刷新到主內(nèi)存中,
  • 將主內(nèi)存中最新的共享變量的值更新到工作內(nèi)存2中.

Synchronized實(shí)現(xiàn)可見性:

Java語(yǔ)言層面支持的可見性實(shí)現(xiàn)方式:

  • synchronized
  • volatile

JMM關(guān)于synchronized的兩條規(guī)定:

  • 線程解鎖前,必須要共享變量的最新值刷新到主內(nèi)存中,
  • 線程加鎖前,將清空工作內(nèi)存中共享變量的值,從而使用共享變量時(shí)需要從主內(nèi)存中重新讀取最新的值.

兩條規(guī)定能保證,線程解鎖前對(duì)共享變量的修改在下次加鎖時(shí)對(duì)其他線程的可見性.

線程執(zhí)行互斥代碼的過(guò)程:

  1. 獲取互斥鎖
  2. 清空工作內(nèi)存
  3. 從主內(nèi)存中拷貝變量的最新副本到工作內(nèi)存
  4. 執(zhí)行代碼
  5. 將更改后的共享變量值刷新到主內(nèi)存中.
  6. 釋放互斥鎖.

指令重排序:代碼書寫的順序與實(shí)際執(zhí)行的順序不同,指令重排序是編譯器或處理為了提高程序性能而做的優(yōu)化,分為三種:

  1. 編譯器優(yōu)化的重排序
  2. 指令級(jí)并行重排序
  3. 內(nèi)存系統(tǒng)的重排序

as-if-serial:無(wú)論如何重排序,程序執(zhí)行的結(jié)果都應(yīng)該與代碼順序執(zhí)行的結(jié)果一致(Java編譯器,運(yùn)行時(shí)和處理器都會(huì)保證java在單線程下遵循as-if-serial語(yǔ)義).

重排序不會(huì)給單線程帶來(lái)內(nèi)存可見性的問(wèn)題,但是在多線程中程序交錯(cuò)執(zhí)行時(shí),重排序可能會(huì)造成內(nèi)存可見性的問(wèn)題.


    public class NoVisibility {
        private static boolean ready;
        private static int number;
    
        private static class ReaderThread extends Thread {
            @Override
            public void run() {
                while (!ready) {
                    Thread.yield();
                    System.out.println(number);
                }
            }
        }

        public static void main(String[] args) {
            new ReaderThread().start();
            number = 42;
            ready = true;
        }
    }

上面的程序可能會(huì)一直運(yùn)行下去,因?yàn)榫€程可能永遠(yuǎn)讀取不到ready的值.也可能輸出為0,因?yàn)榫€程可能看到寫入了ready的值,但是卻沒(méi)有看到number之后寫入的值,這種現(xiàn)象稱為"重排序".

Volatile實(shí)現(xiàn)可見性:

volatile關(guān)鍵字:

  • 能夠保證volatile變量的可見性
  • 不能保證volatile變量復(fù)合操作的原子性

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

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

  • 對(duì)volatile變量執(zhí)行寫操作時(shí),會(huì)在操作后加入一條store屏障指令(強(qiáng)制將變量值刷新到主內(nèi)存中去)
  • 對(duì)volatile變量執(zhí)行讀操作時(shí),會(huì)在操作前加入一條load屏障指令(強(qiáng)制從主內(nèi)存中讀取變量的值)

線程寫volatile變量的過(guò)程:

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

線程讀volatile變量的過(guò)程

  1. 從主內(nèi)存中讀取volatile變量的最新值到線程的工作內(nèi)存中
  2. 從工作內(nèi)存中讀取volatile變量的副本

要在多線程中安全使用volatile變量,必須滿足:

  1. 對(duì)變量的寫入操作不能依賴當(dāng)前值,或者你能確保只有一個(gè)單線程更新變量的值.
  2. 該變量不會(huì)與其他狀態(tài)變量一起納入不變性條件中
  3. 在訪問(wèn)變量時(shí)不需要加鎖.

    public class VolatileDemo {
        private volatile int number = 0;
        
        public int getNumber() {
            return number;
        }
    
        public void increase() {
            try {
                TimeUnit.MILLISECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            this.number++; //非原子操作導(dǎo)致結(jié)果可能不為500
        }
    
        public static void main(String... args) {
            final VolatileDemo volatileDemo = new VolatileDemo();
            for (int i = 0; i < 500; i++) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        volatileDemo.increase();
                    }
                }).start();
            }
            //主線程給子線程讓出資源
            while (Thread.activeCount() > 1) {
                Thread.yield();
            }
            System.out.println("number:" + volatileDemo.getNumber());
        }
    }

想要保證this.number++的原子性操作,有三種方式:

  • synchronized同步關(guān)鍵字
  • Lock
  • AotomicInteger
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 一、首先在弄清楚內(nèi)存可見性前先得了解幾個(gè)基本概念 1.1 什么是可見性:一個(gè)線程對(duì)共享變量值得修改,能及時(shí)地被其他...
    記憶de承渃閱讀 429評(píng)論 0 0
  • 從三月份找實(shí)習(xí)到現(xiàn)在,面了一些公司,掛了不少,但最終還是拿到小米、百度、阿里、京東、新浪、CVTE、樂(lè)視家的研發(fā)崗...
    時(shí)芥藍(lán)閱讀 42,810評(píng)論 11 349
  • Java SE 基礎(chǔ): 封裝、繼承、多態(tài) 封裝: 概念:就是把對(duì)象的屬性和操作(或服務(wù))結(jié)合為一個(gè)獨(dú)立的整體,并盡...
    Jayden_Cao閱讀 2,247評(píng)論 0 8
  • 最近焦慮嗎?為什么事而焦慮的? 我想說(shuō)我挺焦慮的,每次上課前都焦慮,焦慮然后積極地準(zhǔn)備,而后,讓我順利度過(guò)最讓我焦...
    在裝翅膀的豬閱讀 161評(píng)論 0 1
  • 自打《金瓶梅》問(wèn)世四百年以來(lái),在各種褒揚(yáng)和爭(zhēng)論聲中,其“奇書”的魅力愈加彰顯。它立足于現(xiàn)實(shí)市井生活,反映人間世情,...
    凌千一閱讀 3,407評(píng)論 3 16

友情鏈接更多精彩內(nèi)容