国产精品免费嫩草研究院|无遮羞动漫在线观看AV|国产麻豆精品传媒AV国产在线|村在线观看|寂寞情人1正版|韩国床震韩国床震古|精品系列专区久久

Java并發編程 | 從進程、線程到并發問題實例解決( 二 )


  1. GETSTATIC 將靜態變量 val壓入棧中;
  2. ICONST_1 將常量1壓入棧中;
  3. IADD 執行加(+)運算操作;
  4. PUTSTATIC 將結果放回 val變量 。
    Java并發編程 | 從進程、線程到并發問題實例解決

    文章插圖
    可以看到執行 +1 這個操作其實是在獨立棧內進行,不同線程其實有不同的操作棧 。
如果線程(1)還未執行完 PUTSTATIC 操作,另外一個線程(2)進行了 GETSTATIC ;這個時候線程(2)執行 +1 操作時,就不會使用線程(1)+1 執行完成后的結果 。
當同樣執行到 PUTSTATIC 時,也不會考慮線程(1)情況 直接把自己運算結果寫進 val 。這樣也就出現了并發問題,并非我們想象的多線程執行都能改變val的值 。
Java并發編程 | 從進程、線程到并發問題實例解決

文章插圖
怎么解決這種并發問題?設計初衷上說val+1操作的邏輯時希望在讀取val值上進行+1的操作,而非在+1過程中初始val值由于其他線程操作而改變 。因此在計算機指令上就給到了一個指令 cmpxchg,在將棧里面值交換到堆里面val時,比較val初始值么沒有變化執行成,否則執行失敗 。如果指令執行失敗了,我們再重新進行新val值的計算直到完成一次成功操作 。這也就是 解決Java并發一個基本算法 CAS(Compare-and-Swap) 。
CAS算法有三個操作數,通過內存中的值(V)、預期原始值(A)、修改后的新值 。
如果內存中的值和預期原始值相等,就將修改后的新值保存到內存中 。如果內存中的值和預期原始值不相等,說明共享數據已經被修改,放棄已經所做的操作,然后重新執行剛才的操作,直到重試成功 。Java中Unsafe 中的getAndAddInt就是使用的這個算法,不妨詳細解讀下其代碼 。
Java并發編程 | 從進程、線程到并發問題實例解決

文章插圖
到這里還涉及到一個線程變量修改同步問題,由于計算機結構復雜性,CPU、Mem等各級緩存特性、不同操作系統、不同廠商硬件等等,其中有著很多緩存/同步設計;為了屏蔽這些復雜性,java提供了volatile 關鍵字來進行保證 。截取一段The Java Language Specification (Java SE 10 Edition)原文:
Java并發編程 | 從進程、線程到并發問題實例解決

文章插圖
抓重點的理解:字段被聲明為volatile,在這種情況下,Java內存模型確保所有線程都看到變量的一致值 。
試一試,多線程性能更好?按照前面解決的思路,修改下之前的代碼進行測試下 。另外將耗時也記錄一下:
Java并發編程 | 從進程、線程到并發問題實例解決

文章插圖
是不是發現,val 的數值已經和單線程的一致了都是 1000,沒有并發問題了 。性能上從這個例子可以看到,單線程耗時6ms,多線程耗時29ms 。不用質疑結果是沒錯的,明顯多線程耗時更高 。
可以看出多線程運行簡單程序并不一定能夠提升性能,因為其開啟線程有相關的開銷;同時看到其 復雜性高、維護成本高、可讀性降低 等缺陷 。對于簡單業務邏輯場景,不建議用多線程 。
在此基礎上,加上模擬下相關業務邏輯,模擬邏輯執行doSomeThings(),模擬實現邏輯就是線程休眠 1ms 。相關代碼,耗時記錄如下:
Java并發編程 | 從進程、線程到并發問題實例解決

文章插圖
這個例子里面 多線程性能優勢,與單線程的1914ms 相比多線程只需要 262ms 。當然具體提升的數值和運行的機器、CPU等等有關系,筆者電腦是 4核8線程的情況 。
本篇總結下,介紹了進程、線程以及相關發展史;展示了一個具體的并發問題;詳細分析了并發問題的發生原因以及解決辦法 。最后對多線程并發程序進行了驗證,以及相關性能上的探究 。

經驗總結擴展閱讀