前天分享了一篇關於阿里的「Java常見疑惑和陷阱」的文章,有人說這個很早就有了,可能我才注意到,看完以後發現內容很是不錯,有幾個我也是須要停頓下想一想,若是後續有機會我錄製一個視頻把這個ppt裏面的全部內容,根據個人理解和知道的給你們分享一遍。程序員
若是你以前尚未看過建議好好看一遍:Java常見疑惑和陷阱,若是你須要獲取完整ppt,能夠在公號對話框回覆: 「PPT」 便可獲取完整文件,只要你發現你看到裏面知識點的時候,你須要思考一會,那麼就表示你還不太熟悉,你應該去補補相關的基礎知識了。編程
我我的一直認爲: 網絡、併發相關的知識,相對其餘一些編程知識點更難一些,主要是很差調試而且涉及內容太多 !安全
因此今天就取一篇併發相關的內容分享下,我相信你們認真看完會有收穫的。bash
你們能夠先看看這個問題,看看這個是否有問題呢? 那裏有問題呢?網絡
若是你在這個問題上面停留超過5s的話,那麼表示你對這塊某些知識還有點模糊,須要再鞏固下,下面咱們一塊兒來分析下!多線程
多線程併發的同時進行set、get操做,A線程調用set方法,B線程並不必定能對這個改變可見!!!併發
這個類很是簡單,裏面有一個屬性,有2個方法:get、set方法,一個用來設置屬性值,一個用來獲取屬性值,在設置屬性方法上面加了synchronized。app
隱式信息: 多線程併發的同時進行set、get操做,A線程調用set方法,B線程能夠裏面感知到嗎???ui
說到這裏,問題就變成了synchronized在剛剛說的上下文下面可否保證可見性!!!this
synchronized它的工做就是對須要同步的代碼加鎖,使得每一次只有一個線程能夠進入同步塊(實際上是一種悲觀策略)從而保證線程之間得安全性。
從這裏咱們能夠知道,咱們須要分析的屬於第二類狀況,也就是說多個線程若是同時進行set方法的時候,因爲存在鎖,因此會一個一個進行set操做,而且是線程安全的,可是get方法並無加鎖,表示假如A線程在進行set的同時B線程能夠進行get操做。而且能夠多個線程同時進行get操做,可是同一時間最多隻能有一個set操做。
JSR-133 內存模型使用 happens-before 的概念來闡述操做之間的內存可見性。在 JMM 中,若是一個操做執行的結果須要對另外一個操做可見,那麼這兩個操做之間必需要存在 happens-before 關係。這裏提到的兩個操做既能夠是在一個線程以內,也能夠是在不一樣線程之間。
與程序員密切相關的 happens-before 規則以下:
注意,兩個操做之間具備 happens-before 關係,並不意味着前一個操做必需要在後一個操做以前執行!happens-before 僅僅要求前一個操做(執行的結果)對後一個操做可見,且前一個操做按順序排在第二個操做以前(the first is visible to and ordered before the second)。
其中有監視器鎖規則:對一個監視器的解鎖,happens-before 於隨後對這個監視器的加鎖。 這一條,僅僅只是針對synchronized的set方法,而對於get並無這方面的說明。
其實在這種上下文下面一個synchronized的set方法,一個普通的get方法,a線程調用set方法,b線程並不必定能對這個改變可見!
更多Java內存模型內存歡迎查看:深刻理解 Java 內存模型,寫的很是詳細,建議多讀幾遍!!!
前面happens-before原則就提到:volatile 變量規則:對一個 volatile 域的寫,happens-before 於任意後續對這個 volatile 域的讀。 volatile從而保證了多線程下的可見性!!!
下面是 JMM 針對編譯器制定的 volatile 重排序規則表:
爲了實現 volatile 的內存語義,編譯器在生成字節碼時,會在指令序列中插入內存屏障來禁止特定類型的處理器重排序。
下面是基於保守策略的 JMM 內存屏障插入策略:
下面是保守策略下,volatile 寫操做 插入內存屏障後生成的指令序列示意圖:
下面是在保守策略下,volatile 讀操做 插入內存屏障後生成的指令序列示意圖:
上述 volatile 寫操做和 volatile 讀操做的內存屏障插入策略很是保守。在實際執行時,只要不改變 volatile 寫-讀的內存語義,編譯器能夠根據具體狀況省略沒必要要的屏障。
更多Java內存模型內存歡迎查看:深刻理解 Java 內存模型,寫的很是詳細,建議多讀幾遍!!!
雙重檢查鎖實現單例中就須要用到這個特性!!!
經過上面的分析,其實這個題目涉及到的內容都提到了,而且進行了解答。
雖然你知道的緣由,可是想模擬並非一件容易的事情!,下面咱們來模擬看看效果:
public class ThreadSafeCache {
int result;
public int getResult() {
return result;
}
public synchronized void setResult(int result) {
this.result = result;
}
public static void main(String[] args) {
ThreadSafeCache threadSafeCache = new ThreadSafeCache();
for (int i = 0; i < 8; i++) {
new Thread(() -> {
int x = 0;
while (threadSafeCache.getResult() < 100) {
x++;
}
System.out.println(x);
}).start();
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
threadSafeCache.setResult(200);
}
}
複製代碼
效果:
程序會一直卡在這邊不動,表示set修改的200,get方法並不可見!!!
添加volatile 關鍵詞觀察效果
其實例子中synchronized關鍵字能夠去掉,僅僅用volatile便可。
效果:
代碼很快正常結束了!
結論: 多線程併發的同時進行set、get操做,A線程調用set方法,B線程並不必定能對這個改變可見!!!,上面的代碼中,若是對get方法也加synchronized也是可見的,仍是happens-before的監視器鎖規則:對一個監視器的解鎖,happens-before 於隨後對這個監視器的加鎖。,只是volatile比synchronized更輕量級,因此本例直接用volatile。可是對於符合非原子操做i++這裏仍是不行的仍是須要synchronized。
更多Java內存模型內存歡迎查看:深刻理解 Java 內存模型,寫的很是詳細,建議多讀幾遍!!!
建議好好看看Java常見疑惑和陷阱,裏面有不少很優秀的東西,若是你須要獲取完整ppt,能夠在公號對話框回覆: 「PPT」 便可獲取完整文件!
若是讀完以爲有收穫的話,歡迎點贊、關注、加公衆號【匠心零度】,查閱更多精彩歷史!!!
)