一文讀懂a(chǎn)va中的Volatile關(guān)鍵字使用
在本文中,我們會介紹java中的一個關(guān)鍵字volatile。 volatile的中文意思是易揮發(fā)的,不穩(wěn)定的。那么在java中使用是什么意思呢?
我們知道,在java中,每個線程都會有個自己的內(nèi)存空間,我們稱之為working memory。這個空間會緩存一些變量的信息,從而提升程序的性能。當(dāng)執(zhí)行完某個操作之后,thread會將更新后的變量更新到主緩存中,以供其他線程讀寫。
因?yàn)樽兞看嬖趙orking memory和main memory兩個地方,那么就有可能出現(xiàn)不一致的情況。 那么我們就可以使用Volatile關(guān)鍵字來強(qiáng)制將變量直接寫到main memory,從而保證了不同線程讀寫到的是同一個變量。
什么時候使用volatile
那么我們什么時候使用volatile呢?當(dāng)一個線程需要立刻讀取到另外一個線程修改的變量值的時候,我們就可以使用volatile。我們來舉個例子:
public class VolatileWithoutUsage { private int count = 0; public void incrementCount() { count++; } public int getCount() { return count; } }
這個類定義了一個incrementCount()方法,會去更新count值,我們接下來在多線程環(huán)境中去測試這個方法:
@Test public void testWithoutVolatile() throws InterruptedException { ExecutorService service= Executors.newFixedThreadPool(3); VolatileWithoutUsage volatileWithoutUsage=new VolatileWithoutUsage(); IntStream.range(0,1000).forEach(count ->service.submit(volatileWithoutUsage::incrementCount) ); service.shutdown(); service.awaitTermination(1000, TimeUnit.MILLISECONDS); assertEquals(1000,volatileWithoutUsage.getCount() ); }
運(yùn)行一下,我們會發(fā)現(xiàn)結(jié)果是不等于1000的。
java.lang.AssertionError:
Expected :1000
Actual :999
這是因?yàn)槎嗑€程去更新同一個變量,我們在上篇文章也提到了,這種情況可以通過加Synchronized關(guān)鍵字來解決。
那么是不是我們加上Volatile關(guān)鍵字后就可以解決這個問題了呢?
public class VolatileFalseUsage { private volatile int count = 0; public void incrementCount() { count++; } public int getCount() { return count; } }
上面的類中,我們加上了關(guān)鍵字Volatile,我們再測試一下:
@Test public void testWithVolatileFalseUsage() throws InterruptedException { ExecutorService service= Executors.newFixedThreadPool(3); VolatileFalseUsage volatileFalseUsage=new VolatileFalseUsage(); IntStream.range(0,1000).forEach(count ->service.submit(volatileFalseUsage::incrementCount) ); service.shutdown(); service.awaitTermination(5000, TimeUnit.MILLISECONDS); assertEquals(1000,volatileFalseUsage.getCount() ); }
運(yùn)行一下,我們會發(fā)現(xiàn)結(jié)果還是錯誤的:
java.lang.AssertionError:
Expected :1000
Actual :992
~~
為什么呢? 我們先來看下count++的操作,count++可以分解為三步操作,1. 讀取count的值,2.給count加1, 3.將count寫回內(nèi)存。添加Volatile關(guān)鍵詞只能夠保證count的變化立馬可見,而不能保證1,2,3這三個步驟的總體原子性。 要實(shí)現(xiàn)總體的原子性還是需要用到類似Synchronized的關(guān)鍵字。
下面看下正確的用法:
public class VolatileTrueUsage { private volatile int count = 0; public void setCount(int number) { count=number; } public int getCount() { return count; } } @Test public void testWithVolatileTrueUsage() throws InterruptedException { VolatileTrueUsage volatileTrueUsage=new VolatileTrueUsage(); Thread threadA = new Thread(()->volatileTrueUsage.setCount(10)); threadA.start(); Thread.sleep(100); Thread reader = new Thread(() -> { int valueReadByThread = volatileTrueUsage.getCount(); assertEquals(10, valueReadByThread); }); reader.start(); } ## Happens-Before
從java5之后,volatile提供了一個Happens-Before的功能。Happens-Before 是指當(dāng)volatile進(jìn)行寫回主內(nèi)存的操作時,會將之前的非volatile的操作一并寫回主內(nèi)存。
public class VolatileHappenBeforeUsage { int a = 0; volatile boolean flag = false; public void writer() { a = 1; // 1 線程A修改共享變量 flag = true; // 2 線程A寫volatile變量 } }
上面的例子中,a是一個非volatile變量,flag是一個volatile變量,但是由于happens-before的特性,a 將會表現(xiàn)的和volatile一樣。
本文的例子可以參考
[https://github.com/ddean2009/learn-java-concurrency/tree/master/volatile](https://github.com/ddean2009/learn-java-concurrency/tree/master/volatile)
總結(jié)
到此這篇關(guān)于一文讀懂a(chǎn)va中的Volatile關(guān)鍵字使用的文章就介紹到這了,更多相關(guān)java volatile關(guān)鍵字內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java 數(shù)據(jù)類型及類型轉(zhuǎn)換的互相轉(zhuǎn)換實(shí)例代碼
這篇文章主要介紹了Java 數(shù)據(jù)類型及類型轉(zhuǎn)換的互相轉(zhuǎn)換實(shí)例代碼,需要的朋友可以參考下2020-10-10SpringBoot的HandlerInterceptor中依賴注入為null問題
這篇文章主要介紹了SpringBoot的HandlerInterceptor中依賴注入為null問題,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-09-09SpringBoot ApplicationContextAware拓展接口使用詳解
當(dāng)一個類實(shí)現(xiàn)了這個接口(ApplicationContextAware)之后,這個類就可以方便獲得ApplicationContext中的所有bean。換句話說,就是這個類可以直接獲取spring配置文件中,所有有引用到的bean對象2023-04-04一步步教你把SpringBoot項(xiàng)目打包成Docker鏡像
Docker可以讓開發(fā)者打包他們的應(yīng)用以及依賴包到一個輕量級、可移植的容器中,然后發(fā)布到任何流行的 Linux 機(jī)器上,也可以實(shí)現(xiàn)虛擬化,下面這篇文章主要給大家介紹了關(guān)于SpringBoot項(xiàng)目打包成Docker鏡像的相關(guān)資料,需要的朋友可以參考下2023-02-02java利用Future實(shí)現(xiàn)多線程執(zhí)行與結(jié)果聚合實(shí)例代碼
這篇文章主要給大家介紹了關(guān)于java利用Future實(shí)現(xiàn)多線程執(zhí)行與結(jié)果聚合的相關(guān)資料,Future模式的核心,去除了主函數(shù)的等待時間,并使得原本需要等待的時間段可以用于處理其他業(yè)務(wù)邏輯,需要的朋友可以參考下2021-12-12Idea中maven項(xiàng)目實(shí)現(xiàn)登錄驗(yàn)證碼功能
這篇文章主要介紹了Idea中maven項(xiàng)目實(shí)現(xiàn)登錄驗(yàn)證碼功能,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-12-12