日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
既生Synchronized,何生Volatile?!

在我的博客和公眾號中,發(fā)表過很多篇關(guān)于并發(fā)編程的文章,之前的文章中我們介紹過了兩個在Java并發(fā)編程中比較重要的兩個關(guān)鍵字:synchronized和volatile

創(chuàng)新互聯(lián)主要從事成都網(wǎng)站制作、成都網(wǎng)站設(shè)計、網(wǎng)頁設(shè)計、企業(yè)做網(wǎng)站、公司建網(wǎng)站等業(yè)務(wù)。立足成都服務(wù)柳州,10余年網(wǎng)站建設(shè)經(jīng)驗,價格優(yōu)惠、服務(wù)專業(yè),歡迎來電咨詢建站服務(wù):18980820575

我們簡單回顧一下相關(guān)內(nèi)容:

1、Java語言為了解決并發(fā)編程中存在的原子性、可見性和有序性問題,提供了一系列和并發(fā)處理相關(guān)的關(guān)鍵字,比如synchronized、volatile、final、concurren包等。

2、synchronized通過加鎖的方式,使得其在需要原子性、可見性和有序性這三種特性的時候都可以作為其中一種解決方案,看起來是“萬能”的。的確,大部分并發(fā)控制操作都能使用synchronized來完成。

3、volatile通過在volatile變量的操作前后插入內(nèi)存屏障的方式,保證了變量在并發(fā)場景下的可見性和有序性。

4、volatile關(guān)鍵字是無法保證原子性的,而synchronized通過monitorenter和monitorexit兩個指令,可以保證被synchronized修飾的代碼在同一時間只能被一個線程訪問,即可保證不會出現(xiàn)CPU時間片在多個線程間切換,即可保證原子性。

那么,我們知道,synchronized和volatile兩個關(guān)鍵字是Java并發(fā)編程中經(jīng)常用到的兩個關(guān)鍵字,而且,通過前面的回顧,我們知道synchronized可以保證并發(fā)編程中不會出現(xiàn)原子性、可見性和有序性問題,而volatile只能保證可見性和有序性,那么,既生synchronized、何生volatile?

接下來,本文就來論述一下,為什么Java中已經(jīng)有了synchronized關(guān)鍵字,還要提供volatile關(guān)鍵字。

1.synchronized的問題

我們都知道synchronized其實是一種加鎖機制,那么既然是鎖,天然就具備以下幾個缺點:

1、有性能損耗

雖然在JDK 1.6中對synchronized做了很多優(yōu)化,如如適應(yīng)性自旋、鎖消除、鎖粗化、輕量級鎖和偏向鎖等(深入理解多線程(五)—— Java虛擬機的鎖優(yōu)化技術(shù)),但是他畢竟還是一種鎖。

以上這幾種優(yōu)化,都是盡量想辦法避免對Monitor(深入理解多線程(四)—— Moniter的實現(xiàn)原理)進行加鎖,但是,并不是所有情況都可以優(yōu)化的,況且就算是經(jīng)過優(yōu)化,優(yōu)化的過程也是有一定的耗時的。

所以,無論是使用同步方法還是同步代碼塊,在同步操作之前還是要進行加鎖,同步操作之后需要進行解鎖,這個加鎖、解鎖的過程是要有性能損耗的。

關(guān)于二者的性能對比,由于虛擬機對鎖實行的許多消除和優(yōu)化,使得我們很難量化這兩者之間的性能差距,但是我們可以確定的一個基本原則是:volatile變量的讀操作的性能小號普通變量幾乎無差別,但是寫操作由于需要插入內(nèi)存屏障所以會慢一些,即便如此,volatile在大多數(shù)場景下也比鎖的開銷要低。

2、產(chǎn)生阻塞

我們在深入理解多線程(一)——Synchronized的實現(xiàn)原理中介紹過關(guān)于synchronize的實現(xiàn)原理,無論是同步方法還是同步代碼塊,無論是ACC_SYNCHRONIZED還是monitorenter、monitorexit都是基于Monitor實現(xiàn)的。

基于Monitor對象,當多個線程同時訪問一段同步代碼時,首先會進入Entry Set,當有一個線程獲取到對象的鎖之后,才能進行The Owner區(qū)域,其他線程還會繼續(xù)在Entry Set等待。并且當某個線程調(diào)用了wait方法后,會釋放鎖并進入Wait Set等待。

