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包下的類。