本篇文章給大家分享的是有關(guān)volatile和synchronized的區(qū)別是什么,小編覺得挺實用的,因此分享給大家學(xué)習(xí),希望大家閱讀完這篇文章后可以有所收獲,話不多說,跟著小編一起來看看吧。
成都創(chuàng)新互聯(lián)公司專注于企業(yè)網(wǎng)絡(luò)營銷推廣、網(wǎng)站重做改版、臨城網(wǎng)站定制設(shè)計、自適應(yīng)品牌網(wǎng)站建設(shè)、H5建站、商城網(wǎng)站建設(shè)、集團公司官網(wǎng)建設(shè)、外貿(mào)網(wǎng)站建設(shè)、高端網(wǎng)站制作、響應(yīng)式網(wǎng)頁設(shè)計等建站業(yè)務(wù),價格優(yōu)惠性價比高,為臨城等各大城市提供網(wǎng)站開發(fā)制作服務(wù)。
Java 內(nèi)存模型(JMM)
CPU 增加了緩存均衡了與內(nèi)存的速度差異,這一增加還是好幾層。
此時內(nèi)存的短板不再那么明顯,CPU甚喜。但隨之卻帶來很多問題
看上圖,每個核都有自己的一級緩存(L1 Cache),有的架構(gòu)里面還有所有核共用的二級緩存(L2 Cache)。使用緩存之后,當(dāng)線程要訪問共享變量時,如果 L1 中存在該共享變量,就不會再逐級訪問直至主內(nèi)存了。所以,通過這種方式,就補上了訪問內(nèi)存慢的短板
具體來說,線程讀/寫共享變量的步驟是這樣:
鴻蒙官方戰(zhàn)略合作共建——HarmonyOS技術(shù)社區(qū)
從主內(nèi)存復(fù)制共享變量到自己的工作內(nèi)存
在工作內(nèi)存中對變量進行處理
處理完后,將變量值更新回主內(nèi)存
假設(shè)現(xiàn)在主內(nèi)存中有共享變量 X, 其初始值為 0
線程1先訪問變量 X, 套用上面的步驟就是這樣:
鴻蒙官方戰(zhàn)略合作共建——HarmonyOS技術(shù)社區(qū)
L1 和 L2 中都沒有發(fā)現(xiàn)變量 X,直到在主內(nèi)存中找到
拷貝變量 X 到 L1 和 L2 中
在 L1 中將 X 的值修改為1,并逐層寫回到主內(nèi)存中
此時,在線程 1 眼中,X 的值是這樣的:
接下來,線程 2 同樣按照上面的步驟訪問變量 X
鴻蒙官方戰(zhàn)略合作共建——HarmonyOS技術(shù)社區(qū)
L1 中沒有發(fā)現(xiàn)變量 X
L2 中發(fā)現(xiàn)了變量X
從L2中拷貝變量到L1中
在L1中將X 的值修改為2,并逐層寫回到主內(nèi)存中
此時,線程 2 眼中,X 的值是這樣的:
結(jié)合剛剛的兩次操作,當(dāng)線程1再訪問變量x,我們看看有什么問題:
此刻,如果線程 1 再次將 x=1回寫,就會覆蓋線程2 x=2 的結(jié)果,同樣的共享變量,線程拿到的結(jié)果卻不一樣(線程1眼中x=1;線程2眼中x=2),這就是共享變量內(nèi)存不可見的問題。
怎么補坑呢?今天的兩位主角閃亮登場,不過在說明 volatile關(guān)鍵字之前,我們先來說說你最熟悉的 synchronized 關(guān)鍵字
synchronized
遇到線程不安全的問題,習(xí)慣性的會想到用 synchronized 關(guān)鍵字來解決問題,暫且先不論該辦法是否合理,我們來看 synchronized 關(guān)鍵字是怎么解決上面提到的共享變量內(nèi)存可見性問題的
【進入】synchronized 塊的內(nèi)存語義是把在 synchronized 塊內(nèi)使用的變量從線程的工作內(nèi)存中清除,從主內(nèi)存中讀取
【退出】synchronized 塊的內(nèi)存語義事把在 synchronized 塊內(nèi)對共享變量的修改刷新到主內(nèi)存中
二話不說,無情向下看 volatile
volatile
當(dāng)一個變量被聲明為 volatile 時:
線程在【讀取】共享變量時,會先清空本地內(nèi)存變量值,再從主內(nèi)存獲取最新值
線程在【寫入】共享變量時,不會把值緩存在寄存器或其他地方(就是剛剛說的所謂的「工作內(nèi)存」),而是會把值刷新回主內(nèi)存
有種換湯不換藥的感覺,你看的一點都沒錯
所以,當(dāng)使用 synchronized 或 volatile 后,多線程操作共享變量的步驟就變成了這樣:
簡單點來說就是不再參考 L1 和 L2 中共享變量的值,而是直接訪問主內(nèi)存
來點踏實的,上例子
public class ThreadNotSafeInteger { /** * 共享變量 value */ private int value; public int getValue() { return value; } public void setValue(int value) { this.value = value; } }
經(jīng)過前序分析鋪墊,很明顯,上面代碼中,共享變量 value 存在大大的隱患,嘗試對其作出一些改變
先使用 volatile 關(guān)鍵字改造:
public class ThreadSafeInteger { /** * 共享變量 value */ private volatile int value; public int getValue() { return value; } public void setValue(int value) { this.value = value; } }
再使用 synchronized 關(guān)鍵字改造
public class ThreadSafeInteger { /** * 共享變量 value */ private int value; public synchronized int getValue() { return value; } public synchronized void setValue(int value) { this.value = value; } }
這兩個結(jié)果是完全相同,在解決【當(dāng)前】共享變量數(shù)據(jù)可見性的問題上,二者算是等同的
如果說 synchronized 和 volatile 是完全等同的,那就沒必要設(shè)計兩個關(guān)鍵字了,繼續(xù)看個例子
@Slf4j public class VisibilityIssue { private static final int TOTAL = 10000; // 即便像下面這樣加了 volatile 關(guān)鍵字修飾不會解決問題,因為并沒有解決原子性問題 private volatile int count; public static void main(String[] args) { VisibilityIssue visibilityIssue = new VisibilityIssue(); Thread thread1 = new Thread(() -> visibilityIssue.add10KCount()); Thread thread2 = new Thread(() -> visibilityIssue.add10KCount()); thread1.start(); thread2.start(); try { thread1.join(); thread2.join(); } catch (InterruptedException e) { log.error(e.getMessage()); } log.info("count 值為:{}", visibilityIssue.count); } private void add10KCount(){ int start = 0; while (start ++ < TOTAL){ this.count ++; } } }
其實就是將上面setValue 簡單賦值操作 (this.value = value;)變成了 (this.count ++;)形式,如果你運行代碼,你會發(fā)現(xiàn),count的值始終是處于1w和2w之間的
將上面方法再以 synchronized 的形式做改動
@Slf4j public class VisibilityIssue { private static final int TOTAL = 10000; private int count; //... 同上 private synchronized void add10KCount(){ int start = 0; while (start ++ < TOTAL){ this.count ++; } } }
再次運行代碼,count 結(jié)果就是 2w
以上就是volatile和synchronized的區(qū)別是什么,小編相信有部分知識點可能是我們?nèi)粘9ぷ鲿姷交蛴玫降?。希望你能通過這篇文章學(xué)到更多知識。更多詳情敬請關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道。