所以,synchronize實現(xiàn)的鎖本質(zhì)上是一種阻塞鎖,也就是說多個線程要排隊訪問同一個共享對象。

而volatile是Java虛擬機提供的一種輕量級同步機制,他是基于內(nèi)存屏障實現(xiàn)的。說到底,他并不是鎖,所以他不會有synchronized帶來的阻塞和性能損耗的問題。

2.volatile的附加功能

除了前面我們提到的volatile比synchronized性能好以外,volatile其實還有一個很好的附加功能,那就是禁止指令重排。

我們先來舉一個例子,看一下如果只使用synchronized而不使用volatile會發(fā)生什么問題,就拿我們比較熟悉的單例模式來看。

我們通過雙重校驗鎖的方式實現(xiàn)一個單例,這里不使用volatile關(guān)鍵字:

 
 
 
 
  1. public class Singleton {   
  2.        private static Singleton singleton;   
  3.         private Singleton (){}   
  4.         public static Singleton getSingleton() {   
  5.         if (singleton == null) {   
  6.             synchronized (Singleton.class) {   
  7.                 if (singleton == null) {   
  8.                     singleton = new Singleton();   
  9.                 }   
  10.             }   
  11.         }   
  12.        return singleton;   
  13.        }   
  14.     }   

以上代碼,我們通過使用synchronized對Singleton.class進行加鎖,可以保證同一時間只有一個線程可以執(zhí)行到同步代碼塊中的內(nèi)容,也就是說singleton = new Singleton()這個操作只會執(zhí)行一次,這就是實現(xiàn)了一個單例。

但是,當我們在代碼中使用上述單例對象的時候有可能發(fā)生空指針異常。這是一個比較詭異的情況。

我們假設(shè)Thread1 和 Thread2兩個線程同時請求Singleton.getSingleton方法的時候:

  • Step1 ,Thread1執(zhí)行到第8行,開始進行對象的初始化。
  • Step2 ,Thread2執(zhí)行到第5行,判斷singleton == null。
  • Step3 ,Thread2經(jīng)過判斷發(fā)現(xiàn)singleton != null,所以執(zhí)行第12行,返回singleton。
  • Step4 ,Thread2拿到singleton對象之后,開始執(zhí)行后續(xù)的操作,比如調(diào)用singleton.call()。

以上過程,看上去并沒有什么問題,但是,其實,在Step4,Thread2在調(diào)用singleton.call()的時候,是有可能拋出空指針異常的。

之所有會有NPE拋出,是因為在Step3,Thread2拿到的singleton對象并不是一個完整的對象。

什么叫做不完整對象,這個怎么理解呢?

我們這里來先來看一下,singleton = new Singleton();這行代碼到底做了什么事情,大致過程如下:

1、虛擬機遇到new指令,到常量池定位到這個類的符號引用。

2、檢查符號引用代表的類是否被加載、解析、初始化過。

3、虛擬機為對象分配內(nèi)存。

4、虛擬機將分配到的內(nèi)存空間都初始化為零值。

5、虛擬機對對象進行必要的設(shè)置。

6、執(zhí)行方法,成員變量進行初始化。

7、將對象的引用指向這個內(nèi)存區(qū)域。

我們把這個過程簡化一下,簡化成3個步驟:

a、JVM為對象分配一塊內(nèi)存M

b、在內(nèi)存M上為對象進行初始化

c、將內(nèi)存M的地址復(fù)制給singleton變量

如下圖:

因為將內(nèi)存的地址賦值給singleton變量是最后一步,所以Thread1在這一步驟執(zhí)行之前,Thread2在對singleton==null進行判斷一直都是true的,那么他會一直阻塞,直到Thread1將這一步驟執(zhí)行完。

但是,問題就出在以上過程并不是一個原子操作,并且編譯器可能會進行重排序,如果以上步驟被重排成:

  • a、JVM為對象分配一塊內(nèi)存M
  • c、將內(nèi)存的地址復(fù)制給singleton變量
  • b、在內(nèi)存M上為對象進行初始化

如下圖:

