日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 >

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

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

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

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

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

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

有的變量都存儲在主內存中。

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

一份拷貝)。

? ? ? ??(2)數據爭用問題

? ? ? ??多個線程對同一資源操作時,通常會產生進程,比如一個線程往消息隊列插入數據,而另一個線程從消息隊列取

出數據 當消息隊列滿時,插入消息的隊列需要Sleep幾個毫秒,把時間片讓出給取消息的線程,當消息隊列為空

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

資源,最終導致另一個線程死等狀態,會引發一些我問題。這其中就涉及到了數據爭用問題。

? ? ? ??(3)Java內存模型(JMM)

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

變量存儲到內存和從內存中讀取變量這樣的底層細節。

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



? ? ? ??其中有兩條規定

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

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

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

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

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

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

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






? ? ? ??要實現共享變量的的可見性,必須保證兩點:

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

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

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

? ? ? ? 語言層面支持的可見性實現方式:

? ? ? ? 1)使用關鍵字synchronized

? ? ? ? 2)使用關鍵字volatile

? ? ? ??二synchronized實現可見性

? ? ? ??(1)Synchronized實現可見性

? ? ? ? Synchronized能夠實現多線程的原子性(同步)和可見性。

? ? ? ? JVM關于Synchronized的兩條規定:

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

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

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

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

? ? ? ? 1)獲得互斥鎖

? ? ? ? 2)清空工作內存

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

? ? ? ? 4)執行代碼

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

? ? ? ? 6)釋放互斥鎖

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

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

? ? ? ? 包括三種:

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

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

? ? ? ? 3)內存系統的重排序(處理器優化)

? ? ? ? 重排序的可能:


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

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

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

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

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

? ? ? ? 多線程中程序交錯執行,重排序可能會造成內存可見性問題。

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

[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);??
  • ????}??
  • ??
  • ??
  • ????//內部線程類??
  • ????private?class?ReadWriteThread?extends?Thread?{??
  • ????????//根據構造方法中傳入的flag參數,確定線程執行讀操作還是寫操作??
  • ????????private?boolean?flag;??
  • ??????????
  • ????????public?ReadWriteThread(boolean?flag){??
  • ????????????this.flag?=?flag;??
  • ????????}??
  • ??????????????????????????????????????????????????????????????????????????????
  • ????????public?void?run()?{??
  • ????????????if(flag){??
  • ????????????????//構造方法中傳入true,執行寫操作??
  • ????????????????write();??
  • ????????????}else{??
  • ????????????????//構造方法中傳入false,執行讀操作??
  • ????????????????read();??
  • ????????????}??
  • ????????}??
  • ????}??
  • ??
  • ??
  • ????public?static?void?main(String[]?args){??
  • ????????SynchronizedDemo?synDemo?=?new?SynchronizedDemo();??
  • ????????//啟動線程執行寫操作??
  • ????????????????synDemo.new?ReadWriteThread(true).start();??
  • ????????//啟動線程執行讀操作??
  • ????????synDemo.new?ReadWriteThread(false).start();??
  • ????}??
  • }??
  • ? ? ? ? 運行結果:


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


    ? ? ? ? 如果線程程序正常執行那么結果為:6

    ? ? ? ??第一種可能的執行順序:

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

    ? ? ? ? result的值:3

    ? ? ? ??第二種可能的執行順序:

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

    ? ? ? ? result的值:0

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

    ? ? ? ? 1)線程的交叉執行。

    ? ? ? ? 2)重排序結合線程交叉執行。

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

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

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

    ? ? ? ??實現可見性改寫后的代碼:

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

    執行的順序。

    [java]?view plaincopy print?
  • public?static?void?main(String[]?args){??
  • ????????SynchronizedDemo?synDemo?=?new?SynchronizedDemo();??
  • ????????//啟動線程執行寫操作??
  • ????????synDemo.new?ReadWriteThread(true).start();??
  • ????????try?{??
  • ????????????Thread.sleep(1000);??
  • ????}?catch?(InterruptedException?e)?{??
  • ????????????e.printStackTrace();??
  • ????}??
  • ????????//啟動線程執行讀操作??
  • ????????synDemo.new?ReadWriteThread(false).start();??
  • }??
  • ? ? ? ? 或者給讀操作和寫操作的方法聲明中加關鍵字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);??
  • ????}??
  • ? ? ? ? 最后的結果為:


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

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

    ? ? ? ? 2)重排序結合線程交叉執行 ? ? ? —>原子性

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

    ? ? ? ??三volatile實現可見性

    ? ? ? ??(1)volatile關鍵字:

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

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

    ? ? ? ??(2)volatile如何實現內存可見性:

    ? ? ? ? 深入來說:通過加入內存屏障和禁止重排序優化來實現的。

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

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

    ? ? ? ? 通俗地講:volatile變量在每次被線程訪問時,都強迫從主內存中重讀該變量的值,而當該變量發生變化時,又會

    強迫線程將最新的值刷新到主內存,這樣任何時刻,不同的線程總能看到該變量的最新值。

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

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

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

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

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

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

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

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

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

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

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

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

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

    ? ? ? ??//加入synchronized,變為原子操作? ?

    ? ? ? ??synchronized(thhis){?

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

    ? ? ? ? }

    ? ? ? ??//變為volatile變量,無法保證原子性

    ? ? ? ? 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();??
  • ????????}??
  • ??????????
  • ????????//如果還有子線程在運行,主線程就讓出CPU資源,??
  • ????????//直到所有的子線程都運行完了,主線程再繼續往下執行??
  • ????????while(Thread.activeCount()?>?1){??
  • ????????????Thread.yield();??
  • ????????}??
  • ??????????
  • ????????System.out.println("number?:?"?+?volDemo.getNumber());??
  • ????}??
  • }??
  • ? ? ? ? 輸出的結果基本都接近500:


    ? ? ? ? 解決方案:

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

    ? ? ? ? 1)使用synchronized關鍵字

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

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

    ? ? ? ??使用synchronized關鍵字保證原子性修改后的代碼:

    [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++;??
  • ????}??
  • }??
  • ? ? ? ? ?運行結果:

    ? ? ? ? ?使用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();??
  • }??
  • ? ? ? ? ?運行結果:同上

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

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

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

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

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

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

    ? ? ? ? ?四總結

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

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

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

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

    ? ? ? ? ?4)從內存可見性角度看,volatile讀相當于加鎖,volatile寫相當于解鎖。

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

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

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

    ? ? ? ? ?(2)補充1

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

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

    ? ? ? ? Java中對共享數據操作的并發控制是采用加鎖技術。

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

    ? ? ? ? final也可以保證內存可見性。

    ? ? ? ??(3)補充2

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

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

    ? ? ? ? 導致問題:有可能會出現讀取到半個變量的情況。

    ? ? ? ? 解決方法:加volatile關鍵字。

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

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

    ? ? ? ? 答:一般只有在短時間內高并發的情況下才會出現變量得不到及時更新的情況,因為CPU在執行時會很快地刷新

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

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

    內存2,然后這個線程就釋放CPU時間片,這樣一來就保證了數據的可見性。如果是慢了話CPU強行剝奪該線的資

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

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


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

    總結

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

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。