背景:聽說Volatile Java高階語法亦是挺進BAT的必經之路。 Volatile: volatile同步機制又涉及Java記憶體模型中的可見性、原子性和有序性,惡補基礎一波。 可見性: 可見性簡單的說是線程之間的可見性,一個線程修改的狀態對另一個線程是可見對,也就是一個線程的修改結果另一個線程 ...
背景:聽說Volatile Java高階語法亦是挺進BAT的必經之路。
Volatile:
volatile同步機制又涉及Java記憶體模型中的可見性、原子性和有序性,惡補基礎一波。
可見性:
可見性簡單的說是線程之間的可見性,一個線程修改的狀態對另一個線程是可見對,也就是一個線程的修改結果另一個線程可以馬上看到;但通常,我們無法確保執行讀操作的線程能夠時時的看到其他線程寫入的值,So為了確保多個線程之間對記憶體寫入操作可見性,必須使用同步機制;如用volatile修飾的變數就具有可見性,volatile修飾的變數不允許線程內部緩存和重排序,而是直接修改記憶體,所以對其他線程來說是可見的;但volatile只能保證被修飾的內容具有可見性,並不具備原子性,如volatile int vipNumber = 100,之後有一個vipNumber++ 的操作,這個變數vipNumber具有可見性,但是vipNumber++ 依然是一個非原子操作,也就是說這個操作同樣存線上程安全問題。
原子性:
原子具有不可分割的特性,如int age = 22,這個操作是不可分割的,那麼稱其為原子操作,具有原子性;再比如age++,這個操作實際是age = age + 1,其是可分割的,So它不是一個原子操作;而非原子操作都會存線上程安全問題,需要我們使用同步技術(synchronized)來讓它變成一個原子操作;Java的concurrent包下提供了一些原子類,如:AtomicLongMap、AtomicDouble、AtomicReference 等;在Java中用synchronized、lock和unlock來保證原子性。
有序性:
Java語言提供了volatile和synchronized兩個關鍵字來保證線程之間操作的有序性,volatile是因為本身包含“禁止指令重排序”的語義,synchronized是由“一個變數在同一時刻只允許一條線程對其進行lock操作”這條規則獲得有序性的,此規則決定了持有同一個對象鎖的兩個同步塊只能串列執行。
Volatile原理:
volatile是一種稍弱的同步機制,其用來確保將變數的更新操作通知到其他線程;當變數聲明為volatile類型後,編譯器與JVM運行時都會註意到這個變數時共用的,因此不會將此變數上的操作與其他操作一起重排序;volatile修飾後變數不會緩存在寄存器或者對其他處理器不可見的地方,因此在單曲volatile類型的變數時總會返回最新寫入的值;除此之外,在訪問volatile變數時不會執行加鎖操作,也就不會執行線程阻塞,因此volatile變數是一種比synchronized關鍵字更輕量級的同步機制;當對非volatile變數進行讀寫時,每個線程從記憶體拷貝變數到CPU緩存中,如果電腦有多個CPU則每個線程可能在不同的CPU上被處理,這就意味著每個線程都可以拷貝到不同的CPU緩存cache中,而不是像volatile變數那樣直接讀記憶體,JVM保證其每次讀變數都從記憶體中讀,跳過了CPU cache這一步驟。
當一個變數定義為volatile之後,其具備的兩種特征:
1、保證此變數對所有的線程的可見性;當一個線程修改了此變數的值,volatle保證新值能夠立即同步到主記憶體,以及每次使用前立即從主每次刷新;
2、禁止指令重排序優化;被volatile修飾的變數賦值後多執行了一個“load”操作,此操作相當於一個記憶體屏障(指令重排序時不能把後面的指令重排序到記憶體屏障之前到位置),只有一個CPU訪問記憶體時,不需要記憶體屏障;(指令重排序:指CPU採用了允許將多條指令不按程式規定的順序分開發送給各相應電路單元處理)
另外:在性能方面,volatile的讀操作性能消耗與普通變數基本無異,但是寫操作稍慢,因為它需要在本地代碼中插入許多記憶體屏障來保證處理器不發生亂序執行。
這裡擼了一個例子用volatile保證線程間的同步,如果變數author不經volatile修飾,線程2中對author的值做了修改並未同步到線程1中,其一直存在緩存中。
FYI:
1 import lombok.extern.slf4j.Slf4j;
2
3 @Slf4j
4 public class TestVolatile {
5 //private volatile String author = "tjt"; // volatile修飾author保證兩個線程到可見性,即不存在緩存cache中
6 private String author = "tjt"; // 不用volatile修飾變數author則author修改值後線上程之間並不可見
7 private boolean enable = false;
8 public static void main(String[] args) throws Exception {
9 TestVolatile testVolatile = new TestVolatile();
10 log.info("原始定義的author: "+testVolatile.author);
11 Thread thread = new Thread( new Runnable() {
12 @Override
13 public void run() {
14 testVolatile.testMethodOne();
15 }
16 });
17 thread.start();
18 thread.sleep(2000l);
19 testVolatile.testMethodTwo();
20 }
21 public void testMethodOne() {
22 while(true) {
23 if ("detect_tjt".equals(author) && enable == false) {
24 log.info("線程testMethodOne中檢測到來author修改為: "+author);
25 enable = true;
26 System.exit(0);
27 }
28 } }
29 public void testMethodTwo() {
30 author = "detect_tjt";
31 log.info("線程testMethodTwo中把author修改為: "+author);
32 }
33 }
用volatile修飾author執行結果:
- 原始定義的author: tjt
- 線程testMethodTwo中把author修改為: detect_tjt
- 線程testMethodOne中檢測到來author修改為: detect_tjt
無volatile修飾author執行結果:
- 原始定義的author: tjt
- 線程testMethodTwo中把author修改為: detect_tjt