這樣的話,Thread1會先執(zhí)行內(nèi)存分配,在執(zhí)行變量賦值,最后執(zhí)行對象的初始化,那么,也就是說,在Thread1還沒有為對象進行初始化的時候,Thread2進來判斷singleton==null就可能提前得到一個false,則會返回一個不完整的sigleton對象,因為他還未完成初始化操作。

這種情況一旦發(fā)生,我們拿到了一個不完整的singleton對象,當嘗試使用這個對象的時候就極有可能發(fā)生NPE異常。

那么,怎么解決這個問題呢?因為指令重排導(dǎo)致了這個問題,那就避免指令重排就行了。

所以,volatile就派上用場了,因為volatile可以避免指令重排。只要將代碼改成以下代碼,就可以解決這個問題:

 
 
 
 
  1. public class Singleton {   
  2.      private volatile static Singleton singleton;   
  3.       private Singleton (){}   
  4.       public static Singleton getSingleton() {   
  5.       if (singleton == null) {   
  6.           synchronized (Singleton.class) {   
  7.               if (singleton == null) {   
  8.                   singleton = new Singleton();   
  9.               }   
  10.            }   
  11.        }   
  12.        return singleton;   
  13.        }   
  14.    }   

對singleton使用volatile約束,保證他的初始化過程不會被指令重排。這樣就可以保Thread2 要不然就是拿不到對象,要不然就是拿到一個完整的對象。

3.synchronized的有序性保證呢?

看到這里可能有朋友會問了,說到底上面問題是發(fā)生了指令重排,其實還是個有序性的問題,不是說synchronized是可以保證有序性的么,這里為什么就不行了呢?

首先,可以明確的一點是:synchronized是無法禁止指令重排和處理器優(yōu)化的。那么他是如何保證的有序性呢?

這就要再把有序性的概念擴展一下了。Java程序中天然的有序性可以總結(jié)為一句話:如果在本線程內(nèi)觀察,所有操作都是天然有序的。如果在一個線程中觀察另一個線程,所有操作都是無序的。

以上這句話也是《深入理解Java虛擬機》中的原句,但是怎么理解呢?周志明并沒有詳細的解釋。這里我簡單擴展一下,這其實和as-if-serial語義有關(guān)。

as-if-serial語義的意思指:不管怎么重排序,單線程程序的執(zhí)行結(jié)果都不能被改變。編譯器和處理器無論如何優(yōu)化,都必須遵守as-if-serial語義。

這里不對as-if-serial語義詳細展開了,簡單說就是,as-if-serial語義保證了單線程中,不管指令怎么重排,最終的執(zhí)行結(jié)果是不能被改變的。

那么,我們回到剛剛那個雙重校驗鎖的例子,站在單線程的角度,也就是只看Thread1的話,因為編譯器會遵守as-if-serial語義,所以這種優(yōu)化不會有任何問題,對于這個線程的執(zhí)行結(jié)果也不會有任何影響。

但是,Thread1內(nèi)部的指令重排卻對Thread2產(chǎn)生了影響。

那么,我們可以說,synchronized保證的有序性是多個線程之間的有序性,即被加鎖的內(nèi)容要按照順序被多個線程執(zhí)行。但是其內(nèi)部的同步代碼還是會發(fā)生重排序,只不過由于編譯器和處理器都遵循as-if-serial語義,所以我們可以認為這些重排序在單線程內(nèi)部可忽略。

4.總結(jié)

本文從兩方面論述了volatile的重要性以及不可替代性:

一方面是因為synchronized是一種鎖機制,存在阻塞問題和性能問題,而volatile并不是鎖,所以不存在阻塞和性能問題。

另外一方面,因為volatile借助了內(nèi)存屏障來幫助其解決可見性和有序性問題,而內(nèi)存屏障的使用還為其帶來了一個禁止指令重排的附件功能,所以在有些場景中是可以避免發(fā)生指令重排的問題的。

所以,在日后需要做并發(fā)控制的時候,如果不涉及到原子性的問題,可以優(yōu)先考慮使用volatile關(guān)鍵字。

【本文是專欄作者Hollis的原創(chuàng)文章,作者微信公眾號Hollis(ID:hollischuang)】


分享名稱:既生Synchronized,何生Volatile?!
標題來源:http://m.5511xx.com/article/dhegdjp.html