十四、Java高級特性(volatile、synchronized關鍵字)

1、Java內存模型(JMM)

從抽象的角度來看,JMM定義了線程和主內存之間的抽象關係:線程之間的共享變量存儲在主內存中,每一個線程都有一個私有的本地內存,我們叫做工作內存。線程對共享變量的所有操作都是工作內存中進行,它首先會從主內存中讀取數據到工作內存,然後在工作內存中做相應的操作,而不能直接操作工作內存中的數據。並且線程之間的工作內存也不能互相訪問,必須經過主內存作爲中間件。

2、可見性

可見性指的是當有多個線程訪問共享變量的時候,一個線程修改了值,其他線程可以立即看得到。當一個線程需要對共享變量的值進行修改的時候,首先將主內存中的數據讀到工作內存,然後在工作內存進行修改,接着纔會刷新到主內存,並且刷新到主內存的過程有一定的時間差,不會立即刷新到主內存。這樣就會產生多個線程訪問共享變量的時候拿到的不是最新的值。這種情況我們可以通過使用volatile關鍵字或者加鎖的方式來解決

3、原子性

原子性:即一個操作或者多個操作要麼全部執行並且執行過程不被任何因素打斷,要麼不執行。
我們知道CPU通過分配時間片給線程,讓每個線程在自己的時間片範圍內工作。當時間片完了的時候就會產生上下文切換,切換到下一個線程繼續工作。那麼這個切換過程可能會在CPU的任何一條指令,當我們一個線程中執行了一個 count++的操作,可能包含了三個cpu指令,有可能就在第二個指令的時候cpu切換到別的線程工作,這樣就不能保證一個coun++的原子性。這種情況我們可以通過加鎖的方式來解決,例如synchronized。

4、volatile詳解

volatile保證了可見性,也就是通過volatile關鍵字修飾的共享變量,當一個線程在工作內存中修改了其值之後,會立即刷新到主內存,當後面的線程每次讀的時候,都能讀到最新的值,但是volatile不能保證原子性,因爲volatile只是保證了線程修改共享變量立即刷新到主內存,但是不能保證在它還沒修改成功的時候,其他線程可能已經從主內存中讀取值到自己的工作內存中進行修改。所以可以通過加鎖的方式來保證原子性。

5、volatile的實現原理

volatile關鍵字修飾的變量會存在一個“lock:”的前綴。
Lock前綴,Lock不是一種內存屏障,但是它能完成類似內存屏障的功能。Lock會對CPU總線和高速緩存加鎖,可以理解爲CPU指令級的一種鎖。
同時該指令會將當前處理器緩存行的數據直接寫會到系統內存中,且這個寫回內存的操作會使在其他CPU裏緩存了該地址的數據無效

6、synchronized的實現原理

Synchronized在JVM裏的實現都是基於進入和退出Monitor對象來實現方法同步和代碼塊同步,雖然具體實現細節不一樣,但是都可以通過成對的MonitorEnter和MonitorExit指令來實現。
對同步塊,MonitorEnter指令插入在同步代碼塊的開始位置,而monitorExit指令則插入在方法結束處和異常處,JVM保證每個MonitorEnter必須有對應的MonitorExit。總的來說,當代碼執行到該指令時,將會嘗試獲取該對象Monitor的所有權,即嘗試獲得該對象的鎖:
1、如果monitor的進入數爲0,則該線程進入monitor,然後將進入數設置爲1,該線程即爲monitor的所有者。
2、如果線程已經佔有該monitor,只是重新進入,則進入monitor的進入數加1.
3.如果其他線程已經佔用了monitor,則該線程進入阻塞狀態,直到monitor的進入數爲0,再重新嘗試獲取monitor的所有權。
對同步方法,從同步方法反編譯的結果來看,方法的同步並沒有通過指令monitorenter和monitorexit來實現,相對於普通方法,其常量池中多了ACC_SYNCHRONIZED標示符。
JVM就是根據該標示符來實現方法的同步的:當方法被調用時,調用指令將會檢查方法的 ACC_SYNCHRONIZED 訪問標誌是否被設置,如果設置了,執行線程將先獲取monitor,獲取成功之後才能執行方法體,方法執行完後再釋放monitor。在方法執行期間,其他任何線程都無法再獲得同一個monitor對象。

7、synchronized和ReentrantLock的區別

(1) ReentrantLock
線程可以重複進入任何一個它已經擁有的鎖所同步着的代碼塊,synchronized、ReentrantLock都是可重入的鎖。在實現上,就是線程每次獲取鎖時判定如果獲得鎖的線程是它自己時,簡單將計數器累積即可,每 釋放一次鎖,進行計數器累減,直到計算器歸零,表示線程已經徹底釋放鎖。
底層則是利用了JUC中的AQS來實現的。
(2)synchronized
synchronized (this)原理:涉及兩條指令:monitorenter,monitorexit;再說同步方法,從同步方法反編譯的結果來看,方法的同步並沒有通過指令monitorenter和monitorexit來實現,相對於普通方法,其常量池中多了ACC_SYNCHRONIZED標示符。
JVM就是根據該標示符來實現方法的同步的:當方法被調用時,調用指令將會檢查方法的 ACC_SYNCHRONIZED 訪問標誌是否被設置,如果設置了,執行線程將先獲取monitor,獲取成功之後才能執行方法體,方法執行完後再釋放monitor。在方法執行期間,其他任何線程都無法再獲得同一個monitor對象。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章