1. 程式人生 > >併發程式設計之美,帶你深入理解java多執行緒原理

併發程式設計之美,帶你深入理解java多執行緒原理

1.什麼是多執行緒?

多執行緒是為了使得多個執行緒並行的工作以完成多項任務,以提高系統的效率。執行緒是在同一時間需要完成多項任務的時候被實現的。

2.瞭解多執行緒

瞭解多執行緒之前我們先搞清楚幾個重要的概念!

如上圖所示:對我們的專案有一個主記憶體,這個主記憶體裡面存放了我們的共享變數、方法區、堆中的物件等。

3.執行緒的工作過程

每當我們開啟一個執行緒的時候,執行緒會為我們開闢一塊工作記憶體,將主記憶體中的共享變數複製一個副本存入工作記憶體中,並協調方法區生成棧針,以及對堆的引用(指標)。

如果在執行過程中執行緒對工作記憶體中的共享變數進行的修改操作,此時會向主記憶體回寫我們修改的變數。

4.多執行緒帶來的問題

我們模擬這樣一個場景:

有十個使用者同時購票,但是系統中只剩下了8張票,當每個使用者同時開啟自己的執行緒,將主記憶體中8張票複製到工作記憶體中,在方法中,會判斷票數是否滿足要求,此時,十個執行緒都判斷滿足,都要對票數進行操作。

當用戶一操作後,票數=8-1=7,將資料回寫至主記憶體。

使用者二操作後,使用者二的本地記憶體中票數為8,則修改後票數=8-1=7,繼續回寫至主記憶體,

以此下去,在我們假設十個使用者同時開啟執行緒的情況下最後主記憶體中的票數肯定是7,而且十個使用者均出票成功,出現了超賣的情況,這在現實場景是很危險的事!

5.多執行緒的特性

有序性:程式執行的順序按照程式碼的先後順序執行。

可見性:當多個執行緒訪問同一個變數時,一個執行緒修改了這個變數的值,其他執行緒能夠立即看得到修改的值。

若兩個執行緒在不同的cpu,那麼執行緒1改變了i的值還沒重新整理到主存,執行緒2又使用了i,那麼這個i值肯定還是之前的,執行緒1對變數的修改執行緒沒看到這就是可見性問題。

原子性:即一個操作或者多個操作要麼全部執行並且執行的過程不會被任何因素打斷,要麼就都不執行。

在程式編譯到執行的過程中,程式會經過多次重排序,原始碼->編譯器優化重排序->指令級並行重排序->記憶體系統重排序->最終執行的指令序列,

也就是說我們編寫的程式碼,經過這一連串的重排序後,程式碼很可能就和我們寫的順序不一致了,但是我們的作業系統等會保證我們

最終執行的指令序列與我們的原始碼的結果保持一致,我們的作業系統是可以保證單執行緒的有序性的。

6.怎麼解決多執行緒併發帶來的問題?

什麼時候需要使用多執行緒?

競態條件:檢查後執行是否滿足決定下一步。

方法一:加鎖

1.監視器鎖synchronized,它確保了每個執行緒是隔離的,而且只有當一個執行緒執行進入帶有synchronized的方法中時加鎖,

當該執行緒為結束此方法解鎖時,其它執行緒將掛起,直到該執行緒解鎖後其它執行緒才能繼續執行下去。它能夠保證上述三大特性:有序性、可見性、原子性。

JMM定義記憶體訪問規範,實現有序性、可見性、原子性,共八大規則,大家可以上網瞭解JMM詳細規則資訊。

同步機制:

  監視器鎖synchronized

  顯示鎖ReentrantLock、ReadWriteLock

  原子變數AtomicInteger、AtomicLong、AtomicBoolean

  Volatile

問題:遇到同步問題如何選擇具體的實現方式?

  監視器鎖在jdk1.5以後,效能得到了很大的提升,並且在java版本更新中一直在被優化,而且synchronized鎖可以自動實現加鎖與解鎖。

  顯示鎖需要我們手動解鎖、加鎖,容易失誤導致死鎖。

  在考慮效能時,推薦使用監視器鎖,當考慮功能時,推薦使用顯示鎖,顯示鎖擁有更多自定義的選擇。

方法二:執行緒封閉

什麼是執行緒封閉?

當訪問共享的可變資料時,通常需要同步,一種避免同步的方式就是不共享資料,如果僅在單執行緒內訪問資料,就不需要同步,這種技術稱為執行緒封閉。

如果使用執行緒封閉:1.棧封閉:執行緒為跳用方法生成棧針時區域性變數就使用了執行緒封閉。

         2.ThreadLocal  --> 只有當前執行緒能使用。

方法三:不可變物件一定是執行緒安全的。

最佳方案:使用執行緒安全的物件是實現執行緒安全的。 java.util.concurrent包下的類。