線程中volatile實際場景應用(代碼示例)不看你會後悔的

2021-01-07 老徐聊java

前言:上一篇文章主要介紹了volatile的概念以及使用場景,但是沒有具體展示代碼示例,所謂不見真相就是不信嘛,或者看了概念也不是很理解,看下代碼可能就讓你豁然開朗了。

例子1、一次性標誌,先看下沒有使用volatile會出現什麼問題

未使用volatile

發現即使關閉任務了,但是線程1還是執行的。這是由於不採取任務措施的情況下,共享變量的修改對其他線程未必是可見的造成的,那麼加上volatile呢?

加上volatile

可以看得出,任務結束了,使用一個布爾變量來標記狀態,用於指示發生了一件重要的一次性事件。只有一次性的狀態轉換。上面的代碼中,狀態標誌位只是從false轉換為true,並沒有繼續進行從true到false的轉換等。這種轉換的一次性杜絕了有序性問題的產生。

例子2、單例模式下的雙重檢查

單例模式

這個真是太難模擬出來了,執行了好多次,都沒有復現,大家記住加volatile吧。

單例加上volatile

既然沒有復現出來,那就口頭說下出現問題的原因吧。主要在於instance = new TestThread()這句,這並非是一個原子操作,事實上在 JVM 中這句話大概做了下面 3 件事情:

1.給 instance 分配內存;

2.調用 TestThread的構造函數來初始化成員變量;

3.將instance對象指向分配的內存空間(執行完這步 instance 就為非 null 了)。

但是在 JVM 的即時編譯器中存在指令重排序的優化。也就是說上面的第二步和第三步的順序是不能保證的,最終的執行順序可能是 1-2-3 也可能是 1-3-2。如果是後者,則在 3 執行完畢、2 未執行之前,被線程二搶佔了,這時 instance 已經是非 null 了(但卻沒有初始化),所以線程二會直接返回instance,然後使用,然後順理成章地報錯。

例子3、獨立觀察模式(比如查看最新的值,比如溫度、最後登錄用戶點(不保證原子性))

獨立觀察
結果

例子4、開銷較低的「讀-寫鎖」策略

如果讀操作遠遠超過寫操作,您可以結合使用內部鎖和 volatile 變量來減少公共代碼路徑的開銷。

如下顯示的線程安全的計數器,使用 synchronized 確保增量操作是原子的,並使用 volatile 保證當前結果的可見性。如果更新不頻繁的話,該方法可實現更好的性能,因為讀路徑的開銷僅僅涉及 volatile 讀操作,這通常要優於一個無競爭的鎖獲取的開銷。

讀寫操作

例子5、volatile bean 模式

bean

volatile bean 模式的基本原理是:很多框架為易變數據的持有者(例如 HttpSession)提供了容器,但是放入這些容器中的對象必須是線程安全的。

在 volatile bean 模式中,JavaBean 的所有數據成員都是 volatile 類型的,並且 getter 和 setter 方法必須非常普通——即不包含約束。(這種模式小編在實際中沒用過,不多說,用過的人可以給我講講哈)。

總結:與鎖相比,volatile 變量是一種非常簡單但同時又非常脆弱的同步機制,它在某些情況下將提供優於鎖的性能和伸縮性。如果嚴格遵循 volatile 的使用條件 —— 即變量真正獨立於其他變量和自己以前的值 —— 在某些情況下可以使用 volatile 代替 synchronized 來簡化代碼。然而,使用 volatile 的代碼往往比使用鎖的代碼更加容易出錯。遵循這些模式(注意使用時不要超過各自的限制)可以幫助您安全地實現大多數用例,使用 volatile 變量獲得更佳性能。

