併發系列之「Java中的synchronized關鍵字」
本文為《Java併發程式設計之美》學習筆記
Java中共享變數的記憶體可見性問題
在講synchronized
之前先來講一下Java中共享變數的記憶體可見性問題。
先來看看在多執行緒下處理共享變數時Java的記憶體模型:
Java記憶體模型規定,將所有的變數都存放在主記憶體
中,當執行緒使用變數時,會把主記憶體裡面的變數複製到自己的工作空間或者叫作工作記憶體,執行緒讀寫變數時操作的是自己工作記憶體中的變數。Java記憶體模型是一個抽象的概念,那麼在實際實現中執行緒的工作記憶體是什麼呢?
實際上的記憶體模型:
圖中所示是一個雙核CPU系統架構,每個核有自己的控制器(Controller)
和運算器(ALU)
,其中控制器包含一組暫存器
和操作控制器
,運算器執行算術邏輯運算。每個核都有自己的一級快取,在有些架構裡面還有一個所有CPU都共享的二級快取。那麼Java記憶體模型裡面的工作記憶體,就對應這裡的L1或者L2快取或者CPU的暫存器。
當一個執行緒操作共享變數時,它首先從主記憶體複製共享變數到自己的工作記憶體,然後對工作記憶體裡的變數進行處理,處理完後將變數值更新到主記憶體。那麼假如執行緒A和執行緒B同時處理一個共享變數,會出現什麼情況?我們使用上面實際的CPU架構圖,假設執行緒A和執行緒B使用不同CPU執行,並且當前兩級Cache都為空,那麼這時候由於Cache的存在,將會導致記憶體不可見問題,具體看下面的分析。
- 執行緒A首先獲取共享變數X的值,由於兩級Cache都沒有命中,所以載入主記憶體中X的值,假如為0。然後把X=0的值快取到兩級快取,執行緒A修改X的值為1,然後將其寫入兩級Cache,並且重新整理到主記憶體。執行緒A操作完畢後,執行緒A所在的CPU的兩級Cache內和主記憶體裡面的X的值都是1。
- 執行緒B獲取X的值,首先一級快取沒有命中,然後看二級快取,二級快取命中了,所以返回X= 1;到這裡一切都是正常的,因為這時候主記憶體中也是X=1。然後執行緒B修改X的值為2,並將其存放到執行緒2所在的一級Cache和共享二級Cache中,最後更新主記憶體中X的值為2;到這裡一切都是好的。
- 執行緒A這次又需要修改X的值,獲取時一級快取命中,並且X=1,到這裡問題就出現了,明明執行緒B已經把X的值修改為了2,為何執行緒A獲取的還是1呢?這就是共享變數的記憶體不可見問題,也就是執行緒B寫入的值對執行緒A不可見。
那麼如何解決共享變數記憶體不可見問題?使用Java中的synchronized
關鍵字就可以解決這個問題。
synchronized關鍵字
簡介
synchronized塊是Java提供的一種原子性內建鎖
,Java中的每個物件都可以把它當作一個同步鎖
來使用,這些Java內建的使用者看不到的鎖被稱為內部鎖
,也叫作監視器鎖
。
執行緒的執行程式碼在進入synchronized
程式碼塊前會自動獲取內部鎖,這時候其他執行緒訪問該同步程式碼塊時會被阻塞掛起。
拿到內部鎖的執行緒出現以下幾種情況之一會釋放該內建鎖:
- 正常退出同步程式碼塊;
- 丟擲異常後;
- 在同步塊內呼叫了該內建鎖資源的
wait
系列方法
內建鎖是排它鎖
,也就是當一個執行緒獲取這個鎖後,其他執行緒必須等待該執行緒釋放鎖後才能獲取該鎖。
此外,由於Java中的執行緒是與作業系統的原生執行緒一一對應的,所以當阻塞一個執行緒時,需要從使用者態切換到核心態執行阻塞操作,這是很耗時的操作,而synchronized
的使用就會導致上下文切換。
synchronized
的記憶體語義
前面介紹了共享變數記憶體可見性問題主要是由於執行緒的工作記憶體導致的,下面我們來講解synchronized
的一個記憶體語義,這個記憶體語義就可以解決共享變數記憶體可見性問題。
進入synchronized
塊的記憶體語義是把在synchronized
塊內使用到的變數從執行緒的工作記憶體中清除
,這樣在synchronized
塊內使用到該變數時就不會從執行緒的工作記憶體中獲取,而是直接從主記憶體中獲取
。退出synchronized
塊的記憶體語義是把在synchronized
塊內對共享變數的修改重新整理到主記憶體。
其實這也是加鎖
和釋放鎖
的語義,當獲取鎖後會清空鎖塊內本地記憶體中將會被用到的共享變數,在使用這些共享變數時從主記憶體進行載入,在釋放鎖時將本地記憶體中修改的共享變數重新整理到主記憶體。除可以解決共享變數記憶體可見性問題外,synchronized
經常被用來實現原子性操作。
另外請注意,synchronized
關鍵字會引起執行緒上下文切換並帶來執行緒排程開銷。
相關文章
- Java高併發之synchronized關鍵字Javasynchronized
- Java併發——關鍵字synchronized解析Javasynchronized
- java學習——併發專題——synchronized關鍵字Javasynchronized
- java併發之volatile關鍵字Java
- Java併發程式設計序列之執行緒間通訊-synchronized關鍵字-volatile關鍵字Java程式設計執行緒synchronized
- 【Java併發程式設計】Synchronized關鍵字實現原理Java程式設計synchronized
- 併發程式設計之ThreadLocal、Volatile、synchronized、Atomic關鍵字程式設計threadsynchronized
- 併發程式設計——synchronized關鍵字的使用程式設計synchronized
- Java關鍵字(八)——synchronizedJavasynchronized
- java併發之synchronizedJavasynchronized
- Java併發--final關鍵字Java
- 併發程式設計原理學習:synchronized關鍵字程式設計synchronized
- 【併發程式設計】(二)Java併發機制底層實現原理——synchronized關鍵字程式設計Javasynchronized
- Java併發—— 關鍵字volatile解析Java
- Java之併發程式設計:volatile關鍵字解析Java程式設計
- 多執行緒與高併發(三)synchronized關鍵字執行緒synchronized
- synchronized 關鍵字synchronized
- Java:synchronized關鍵字引出的多種鎖Javasynchronized
- synchronized關鍵字的原理synchronized
- 再識Java併發程式設計關鍵字之volatileJava程式設計
- 使用 Synchronized 關鍵字synchronized
- Java併發程式設計volatile關鍵字Java程式設計
- java併發程式設計:volatile關鍵字Java程式設計
- java併發程式設計——volatile關鍵字Java程式設計
- Java併發程式設計之synchronizedJava程式設計synchronized
- JAVA-多執行緒(關鍵字:synchronized)Java執行緒synchronized
- Java併發程式設計:volatile關鍵字解析Java程式設計
- Java併發指南4:Java中的鎖 Lock和synchronizedJavasynchronized
- java中this關鍵字Java
- Java 關鍵字之 finalJava
- 深入理解synchronized關鍵字synchronized
- 深入分析 synchronized 關鍵字synchronized
- java中的static關鍵字Java
- java中的instanceof關鍵字Java
- 從物件頭出發瞭解Synchronized關鍵字物件synchronized
- Java併發系列之volatileJava
- [java基礎]之JAVA關鍵字Java
- Java中final關鍵字Java