溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

volatile和synchronized的區(qū)別是什么

發(fā)布時(shí)間:2021-06-12 16:58:03 來源:億速云 閱讀:177 作者:Leah 欄目:編程語(yǔ)言

本篇文章給大家分享的是有關(guān)volatile和synchronized的區(qū)別是什么,小編覺得挺實(shí)用的,因此分享給大家學(xué)習(xí),希望大家閱讀完這篇文章后可以有所收獲,話不多說,跟著小編一起來看看吧。


Java 內(nèi)存模型(JMM)

CPU 增加了緩存均衡了與內(nèi)存的速度差異,這一增加還是好幾層。

volatile和synchronized的區(qū)別是什么

此時(shí)內(nèi)存的短板不再那么明顯,CPU甚喜。但隨之卻帶來很多問題

volatile和synchronized的區(qū)別是什么

看上圖,每個(gè)核都有自己的一級(jí)緩存(L1 Cache),有的架構(gòu)里面還有所有核共用的二級(jí)緩存(L2 Cache)。使用緩存之后,當(dāng)線程要訪問共享變量時(shí),如果 L1 中存在該共享變量,就不會(huì)再逐級(jí)訪問直至主內(nèi)存了。所以,通過這種方式,就補(bǔ)上了訪問內(nèi)存慢的短板

具體來說,線程讀/寫共享變量的步驟是這樣:

  1. 鴻蒙官方戰(zhàn)略合作共建——HarmonyOS技術(shù)社區(qū)

  2.  從主內(nèi)存復(fù)制共享變量到自己的工作內(nèi)存

  3.  在工作內(nèi)存中對(duì)變量進(jìn)行處理

  4.  處理完后,將變量值更新回主內(nèi)存

假設(shè)現(xiàn)在主內(nèi)存中有共享變量 X, 其初始值為 0

線程1先訪問變量 X, 套用上面的步驟就是這樣:

  1. 鴻蒙官方戰(zhàn)略合作共建——HarmonyOS技術(shù)社區(qū)

  2.  L1 和 L2 中都沒有發(fā)現(xiàn)變量 X,直到在主內(nèi)存中找到

  3.  拷貝變量 X 到 L1 和 L2 中

  4.  在 L1 中將 X 的值修改為1,并逐層寫回到主內(nèi)存中

此時(shí),在線程 1 眼中,X 的值是這樣的:

volatile和synchronized的區(qū)別是什么

接下來,線程 2 同樣按照上面的步驟訪問變量 X

  1. 鴻蒙官方戰(zhàn)略合作共建——HarmonyOS技術(shù)社區(qū)

  2.     L1 中沒有發(fā)現(xiàn)變量 X

  3.     L2 中發(fā)現(xiàn)了變量X

  4.     從L2中拷貝變量到L1中

  在L1中將X 的值修改為2,并逐層寫回到主內(nèi)存中

此時(shí),線程 2 眼中,X 的值是這樣的:

volatile和synchronized的區(qū)別是什么

結(jié)合剛剛的兩次操作,當(dāng)線程1再訪問變量x,我們看看有什么問題:

volatile和synchronized的區(qū)別是什么

此刻,如果線程 1 再次將 x=1回寫,就會(huì)覆蓋線程2 x=2 的結(jié)果,同樣的共享變量,線程拿到的結(jié)果卻不一樣(線程1眼中x=1;線程2眼中x=2),這就是共享變量?jī)?nèi)存不可見的問題。

怎么補(bǔ)坑呢?今天的兩位主角閃亮登場(chǎng),不過在說明 volatile關(guān)鍵字之前,我們先來說說你最熟悉的 synchronized 關(guān)鍵字

synchronized

遇到線程不安全的問題,習(xí)慣性的會(huì)想到用 synchronized 關(guān)鍵字來解決問題,暫且先不論該辦法是否合理,我們來看 synchronized 關(guān)鍵字是怎么解決上面提到的共享變量?jī)?nèi)存可見性問題的

  •  【進(jìn)入】synchronized 塊的內(nèi)存語(yǔ)義是把在 synchronized 塊內(nèi)使用的變量從線程的工作內(nèi)存中清除,從主內(nèi)存中讀取

  •  【退出】synchronized 塊的內(nèi)存語(yǔ)義事把在 synchronized 塊內(nèi)對(duì)共享變量的修改刷新到主內(nèi)存中

二話不說,無情向下看 volatile

volatile

當(dāng)一個(gè)變量被聲明為 volatile 時(shí):

  •  線程在【讀取】共享變量時(shí),會(huì)先清空本地內(nèi)存變量值,再?gòu)闹鲀?nèi)存獲取最新值

  •  線程在【寫入】共享變量時(shí),不會(huì)把值緩存在寄存器或其他地方(就是剛剛說的所謂的「工作內(nèi)存」),而是會(huì)把值刷新回主內(nèi)存

有種換湯不換藥的感覺,你看的一點(diǎn)都沒錯(cuò)

volatile和synchronized的區(qū)別是什么

所以,當(dāng)使用 synchronized 或 volatile 后,多線程操作共享變量的步驟就變成了這樣:

volatile和synchronized的區(qū)別是什么

簡(jiǎn)單點(diǎn)來說就是不再參考 L1 和 L2 中共享變量的值,而是直接訪問主內(nèi)存

來點(diǎn)踏實(shí)的,上例子

public class ThreadNotSafeInteger {      /**       * 共享變量 value       */      private int value;      public int getValue() {          return value;      }      public void setValue(int value) {          this.value = value;      }  }

經(jīng)過前序分析鋪墊,很明顯,上面代碼中,共享變量 value 存在大大的隱患,嘗試對(duì)其作出一些改變

先使用 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;      }  }

這兩個(gè)結(jié)果是完全相同,在解決【當(dāng)前】共享變量數(shù)據(jù)可見性的問題上,二者算是等同的

如果說 synchronized 和 volatile 是完全等同的,那就沒必要設(shè)計(jì)兩個(gè)關(guān)鍵字了,繼續(xù)看個(gè)例子

@Slf4j  public class VisibilityIssue {      private static final int TOTAL = 10000;  //    即便像下面這樣加了 volatile 關(guān)鍵字修飾不會(huì)解決問題,因?yà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 ++;          }      }  }

其實(shí)就是將上面setValue 簡(jiǎn)單賦值操作 (this.value = value;)變成了 (this.count ++;)形式,如果你運(yùn)行代碼,你會(huì)發(fā)現(xiàn),count的值始終是處于1w和2w之間的

將上面方法再以 synchronized 的形式做改動(dòng)

@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 ++;          }      }  }

再次運(yùn)行代碼,count 結(jié)果就是 2w

以上就是volatile和synchronized的區(qū)別是什么,小編相信有部分知識(shí)點(diǎn)可能是我們?nèi)粘9ぷ鲿?huì)見到或用到的。希望你能通過這篇文章學(xué)到更多知識(shí)。更多詳情敬請(qǐng)關(guān)注億速云行業(yè)資訊頻道。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI