日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程语言 > java >内容正文

java

JavaSE学习53:细说多线程之内存可见性

發(fā)布時間:2025/3/21 java 25 豆豆
生活随笔 收集整理的這篇文章主要介紹了 JavaSE学习53:细说多线程之内存可见性 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

?一共享變量在線程間的可見性

? ? ? ??(1)有關(guān)可見性的一些概念介紹

? ? ? ??可見性:一個線程對共享變量值的修改,能夠及實地被其他線程看到。

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

有的變量都存儲在主內(nèi)存中。

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

一份拷貝)。

? ? ? ??(2)數(shù)據(jù)爭用問題

? ? ? ??多個線程對同一資源操作時,通常會產(chǎn)生進(jìn)程,比如一個線程往消息隊列插入數(shù)據(jù),而另一個線程從消息隊列取

出數(shù)據(jù) 當(dāng)消息隊列滿時,插入消息的隊列需要Sleep幾個毫秒,把時間片讓出給取消息的線程,當(dāng)消息隊列為空

時,取消息隊列的線程需要Sleep幾個毫秒,把時間片讓給插入消息的線程。如果不這樣做,則會出現(xiàn)某個線程獨(dú)占

資源,最終導(dǎo)致另一個線程死等狀態(tài),會引發(fā)一些我問題。這其中就涉及到了數(shù)據(jù)爭用問題。

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

? ? ? ??Java內(nèi)存模型(Java?Memory Model)描述了Java程序中各種變量(線程共享變量)的大訪問規(guī)則,以及在JVM中將

變量存儲到內(nèi)存和從內(nèi)存中讀取變量這樣的底層細(xì)節(jié)。

? ? ? ? JMM模型的工作流程:



? ? ? ??其中有兩條規(guī)定

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

? ? ? ? 2)不同線程之間無法直接訪問其他線程工作內(nèi)存中的變量,線程間變量值的傳遞需要通過主存來完成。

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

? ? ? ? 線程1對共享變量的修改要想被線程2及時看到,必須要經(jīng)過如下兩個步驟:

? ? ? ? 1)把工作內(nèi)存1中更新過的共享變量刷新到主內(nèi)存中。

? ? ? ? 2)將主內(nèi)存中最新的共享變量的值更新到工作內(nèi)存2中。

? ? ? ? 我們來看一下這個流程:






? ? ? ??要實現(xiàn)共享變量的的可見性,必須保證兩點(diǎn):

? ? ? ? 1)線程修改后的共享變量值能夠及時從工作內(nèi)存刷新到主內(nèi)存中。

? ? ? ? 2)其他線程能夠及時把共享變量的最新值從主內(nèi)存更新到自己的工作內(nèi)存中。

? ? ? ??(5)可見性的實現(xiàn)方式

? ? ? ? 語言層面支持的可見性實現(xiàn)方式:

? ? ? ? 1)使用關(guān)鍵字synchronized

? ? ? ? 2)使用關(guān)鍵字volatile

? ? ? ??二synchronized實現(xiàn)可見性

? ? ? ??(1)Synchronized實現(xiàn)可見性

? ? ? ? Synchronized能夠?qū)崿F(xiàn)多線程的原子性(同步)和可見性。

? ? ? ? JVM關(guān)于Synchronized的兩條規(guī)定:

? ? ? ? 1)線程解鎖前,必須把共享變量的最新值刷新到主內(nèi)存中。

? ? ? ? 2)線程加鎖時,將清空工作內(nèi)存中共享變量的值,從而使用共享變量時需要從主內(nèi)存中重新讀取最新的值(注

意:加鎖和解鎖需要同一把鎖)。

? ? ? ??(2)線程執(zhí)行互斥代碼的過程:

? ? ? ? 1)獲得互斥鎖

? ? ? ? 2)清空工作內(nèi)存

? ? ? ? 3)從主內(nèi)存拷貝變量的最新副本到工作內(nèi)存

? ? ? ? 4)執(zhí)行代碼

? ? ? ? 5)將更改后的共享變量的值刷新到主內(nèi)存

? ? ? ? 6)釋放互斥鎖

? ? ? ??(3)指令重排序

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

