併發編程學習(2)----volatile與synchronized

  這次文章主要探討volatile與synchronized,經過一些基礎概念的介紹,讓讀者對於二者有更深的瞭解。數組

1、幾個相關概念緩存

一、原子性多線程

  其本意是「不能被進一步分隔的最小粒子」,而原子操做意爲「不可被中斷的一個或一系列操做」。在多處理器重實現原子操做變得有點複雜。異步

1)操做系統如何實現原子性。性能

  單處理器能夠對同一個緩存行裏自動進行16/32/64位的原子操做。可是複雜的內存操做處理器是不能保證其原子性的,好比跨總線寬度、跨多個緩存行和跨頁表的訪問。例如,i++是一個讀改寫的操做,因爲該代碼可能被不一樣的線程執行致使最終出現的結果可能不是咱們想要的結果(具體緣由不在此贅述)。可是,處理器提供總線鎖定和緩存鎖定兩個機制來保證複雜內存操做的原子性。優化

a。使用總線鎖保證原子性spa

  處理器經過使用總線鎖來解決i++問題。所謂總線鎖就是使用處理器提供的一個LOCK#信號,當一個處理器在總線上輸出此信號時,其餘處理器的請求將被阻塞,此時該處理器能夠獨佔共享內存。操作系統

b。使用緩存鎖來保證原子性線程

  總線鎖把CPU與內存間的通訊鎖住了,這使得在鎖按期間,其它處理器不能操做其它內存地址的數據,因此總線鎖開銷比較大。咱們只須要保證對某個內存地址的操做是原子性便可(減少鎖粒度)。目前處理器在某些場合下回使用緩存鎖定來代替總線鎖來進行優化。3d

二、可見性

  可見性的意思是當一個線程修改一個共享變量時,另外一個線程能讀到這個修改的值。

三、指令重排

  重排序指編譯器和處理器爲了優化程序性能而對指令序列進行從新排序的一種手段。在多線程的程序中,對某些指令的重排序可能會改變程序的執行結果(後續會有實例說明)。

2、volatile

一、volatile變量具備如下特性。

  可見性:對一個volatile變量的讀,老是能看到任意線程對這個volatile變量最後的寫入、

  禁止重排序:jdk1.5之後對volatile語義進行了增強,不容許volatile變量之間進行重排序。

二、底層實現原理

1)操做系統層面

  操做系統可經過LOCK#前綴指令實現以上前兩個特性。爲了提升處理速度,處理器不直接和內存進行通訊,而是先將系統內存的數據讀到內部緩存(L一、L2或其它)後再進行操做,但操做完不知道什麼時候寫回到內存(操做系統這裏其實使用了異步操做來解決生產消費速度不均的問題)。若是申明瞭volatile的變量進行寫操做,JVM就會想處理器發送一條Lock前綴指令,將這個變量的緩存行的數據寫回到內存中。此時,其它處理器中的值仍是舊值。在多處理器下,爲了保證各個處理器緩存一致,實現了緩存一致性協議。每一個處理器經過嗅探在總線上傳播的數據來檢查本身緩存的值是否是過時了,當處理器發現本身緩存行過時時,就會將當前處理器的緩存行置爲無效狀態,當處理器對這個數據進行修改時,會從新從操做系統內存中把數據讀取處處理器緩存中。

  a。Lock前綴指令會引發處理器緩存回寫到內存。

  Lock前綴指令致使在執行指令期間,聲言處理器的Lock信號。在多處理器環境下,處理器能夠獨佔任何共享內存。操做系統經過總線鎖或者緩存鎖定,來確保同時只能有一個處理器可修改緩存數據。

  b。一個處理器的緩存會寫到內存致使其它處理器的緩存無效。

2)JMM層面。

  在Java中,全部實例域、靜態域和數組元素都存儲在堆內存中,堆內存在線程之間共享。局部變量,方法定義參數和異常處理參數不會在線程之間共享,它們不會有內存可見性問題。

  從抽象的角度來看,JMM定義了線程和主內存之間的抽象關係:線程之間的共享變量存儲在主內存中,每一個線程都有一個私有的本地內存(Local Memory),本地內存中存儲了改線程共享變量的副本。本地內存是JMM的一個抽象概念,並不真實存在。JMM的抽象示意圖以下所示。

  當一個變量被申明爲volatile時。

  寫入操做:JMM會把該線程對應的本地內存中的變量刷新到主存中。

  讀取操做:JMM會把本地內存置爲無效,線程接下來會從主存中讀取共享變量。

 

        

 

3)禁止重排序應用

  在單例模式中,人們使用了雙重校驗來下降鎖同步的開銷,查看如下無volatile時的代碼。

  

  以上是一個錯誤的優化,當線程執行到第4行時,代碼讀取到instance不爲null,可是注意此時instance引用的對象還未初始化。緣由以下。

  instance = new Singleton()能夠分解爲以下3行僞代碼。

    memory = allocate();//1.分配對象的內存空間。

    ctorInstance(memory);//2.初始化對象

    instance = memory;// 3.設置instance指向剛剛分配的地址

  步驟2和3因爲指令重排,可能致使另外一個線程訪問到未被初始化的對象。若是在instance變量前加上volatile便可解決此問題。

 3、synchronized

一、簡單介紹

  synchronized簡單的理解就是對象鎖,Java中的每個對象均可以做爲鎖。它主要能夠確保代碼一系列操做在同一線程只能由一個線程訪問。

具體表現爲一下3種形式。

  對於普通同步方法,鎖是當前實例對象。

  對於靜態同步方法,鎖是當前的Class對象。

  對於同步方法塊,鎖是synchronized括號裏配置的對象。

二、原理介紹。

  在Java中任意一個對象都擁有本身的監視器,當這個對象由同步塊或者這個對象的同步方法調用時,執行方法的線程必須先獲取到該對象的監視器才能進入到同步塊或者同步方法中,而沒有獲取到監視器的線程將會被阻塞在同步塊或者同步方法入口處,進入BLOCKED狀態。當訪問訪問Object的前驅(得到了鎖的線程)釋放了鎖,則會喚醒阻塞在同步隊列中的線程,使其從新嘗試對監視器獲取。具體過程以下圖。

         

 

4、synchronized和volatile比較

一、原理分析

  從原理上分析,volatile是JMM藉助操做系統底層指令實現的關鍵字。當變量被它修飾時,它能夠保證對於該變量的訪問都須要從共享內存中獲取,而每次修改則必須同步刷新回共享內存,它能保證全部線程對變量訪問的可見性。synchronized是JVM層面的,它藉助Java對象的monitor實現的,同一時刻只能有一個線程進入監視器,它能夠保證程序對於變量訪問的可見性和排它性。

  爲了實現線程間的同步,二者都是經過總線鎖/內存鎖定、monitor這樣的方式,即線程在同一時刻只能訪問對應的內存區域,這樣就避免了多個線程同時寫入內存致使結果沒法預知的狀況。

二、特性對比

1)volatile

  a.可見性:可保證變量在內存中的能夠性。

  b.多數狀況下相對synchronized具備較高的性能

  c.有序性:在某些狀況下能夠防止指令重排(經典案例爲單例雙重校驗)

2)synchronized

  a.性能相對較差,可是1.6之後性能有所提高。

  b.有序性,被加鎖的代碼塊同一時刻只能有一個線程訪問程序,保證程序有序執行

5、總結思考

1)爲了解決多線程間的同步問題核心思想:經過限定同一時刻僅有一個線程有寫入。

2)操做系統經過減少鎖的粒度提高性能。

相關文章
相關標籤/搜索