簡介:
本文主要介紹了Java多線程環境下,可能會出現的問題(線程不安全)以及相應的解決措施。通過本文,你將學習到如下幾塊知識:
1. 為什么需要多線程(多線程的優勢)
1. 多線程帶來的問題—線程安全
2. 產生線程不安全的原因
3. 有哪些方法能解決線程不安全
------------------------------------------------------------
系好安全帶,下面進入正文:
一:為什么需要多線程?
線程是Java語言中不可或缺的重要部分,它們能使復雜的異步代碼變得簡單,簡化復雜系統的開發;能充分發揮多處理器系統的強大計算能力。多線程和多進程的區別與選擇可以參考我的另一篇博客。
(1) 優點
1. 充分利用硬件資源。由於線程是cpu的基本調度單位,所以如果是單線程,那么最多只能同時在一個處理器上運行,意味着其他的CPU資源都將被浪費。而多線程可以同時在多個處理器上運行,只要各個線程間的通信設計正確,那么多線程將能充分利用處理器的資源。
2. 結構優雅。多線程程序能將代碼量巨大,復雜的程序分成一個個簡單的功能模塊,每塊實現復雜程序的一部分單一功能,這將會使得程序的建模,測試更加方便,結構更加清晰,更加優雅。
3. 簡化異步處理。為了避免阻塞,單線程應用程序必須使用非阻塞I/O,這樣的I/O復雜性遠遠高於同步I/O,並且容易出錯。
(2) 缺點
1. 線程安全:由於統一進程下的多個線程是共享同樣的地址空間和數據的,又由於線程執行順序的不可預知性,一個線程可能會修改其他線程正在使用的變量,這一方面是給數據共享帶來了便利;另一方面,如果處理不當,會產生臟讀,幻讀等問題,好在Java提供了一系列的同步機制來幫助解決這一問題,例如內置鎖。
2. 活躍性問題。可能會發生長時間的等待鎖,甚至是死鎖。
3. 性能問題。 線程的頻繁調度切換會浪費資源,同步機制會導致內存緩沖區的數據無效,以及增加同步流量。
二:線程安全
(1) 定義:當多個線程訪問某個類時,不管運行時環境采用何種調度方式或者這些線程將如何交替運行,並且在主調試代碼中不需要任何額外的同步或者協同,這個類都能表現出正確的行為,則稱這個類時線程安全的。線程安全類中封裝了必要的同步機制,因此客戶端無須進一步采取同步措施。
(2) 線程安全產生的原因:正確性取決於多個線程的交替執行時序,產生了競態條件。
(3) 原子類: 應盡量使用原子類,這樣會讓你分析線程安全時更加方便,但需要注意的是用線程安全類構建的類並不能保證線程安全。例如,一個AtomicInteger get() 和 AtomicInteger set() 是線程安全的,在一個類的一個方法 f()中同時用到了這兩個方法,此時的f()就是線程不安全的,因為你不能保證這個復合操作中的get 和 set同時更新。
三:解決機制
1. 加鎖。
(1) 鎖能使其保護的代碼以串行的形式來訪問,當給一個復合操作加鎖后,能使其成為原子操作。一種錯誤的思想是只要對寫數據的方法加鎖,其實這是錯的,對數據進行操作的所有方法都需加鎖,不管是讀還是寫。
(2) 加鎖時需要考慮性能問題,不能總是一味地給整個方法加鎖synchronized就了事了,應該將方法中不影響共享狀態且執行時間比較長的代碼分離出去。
(3) 加鎖的含義不僅僅局限於互斥,還包括可見性。為了確保所有線程都能看見最新值,讀操作和寫操作必須使用同樣的鎖對象。
2. 不共享狀態
(1) 無狀態對象: 無狀態對象一定是線程安全的,因為不會影響到其他線程。
(2) 線程關閉: 僅在單線程環境下使用。
3. 不可變對象
可以使用final修飾的對象保證線程安全,由於final修飾的引用型變量(除String外)不可變是指引用不可變,但其指向的對象是可變的,所以此類必須安全發布,也即不能對外提供可以修改final對象的接口。