? ? ? ? 包括三種:

? ? ? ? 1)編譯器優(yōu)化的重排序(編譯器優(yōu)化)

? ? ? ? 2)指令級并行的重排序(處理器優(yōu)化)

? ? ? ? 3)內(nèi)存系統(tǒng)的重排序(處理器優(yōu)化)

? ? ? ? 重排序的可能:


? ? ? ??(4)as-if-serial語義

? ? ? ? as-if-serial含義指的是無論如何重排序,程序執(zhí)行的結(jié)果應(yīng)該與代碼順序執(zhí)行的結(jié)果一致(Java編譯器、運(yùn)行時和

處理器都會保證Java在單線程下遵循as-if-serial語義)。重排序不會給單線程帶來內(nèi)存可見性的問題。

? ? ? ? 我們來看一段程序的例子:

? ? ? ? 單線程中程序中第1、2行的順序可以重排,但第3行不能。

? ? ? ? 多線程中程序交錯執(zhí)行,重排序可能會造成內(nèi)存可見性問題。

? ? ? ??(5)synchronized實現(xiàn)可見性的實例代碼:

[java]?view plaincopy print?
  • public?class?SynchronizedDemo?{??
  • ????//共享變量??
  • ????private?boolean?ready?=?false;??
  • ????private?int?result?=?0;??
  • ????private?int?number?=?1;?????
  • ??????
  • ????//寫操作??
  • ????public?void?write(){??
  • ????????ready?=?true;???????????????????//1.1?????????????????
  • ????????number?=?2;?????????????????????//1.2?????????????????
  • ????}??
  • ??????
  • ????//讀操作??
  • ????public?void?read(){????????????????
  • ????????if(ready){??????????????//2.1??
  • ????????????result?=?number*3;??????//2.2??
  • ????????}?????????
  • ????????System.out.println("result的值為:"?+?result);??
  • ????}??
  • ??
  • ??
  • ????//內(nèi)部線程類??
  • ????private?class?ReadWriteThread?extends?Thread?{??
  • ????????//根據(jù)構(gòu)造方法中傳入的flag參數(shù),確定線程執(zhí)行讀操作還是寫操作??
  • ????????private?boolean?flag;??
  • ??????????
  • ????????public?ReadWriteThread(boolean?flag){??
  • ????????????this.flag?=?flag;??
  • ????????}??
  • ??????????????????????????????????????????????????????????????????????????????
  • ????????public?void?run()?{??
  • ????????????if(flag){??
  • ????????????????//構(gòu)造方法中傳入true,執(zhí)行寫操作??
  • ????????????????write();??
  • ????????????}else{??
  • ????????????????//構(gòu)造方法中傳入false,執(zhí)行讀操作??
  • ????????????????read();??
  • ????????????}??
  • ????????}??
  • ????}??
  • ??
  • ??
  • ????public?static?void?main(String[]?args){??
  • ????????SynchronizedDemo?synDemo?=?new?SynchronizedDemo();??
  • ????????//啟動線程執(zhí)行寫操作??
  • ????????????????synDemo.new?ReadWriteThread(true).start();??
  • ????????//啟動線程執(zhí)行讀操作??
  • ????????synDemo.new?ReadWriteThread(false).start();??
  • ????}??
  • }??
  • ? ? ? ? 運(yùn)行結(jié)果:


    ? ? ? ? 在主線程中啟動線程執(zhí)行讀、寫操作。可見性分析:


    ? ? ? ? 如果線程程序正常執(zhí)行那么結(jié)果為:6

    ? ? ? ??第一種可能的執(zhí)行順序:

    ? ? ? ? 1.1—>2.1—>2.2—>1.2

    ? ? ? ? result的值:3

    ? ? ? ??第二種可能的執(zhí)行順序:

    ? ? ? ? 1.2—>2.1—>2.2—>1.1

    ? ? ? ? result的值:0

    ? ? ? ? 也可能有其他的情況,就不再進(jìn)行舉例。導(dǎo)致共享變量在線程間不可見的原因:

    ? ? ? ? 1)線程的交叉執(zhí)行。

    ? ? ? ? 2)重排序結(jié)合線程交叉執(zhí)行。

    ? ? ? ? 3)共享變量更新后的值沒有在工作內(nèi)存與主內(nèi)存間及時更新。

    ? ? ? ? 我們加入synchronized(this)同步代碼塊,當(dāng)一個線程訪問object的一個synchronized(this)同步代碼塊時,其他線

    程對object中所有其它synchronized(this)同步代碼塊的訪問將會被阻塞。

    ? ? ? ??實現(xiàn)可見性改寫后的代碼:

    ? ? ? ?可以在寫線程和讀線程之間加個休眠操作,讓寫線程執(zhí)行完,讀線程在執(zhí)行,也可以使用wait和notify來控制線程

    執(zhí)行的順序。

    [java]?view plaincopy print?
  • public?static?void?main(String[]?args){??
  • ????????SynchronizedDemo?synDemo?=?new?SynchronizedDemo();??
  • ????????//啟動線程執(zhí)行寫操作??
  • ????????synDemo.new?ReadWriteThread(true).start();??
  • ????????try?{??
  • ????????????Thread.sleep(1000);??
  • ????}?catch?(InterruptedException?e)?{??
  • ????????????e.printStackTrace();??
  • ????}??
  • ????????//啟動線程執(zhí)行讀操作??
  • ????????synDemo.new?ReadWriteThread(false).start();??
  • }??
  • ? ? ? ? 或者給讀操作和寫操作的方法聲明中加關(guān)鍵字synchronized修飾:

    [java]?view plaincopy print?
  • //寫操作??
  • ????public?synchronized?void?write(){??
  • ????????ready?=?true;????????????????????????//1.1????????????????
  • ????????number?=?2;?????????????????????????//1.2?????????????????
  • ????}??
  • ??????
  • ????//讀操作??
  • ????public?synchronized?void?read(){???????????????????
  • ????????if(ready){???????????????????????????//2.1??
  • ????????????result?=?number*3;??????//2.2??
  • ????????}?????????
  • ????????System.out.println("result的值為:"?+?result);??
  • ????}??
  • ? ? ? ? 最后的結(jié)果為:


    ? ? ? ? 不可見原因: ? ? ? ? ? ? ? ? ? ? ? ? ?synchronized解決方案:

    ? ? ? ? 1)線程的交叉執(zhí)行 ? ? ? ? ? ? ? ? ? ? —>原子性

    ? ? ? ? 2)重排序結(jié)合線程交叉執(zhí)行 ? ? ? —>原子性

    ? ? ? ? 3)共享變量未及時更新 ? ? ? ? ? ? ?—>可見性

    ? ? ? ??三volatile實現(xiàn)可見性

    ? ? ? ??(1)volatile關(guān)鍵字:

    ? ? ? ? 1)能夠保證volatile變量的可見性

    ? ? ? ? 2)不能保證volatile變量復(fù)合操作的原子性

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

    ? ? ? ? 深入來說:通過加入內(nèi)存屏障和禁止重排序優(yōu)化來實現(xiàn)的。

    ? ? ? ? 1)對volatile變量執(zhí)行寫操作時,會在寫操作后加入一條store屏障指令。

    ? ? ? ? 2)對volatile變量執(zhí)行讀操作時,會在讀操作后加入一條load屏障指令。

    ? ? ? ? 通俗地講:volatile變量在每次被線程訪問時,都強(qiáng)迫從主內(nèi)存中重讀該變量的值,而當(dāng)該變量發(fā)生變化時,又會

    強(qiáng)迫線程將最新的值刷新到主內(nèi)存,這樣任何時刻,不同的線程總能看到該變量的最新值。

    ? ? ? ? 線程寫volatile變量的過程:

    ? ? ? ? 1)改變線程工作內(nèi)存中volatile變量副本的值。

    ? ? ? ? 2)將改變后的副本的值從工作內(nèi)存刷新到主內(nèi)存。

    ? ? ? ? 線程讀volatile變量的過程:

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

    ? ? ? ? 2)從工作內(nèi)存中讀取volatile變量的副本。

    ? ? ? ??(3)volatile不能保證volatile變量復(fù)合操作的原子性

    ? ? ? ? 對于下面的一段程序的使用volatile和synchronized

    ? ? ? ? private int number = 0; ? ?? ? ? ? ??

    ? ? ? ? number++;//不是原子操作 ?? ? ? ? ? ? ? ? ?

    ? ? ? ? 1讀取number的值 ??? ? ? ? ? ? ? ? ? ?

    ? ? ? ? 2將number的值加1 ? ?? ? ? ? ? ? ? ??

    ? ? ? ? 3寫入最新的number的值 ?

    ? ? ? ??//加入synchronized,變?yōu)樵硬僮? ?

    ? ? ? ??synchronized(thhis){?

    ? ? ? ? ? ? ??number++;?

    ? ? ? ? }

    ? ? ? ??//變?yōu)関olatile變量,無法保證原子性

    ? ? ? ? private volatile int number = 0;

    ? ? ? ??(4)volatile不能保證原子性的實例代碼:

    [java]?view plaincopy print?
  • import?java.util.*;??
  • ??
  • public?class?VolatileDemo?{??
  • ????private?volatile?int?number?=?0;??
  • ??????
  • ????public?int?getNumber(){??
  • ????????return?this.number;??
  • ????}??
  • ??????
  • ????public?void?increase(){??
  • ????????try?{??
  • ????????????Thread.sleep(100);??
  • ????????}?catch?(InterruptedException?e)?{??
  • ????????????e.printStackTrace();??
  • ????????}??
  • ????????this.number++;??
  • ????}??
  • ??????
  • ????public?static?void?main(String[]?args)?{??
  • ????????final?VolatileDemo?volDemo?=?new?VolatileDemo();??
  • ????????????????//在主線程中啟動500個線程的++操作??
  • ????????for(int?i?=?0?;?i?<?500?;?i++){??
  • ????????????new?Thread(new?Runnable()?{??
  • ????????????????public?void?run()?{??
  • ????????????????????volDemo.increase();??
  • ????????????????}??
  • ????????????}).start();??
  • ????????}??
  • ??????????
  • ????????//如果還有子線程在運(yùn)行,主線程就讓出CPU資源,??
  • ????????//直到所有的子線程都運(yùn)行完了,主線程再繼續(xù)往下執(zhí)行??
  • ????????while(Thread.activeCount()?>?1){??
  • ????????????Thread.yield();??
  • ????????}??
  • ??????????
  • ????????System.out.println("number?:?"?+?volDemo.getNumber());??
  • ????}??
  • }??
  • ? ? ? ? 輸出的結(jié)果基本都接近500:


    ? ? ? ? 解決方案:

    ? ? ? ??保證number自增操作的原子操作

    ? ? ? ? 1)使用synchronized關(guān)鍵字

    ? ? ? ? 2)JDK1.5以后使用ReentrantLock(java.util.concurrent.locks包下)

    ? ? ? ? 3)JDK1.5以后使用At.tomicIterger(java.util.concurrent.atomic包下)

    ? ? ? ??使用synchronized關(guān)鍵字保證原子性修改后的代碼:

    [java]?view plaincopy print?
  • ???????private?int?number?=?0;??
  • ??
  • public?int?getNumber(){??
  • ????return?this.number;??
  • }??
  • ??
  • public?void?increase(){??
  • ????try?{??
  • ????????Thread.sleep(100);??
  • ????}?catch?(InterruptedException?e)?{??
  • ????????e.printStackTrace();??
  • ????}??
  • ????synchronized(this){??
  • ????????this.number++;??
  • ????}??
  • }??
  • ? ? ? ? ?運(yùn)行結(jié)果:

    ? ? ? ? ?使用ReentrantLock修改后的代碼:

    [java]?view plaincopy print?
  • ???????private?Lock?lock?=?new?ReentrantLock();??
  • private?int?number?=?0;??
  • ??
  • public?int?getNumber(){??
  • ????return?this.number;??
  • }??
  • ??
  • public?void?increase(){??
  • ????try?{??
  • ????????Thread.sleep(100);??
  • ????}?catch?(InterruptedException?e)?{??
  • ????????e.printStackTrace();??
  • ????}??
  • ????lock.lock();??
  • ????try?{??
  • ????????this.number++;??
  • ????}?finally?{??
  • ????????lock.unlock();??
  • }??
  • ? ? ? ? ?運(yùn)行結(jié)果:同上

    ? ? ? ? ?(5)?volatile使用注意volatile適用場合

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

    ? ? ? ? ?1)對變量的寫入操作不依賴其當(dāng)前值

    ? ? ? ? ?不滿足:number++ ?count=count*5等

    ? ? ? ? ?滿足:boolean變量、記錄溫度變化的變量

    ? ? ? ? ?2)該變量沒有包含在具有其他變量的不變式中。不滿足:不變式low<up

    ? ? ? ? ?四總結(jié)

    ? ? ? ? ?(1)synchronized與volatile的比較

    ? ? ? ? ?1)volatile比synchronized更輕量級。

    ? ? ? ? ?2)volatile沒有synchronized使用的廣泛。

    ? ? ? ? ?3)volatile不需要加鎖,比synchronized更輕量級,不會哦阻塞線程。

    ? ? ? ? ?4)從內(nèi)存可見性角度看,volatile讀相當(dāng)于加鎖,volatile寫相當(dāng)于解鎖。

    ? ? ? ? ?5)synchronized既能保證可見性,又能保證原子性,而volatile只能保證可見性,無法保證原子性。

    ? ? ? ? ?6)volatile本身不保證獲取和設(shè)置操作的原子性,僅僅保持修改的可見性。但是java的內(nèi)存模型保證聲明為

    volatile的long和double變量的get和set操作是原子的。

    ? ? ? ? ?(2)補(bǔ)充1

    ? ? ? ? ?共享數(shù)據(jù)的訪問權(quán)限都必須定義為private。一般是考慮安全性,對數(shù)據(jù)提供保護(hù),可以通過set()方法賦值,再

    通過get()方法取值,這就是java封裝的思想。

    ? ? ? ? Java中對共享數(shù)據(jù)操作的并發(fā)控制是采用加鎖技術(shù)。

    ? ? ? ? Java中沒有提供檢測與避免死鎖的專門機(jī)制,但應(yīng)用程序員可以采用某些策略防止死鎖的發(fā)生。

    ? ? ? ? final也可以保證內(nèi)存可見性。

    ? ? ? ??(3)補(bǔ)充2

    ? ? ? ? 對64位(long、double)變量的讀寫可能不是原子操作

    ? ? ? ? Java內(nèi)存模型允許JVM將沒有被volatile修飾的64位數(shù)據(jù)類型的讀寫操作劃分為兩次32位的讀寫操作來運(yùn)行。

    ? ? ? ? 導(dǎo)致問題:有可能會出現(xiàn)讀取到半個變量的情況。

    ? ? ? ? 解決方法:加volatile關(guān)鍵字。

    ? ? ? ??(4)一個問題

    ? ? ? ? 即使沒有保證可見性的措施,很多時候共享變量依然能夠在主內(nèi)存和工作內(nèi)存間得到及時的更新?

    ? ? ? ? 答:一般只有在短時間內(nèi)高并發(fā)的情況下才會出現(xiàn)變量得不到及時更新的情況,因為CPU在執(zhí)行時會很快地刷新

    緩存,所以一般情況下很難看到這種問題。

    ? ? ? ? 慢了不就不會刷新了。。。CPU運(yùn)算快的話,在分配的時間片內(nèi)就能完成所有工作:工作內(nèi)從1->主內(nèi)存->工作

    內(nèi)存2,然后這個線程就釋放CPU時間片,這樣一來就保證了數(shù)據(jù)的可見性。如果是慢了話CPU強(qiáng)行剝奪該線的資

    源,分配給其它線程,該線程就需要等待CPU下次給該線程分配時間片,如果在這段時間內(nèi)有別的線程訪問共享變

    量,可見性就沒法保證了。


    from:?http://blog.csdn.net/erlian1992/article/details/51712615

    總結(jié)

    以上是生活随笔為你收集整理的JavaSE学习53:细说多线程之内存可见性的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。