相關焦點

  • Java 多線程 —— 深入理解 volatile 的原理以及應用
    推薦閱讀:《java 多線程—線程怎麼來的》這一篇主要講解一下volatile的原理以及應用,想必看完這一篇之後,你會對volatile的應用原理以及使用邊界會有更深刻的認知。本篇主要內容:volatile 讀寫同步原理volatile重排序原則volatile應用關鍵字volatile是jvm提供的輕量級的同步機制,但它並不容易理解,而且在多數情況下用不到,被多數開發者拋棄並採用synchronized代替,synchronized屬於重度鎖,如果你對性能有高的要求,那麼同等情況下,變量聲明volatile會減小更少的同步開銷
  • 單片機中volatile定義的作用
    一個中斷服務子程序中會訪問到的非自動變量(Non-automatic variables) 3). 多線程應用中被幾個任務共享的變量 回答不出這個問題的人是不會被僱傭的。我認為這是區分C程式設計師和嵌入式系統程式設計師的最基本的問題。嵌入式系統程式設計師經常同硬體、中斷、RTOS等等打交道,所用這些都要求volatile變量。不懂得volatile內容將會帶來災難。
  • Java中的volatile變量適合於什麼場景下應用
    要想知道在什麼場景下用首先需要知道volatile是什麼,它表示可見性,是指線程之間的可見性,一個線程修改的狀態對另一個線程是可見的。也就是一個線程修改的結果。另一個線程馬上就能看到。為什麼說一個線程修改後結果另外一個線程是可見的,因為volatile修飾的變量不允許線程內部緩存和重排序,即直接修改內存。所以對其他線程是可見的,但是這裡需要注意:volatile修飾內容具有可見性,但不能保證它具有原子性。比如我們對i進行加1操作
  • 從零開始了解多線程知識之開始篇目——jvm&volatile
    這也就是為什麼我們經常說線程上下文切換會涉及到用戶態到內核態的切換原因所在用戶線程:指不需要內核支持而在用戶程序中實現的線程,其不依賴於作業系統核心,應用進程利用線程庫提供創建、同步、調度和管理線程的函數來控制用戶線程。另外,用戶線程是由應用進程利用線程庫創建和管理,不依賴於作業系統核心。不需要用戶態/核心態切換,速度快。
  • Java Volatile關鍵字分析(二)
    下面是沒有使用Volatile的代碼示例 如上代碼所示定義的counter共享計數器對象,並沒有使用volatile ,synchronize這樣的關鍵,在對共享對象進行多線程讀寫的情況下,是無法保證一致性性和準確性的。因為在讀寫操作時,先是對本地緩存進行操作的。如下圖所示:
  • 1分鐘讀懂java中的volatile關鍵字
    volatile作為java中的關鍵詞之一,用以聲明變量的值可能隨時會別的線程修改,使用volatile修飾的變量會強制將修改的值立即寫入主存,主存中值的更新會使緩存中的值失效(非volatile變量不具備這樣的特性,非volatile變量的值會被緩存,線程A更新了這個值,線程B讀取這個變量的值時可能讀到的並不是是線程A更新後的值)。
  • Java內存模型與volatile關鍵字
    多線程不安全這種模式就會出現多線程不安全問題,如果兩個線程同時操作數據,如下圖:兩個線程同時對主內存中變量進行操作,他們都會把變量先加載到自己的工作內存中去,然後對他進行操作,如果並發執行,線程2就會覆蓋線程1的操作。
  • 你應該要理解的java並發關鍵字volatile
    我們可以在這裡簡單的說一下:1、原子性(Atomicity)原子性是指在一個操作中就是cpu不可以在中途暫停然後再調度,既不被中斷操作,要不執行完成,要不就不執行,就好比你做一件事,要麼不做,要麼做完。java提供了很多機制來保證原子性。我們舉一個例子,比如說常見的a++就不滿足原子性。
  • Java開發多線程是如何解決安全問題的?
    每個線程讀取共享變量時,都會將該變量加載進其對應CPU的高速緩存裡,修改該變量後,CPU會立即更新該緩存,但並不一定會立即將其寫回主內存(實際上寫回主內存的時間不可預期)。此時其它線程(尤其是不在同一個CPU上執行的線程)訪問該變量時,從主內存中讀到的就是舊的數據,而非第一個線程更新後的數據。
  • Java並發之volatile關鍵字內存可見性問題
    Java並發之volatile關鍵字內存可見性問題線程之間數據共享案例我們先來看一個場景:Main函數啟動後,調用一個線程向list中添加數據。flag=false複製到自己程序內存中,在執行自己內部代碼後,修改了flag的值,回寫到主內存中(相對於程序自己的內存來說,內存中的數據是主內存。
  • Java中volatile特性概述
    禁止指令重排序:volatile可以防止指令重排序操作。volatile不保證原子性所謂的原子性是指在一次操作或者多次操作中,要麼所有的操作全部都得到了執行並且不會受到任何因素的幹擾而中 斷,要麼所有的操作都不執行。volatile不保證原子性。
  • 分享牛人解釋的volatile關鍵字
    多線程應用中被幾個任務共享的變量回答不出這個問題的人是不會被僱傭的。我認為這是區分C程式設計師和嵌入式系統程式設計師的最基本的問題。嵌入式系統程式設計師經常同硬體、中斷、RTOS等等打交道,所用這些都要求volatile變量。不懂得volatile內容將會帶來災難。
  • 我說:volatile 是輕量級的 synchronized,面試官讓我回去等通知
    volatile 是並發編程的重要組成部分,也是面試常被問到的問題之一。不要向小強那樣,因為一句:volatile 是輕量級的 synchronized,而與期望已久的大廠失之交臂。volatile 有兩大特性:保證內存的可見性和禁止指令重排序。那什麼是可見性和指令重排呢?接下來我們一起來看。
  • 教學筆記:這樣來學習Java volatile關鍵字
    stop, 然後主線程啟動一個新線程,在線程裡不停得增加計數器i的值,直到主線程的布爾變量stop被主線程置為true才結束循環。主線程用Thread.sleep停頓1秒後將布爾值stop置為true。因此,我們期望的結果是,上述Java代碼執行1秒鐘後停止,並且列印出1秒鐘內計數器i的實際值。
  • volatile關鍵字詳解
    的三個特點保證線程之間的可見性禁止指令重排不保證原子性可見性概念可見性是多線程場景中才討論的,它表示多線程環境中,當一個線程修改了共享變量的值,其他線程能夠知道這個修改。>如上述代碼,如果不加volatile,程序運行結果如下加上volatile關鍵字後,程序運行結果如下
  • 為什麼要理解volatile關鍵字,才能夠明白線程操作的意義?
    但是對於volatile關鍵字,尤其在初學者開發時經常被誤解,認為「volatile變量對所有線程是立即可見性的,對volatile變量所有的寫操作都能立刻反映到其他線程之中。也就是說volatile變量在各個線程中是一致的,所以基於volatile變量的運算在並發下是線程安全的」,這句話論據部分並沒有錯。
  • 跟我學Java編程—sleep方法在線程同步中的具體應用場景
    sleep方法是Thread類的一個靜態方法,該方法可以把當前正在運行的線程掛起一段時間(時間值由參數傳入),掛起時間到期後,JVM會在適當的時間再次喚醒該線程。先看一個例子代碼:MyRunner類代碼:在SleepDemo類主線程中,啟動子線程後,應用Thread類的sleep方法將主線程掛起1000毫秒,因為sleep方法拋出InterruptedException異常,因此調用sleep方法時,需要包含在try-cahtch
  • volatile你以為你真的懂?
    我們來看上面的代碼,不加Volatile結果如下:程序一直在運行,死循環中,我們給running加上Volatile,在看運行結果:程序不會陷入死循環,在主線程修改了running的值以後,程序結束運行,那麼為什麼會這樣呢?
  • 深度解析volatile關鍵字,就是這麼簡單
    本文章講解的內容是深入了解volatile關鍵字,建議對著示例項目閱讀文章,示例項目連結如下:https://github.com/TanJiaJunBeyond/VolatileDemo查看彙編代碼的hsdis-amd64.dylib文件連結如下:https://github.com/TanJiaJunBeyond/VolatileDemo/blob/master/hsdis-amd64
  • Java編寫線程安全類的7個技巧
    幾乎每個Java應用程式都會用到線程。例如,Tomcat是在單獨的工作線程中處理每個請求,胖客戶機(Fat Client)在專用工作線程中處理長時間運行的請求。本文將跟你一起探討如何以線程安全的方式來編寫類。