Java線程安全之volatile詳解
volatile
volatile 的存在,解決了不同內(nèi)存間拷貝的同步問(wèn)題,在每一次使用或者修改時(shí)候,都去原持有內(nèi)存中去拿最新的狀態(tài)
或者說(shuō)可以這樣理解,volatile 的修飾讓線程放棄了使用各自?xún)?nèi)存的做法轉(zhuǎn)而使用共享內(nèi)存,從而保證了可靠性,但是降低了效率
所以說(shuō)我們只在需要不同線程訪問(wèn)同一個(gè)值的時(shí)候才去打開(kāi)這個(gè)限制
第一種情況
首先我們來(lái)寫(xiě)一個(gè)測(cè)試代碼
public class Synchronized1Test { private boolean running = true; public void runTest() { new Thread() { @Override public void run() { while (running) { System.out.println("線程開(kāi)始執(zhí)行,,,,"); } } }.start(); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } stop(); } private void stop() { running = false; } }
代碼大致要表達(dá)的是,主線程在一秒之后要調(diào)用 stop 方法將 running = false,這樣,子線程的死循環(huán)就會(huì)被打破,這樣子線程停止執(zhí)行,主線程也執(zhí)行完畢,整個(gè)程序結(jié)束。
但是實(shí)際的執(zhí)行結(jié)果是,子線程的死循環(huán)并不會(huì)結(jié)束,會(huì)一直不停的執(zhí)行下去
這是因?yàn)榫€程直接做變量的值的修改并不是直接改的,而是先拷貝一份到自己線程的內(nèi)存區(qū)域,更改完之后再適時(shí)同步給原持有此值的線程,這樣做的目的是提高程序的運(yùn)行效率
而在這里,主線程持有此變量,子線程在執(zhí)行時(shí)候,將值拷貝了一份到自己的內(nèi)存中,然后子線程并沒(méi)有做值的修改,并不存在將值同步給主線程的過(guò)程,而此值的持有線程是主線程,主線程在更改完此值之后只會(huì)同步給自己,并不會(huì)同步給子線程,所以說(shuō)子線程的值永遠(yuǎn)為最初拷貝走的值,永遠(yuǎn)為true,就永遠(yuǎn)不會(huì)停下來(lái)了。
有一個(gè)關(guān)鍵字 volatile 能解決這個(gè)問(wèn)題
public class Synchronized1Test { private volatile boolean running = true; ..... }
volatile 的存在,解決了不同內(nèi)存間拷貝的同步問(wèn)題,在每一次使用或者修改時(shí)候,都去原持有內(nèi)存中去拿最新的狀態(tài),或者說(shuō)可以這樣理解,volatile 的修飾讓線程放棄了使用各自?xún)?nèi)存的做法轉(zhuǎn)而使用共享內(nèi)存,從而保證了可靠性,但是降低了效率,所以說(shuō)我們只在需要不同線程訪問(wèn)同一個(gè)值的時(shí)候才去打開(kāi)這個(gè)限制
再次運(yùn)行,果然一秒結(jié)束
第二種情況
public class Synchronized2Test { private int x = 0; private void count() { x++; } public void runTest() { new Thread() { @Override public void run() { for (int i = 0; i < 1_000_000; i++) { count(); } System.out.println("x 在線程1的最終值" + x); } }.start(); new Thread() { @Override public void run() { for (int i = 0; i < 1_000_000; i++) { count(); } System.out.println("x 在線程2的最終值" + x); } }.start(); } }
從代碼來(lái)看,兩個(gè)線程分別對(duì)同一個(gè)數(shù)進(jìn)行一百萬(wàn)次從自增操作,無(wú)論誰(shuí)最后執(zhí)行完成,理論上來(lái)講,總有一個(gè)線程打印出來(lái)的結(jié)果是兩百萬(wàn),但實(shí)際的運(yùn)行效果,無(wú)論執(zhí)行多少次都沒(méi)有兩百萬(wàn)的結(jié)果出來(lái)。
都是因?yàn)樯衔恼f(shuō)的,值的同步性問(wèn)題,在各自?xún)?nèi)存區(qū)域修改完后適時(shí)同步回去,導(dǎo)致的這個(gè)問(wèn)題
然后我們加上 volatile 關(guān)鍵字
public class Synchronized2Test { private volatile int x = 0; ..... }
運(yùn)行,還是不行,為啥啊,都加上了怎么還是不行呢?
這里主要的原因是因?yàn)?x++; 這個(gè)操作
在 Java 中, x++; 是兩步操作,不是原子操作,是可拆的操作
他的運(yùn)算過(guò)程大致相當(dāng)于是
int temp = x + 1; x = temp;
這樣,在代碼分為兩行執(zhí)行的時(shí)候,搶線程的操作就發(fā)生了,就出現(xiàn)了值異常的情況
到目前為止,線程安全除了加 volatile 關(guān)鍵字外,還需要保證會(huì)互相影響的操作合成一個(gè)原子操作
這里解釋一下原子操作,簡(jiǎn)單來(lái)說(shuō),把多個(gè)步驟看成一個(gè)整體,要么你別執(zhí)行我,要么就完全執(zhí)行,不要在執(zhí)行一半時(shí)候發(fā)生線程切換
這里引入我們保證一塊代碼塊執(zhí)行原子操作的關(guān)鍵字:synchronized
這時(shí)候我們改一下我們的代碼,給 count 方法增加 synchronized 關(guān)鍵字,并且可以將 volatile 關(guān)鍵字去除了
public class Synchronized2Test { private int x = 0; private synchronized void count() { x++; } ..... }
運(yùn)行,果然順利的打印出來(lái)了兩百萬(wàn),多次運(yùn)行也是這個(gè)結(jié)果
受保護(hù)的類(lèi)型
到此,我們講一下官方提供的原子型的類(lèi)型,這里拿之前出現(xiàn)過(guò)的 AtomicInteger 舉例
AtomicInteger 對(duì)標(biāo)的就是 int,他是對(duì) int 的包裝,可以保證 int 具有同步性和原子性
這里的同步性可以理解為上文提到的被 volatile 修飾,原子性可以理解為被 synchronized 修飾
例:
// 可以理解為默認(rèn)值為 0 的 int AtomicInteger count = new AtomicInteger(0); // 相當(dāng)于是 ++count count.incrementAndGet(); // 相當(dāng)于是 count++ count.getAndIncrement();
代碼中的 count.incrementAndGet() 相當(dāng)于是 ++count
count.getAndIncrement() 相當(dāng)于是 count++
除此之外還有
常規(guī)類(lèi)型 | 保護(hù)類(lèi)型 |
int | AtomicInteger |
boolean | AtomicBoolean |
int[] | AtomicIntegerArray |
T | AtomicReference< T> |
… | … |
不一一列舉
其中要說(shuō)的就是 AtomicReference,他把傳入的范型,自動(dòng)幫我們做成了保護(hù)類(lèi)型,使得在賦值和取值時(shí)候不出錯(cuò)
到此這篇關(guān)于Java線程安全之volatile詳解的文章就介紹到這了,更多相關(guān)Java的volatile內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
實(shí)例講解Java中動(dòng)態(tài)代理和反射機(jī)制
在本篇文章里小編給各位分享了關(guān)于Java中動(dòng)態(tài)代理和反射機(jī)制的相關(guān)知識(shí)點(diǎn)內(nèi)容,有需要的朋友們學(xué)習(xí)下。2019-01-01你知道在Java中Integer和int的這些區(qū)別嗎?
最近面試,突然被問(wèn)道,說(shuō)一下Integer和int的區(qū)別.額…可能平時(shí)就知道寫(xiě)一些業(yè)務(wù)代碼,包括面試的一些Spring源碼等,對(duì)于這種特別基礎(chǔ)的反而忽略了,導(dǎo)致面試的時(shí)候突然被問(wèn)到反而不知道怎么回答了.哎,還是乖乖再看看底層基礎(chǔ),順帶記錄一下把 ,需要的朋友可以參考下2021-06-06SpringBoot項(xiàng)目中集成Apollo的方法步驟
本文主要介紹了SpringBoot項(xiàng)目中集成Apollo的方法步驟,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-10-10詳解Java中實(shí)現(xiàn)SHA1與MD5加密算法的基本方法
這篇文章主要介紹了詳解Java中實(shí)現(xiàn)SHA1與MD5加密算法的基本方法,安全哈希算法第一版和消息摘要算法第五版也是通常人們最常用的加密算法,需要的朋友可以參考下2016-04-04Spring線程池ThreadPoolExecutor配置并且得到任務(wù)執(zhí)行的結(jié)果
今天小編就為大家分享一篇關(guān)于Spring線程池ThreadPoolExecutor配置并且得到任務(wù)執(zhí)行的結(jié)果,小編覺(jué)得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧2019-03-03SpringBoot和MybatisPlus實(shí)現(xiàn)通用Controller示例
本文主要介紹了SpringBoot和MybatisPlus實(shí)現(xiàn)通用Controller示例,只需創(chuàng)建實(shí)體類(lèi)和mapper接口,就可以實(shí)現(xiàn)單表的增刪改查操作,具有一定的參考價(jià)值,感興趣的可以了解一下2025-03-03SpringMVC實(shí)現(xiàn)文件上傳和下載的工具類(lèi)
這篇文章主要為大家詳細(xì)介紹了SpringMVC實(shí)現(xiàn)文件上傳和下載的工具類(lèi),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-05-05