你應該要理解的java併發關鍵字volatile

提高java的併發編程,就不得不提volatile關鍵字,不管是在面試還是實際開發中 volatile都是一個應該掌握的技能。他的重要性不言而喻。因此也有必要學好。

一、爲什麼要用到volatile關鍵字?

使用一個新技術的原因肯定是當前存在了很多問題,在Java多線程的開發中有三種特性:原子性、可見性和有序性。我們可以在這裏簡單的說一下:

1、原子性(Atomicity

原子性是指在一個操作中就是cpu不可以在中途暫停然後再調度,既不被中斷操作,要不執行完成,要不就不執行,就好比你做一件事,要麼不做,要麼做完。java提供了很多機制來保證原子性。我們舉一個例子,比如說常見的a++就不滿足原子性。這個操作實際是a = a + 1;是可分割的。在運行的時候可能做了一半不做了。所以不滿足原子性。

爲了解決上面a++出現的問題,java提供了很多其他的關鍵字和類,比如說AtomicIntegerAtomicLongAtomicReference等。

2、可見性(Visibility)

可見性就是指當一個線程修改了線程共享變量的值,其它線程能夠立即得知這個修改。如果我們學過java內存模型的話,對下面這張圖想必不陌生:

每一個線程都有一份自己的本地內存,所有線程共用一份主內存。如果一個線程對主內存中的數據進行了修改,而此時另外一個線程不知道是否已經發生了修改,就說此時是不可見的。

這種不可見的狀況會帶來一個問題,兩個線程有可能會操作同一份但是值不一樣的數據。這時候怎麼辦呢?於是乎,今天的主角登場了,這就是volatile關鍵字。

volatile關鍵字的作用很簡單,就是一個線程在對主內存的某一份數據進行更改時,改完之後會立刻刷新到主內存。並且會強制讓緩存了該變量的線程中的數據清空,必須從主內存重新讀取最新數據。這樣一來就保證了可見性

3、有序性

程序執行的順序按照代碼的先後順序執行就叫做有序性,但是有時候程序的執行並不會遵循,比如說下面的代碼:

int i = 0; int j = 2;

這兩行代碼很簡單,i=1,j=2,程序在運行的時候一定會先讓i=1,然後j=2嘛?不一定,爲什麼會不一定,這是因爲有可能會發生指令重排序,從名字看就知道,在運行的時候,代碼會重新排列。這裏面涉及到的就比較多了。我會在專門的文章中進行講解。

爲了防止上面的重排序,java依然提供了很多機制,比如volatile關鍵字等。這也是我們volatile關鍵字第二個使用的場景。

在上面我們從java併發編程的三個特徵來分析了爲什麼會用到volatile關鍵字,主要是保證內存可見性和防止指令重排序。下面我們就來正式來分析一下這個volatile

二、深入剖析

1volatile保證原子性嘛?

在上面我們只說了volatile關鍵字會保證可見性和有序性,但是並沒有說會不會保證原子性,原子性的概念我們已經說了,也就是一個操作,要麼不執行,要麼執行到底。我們可以使用代碼來驗證一下:

這段代碼的含義是,有5個線程,每一個線程都對a進行遞增。每個線程一次加10個數。按道理來講,如果volatile關鍵字保證原子性的話,最後結果一定是50。我們可以運行一下看看結果:

最後得出的結論就是volatile不保證原子性。既然不能保證原子性,那肯定就是非線程安全的。

2、單例模式的雙重鎖爲什麼要加volatile

什麼是雙重鎖的單例模式,我們給出代碼可以看看。

這就是單例模式的雙重鎖實現,爲什麼這裏要加volatile關鍵字呢?我們把test2 = new Test2()這行代碼進行拆分。可以分解爲3個步驟:

1memory=allocate();// 分配內存

2ctorInstanc(memory) //初始化對象

3test2=memory //設置s指向剛分配的地址

如果沒有volatile關鍵字,可能會發生指令重排序。在編譯器運行時,從1-2-3 排序爲1-3-2。此時兩個線程同時進來的時候出現可見性問題,也就是說一個線程執行了1-3,另外一個線程一進來直接返回還未執行2null對象。而我們的volatile關鍵之前已經說過了,可以很好地防止指令重排序。也就不會出現這個問題了。

如果我們學過java併發系列的其他類比如說Atomic等,通過源碼我們會發現volatile無處不在。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章