如何理解什麼是執行緒安全?
什麼是執行緒安全
執行緒安全在多執行緒程式設計時是一個比較重要的概念,我們下先來看下維基百科是如何定義這個概念的:
https://en.wikipedia.org/wiki/Thread_safety
- Thread safety is a computer programming concept applicable to multi-threaded code. Thread-safe code only manipulates shared data structures in a manner that ensures that all threads behave properly and fulfill their design specifications without unintended interaction.
意思是說:
執行緒安全是應用於多執行緒程式碼的一種計算機程式設計概念,它確保多個執行緒能夠按照程式的設計正確的訪問共享資料結構。
或者再貼近程式語言的角度一點來講,執行緒安全指的是同時最少有兩個及以上的執行緒操作共享的資料區域,並且至少有一個是寫操作。如果你還想不明白,可以去衛生間觀察一下,一個廁位同時能有幾個人使用。
執行緒安全的級別
執行緒安全的級別或者粒度有三種,如下:
(1)執行緒安全
這種情況下其實沒有執行緒安全問題,比如上面的例子中,每個人都有自己專用的衛生間,所以不會存在競爭問題。
(2)條件安全
條件安全,顧名思義是有條件的,所有人共用幾個衛生間,搶到資源的就把門關上,通過門來隔離資源,後面的人就在外面等待直到裡面的人出來。
(3)不安全
這種情況下連門都沒有,所以並不能很好保證資源安全,所以這種情況最好不能讓同時讓多個人直接使用。
實現執行緒安全的方式
大體來說有兩種,首先我們明白安全問題來自於競爭,沒有競爭就不會有問題。
方式一:
核心思路是避免共享資料結構,共享狀態。包括:
(1)使用執行緒local變數
(2)使用不可變物件
方式二:
核心思路是共享不可避免,需要通過條件來確保按照。包括:
(1)互斥鎖
(2)CAS原子操作
Java語言裡面實現策略
這裡以Java語言為例子,上面談到的4種方式,其實在Java裡面都支援,分別對應的解決手段為:
(1)ThreadLocal變數
(2)不可變物件有String,CopyOnWrite集合類
(3) 互斥鎖包括JDK5之前的內建鎖synchronized和JDK5之後的Lock介面
(4) J.U.C裡面Atom開頭的類
可以看出來Java裡面的處理策略還是比較多的,當然不同的策略其實也有具體的適用場景,此外引入了執行緒安全和同步手段會對程式碼的效能造成一定的影響,這一點需要了解。
一般來說避免共享資料結構是能夠比較優雅的解決併發問題,這種程式對多執行緒更友好,效能也會更高。比如單機的ThreadLocal和分散式的Ator模型。這裡面不存在競爭。其次是不可變變數,多執行緒操作的都是CopyOnWrite,這也是為什麼一些動態程式語言如Scala裡面的預設資料結構大多數都是不可變的。不可變有不可變的好處,但缺點也是明顯的,如果需要頻繁對資料修改,那麼會建立很多臨時物件和佔用更多的記憶體。
上面這兩種場景,我們一般稱為無鎖實現,效能很好。如果避免不了共享資料,那麼接著效能比較好的就是CAS這種原子操作,這種情況下我們一般也稱是無鎖的,但其實是利用了作業系統的原子指令來實現的,在競爭不激烈的場景下效能比較好,一般的程式語言都有封裝好的工具類。如果競爭激烈,其實效能未必比使用互斥鎖高。互斥鎖一般也稱重量級鎖,需要OS干涉執行緒的排程,適合用於競爭激烈的場景下,這種方式下執行緒上下文的交換會降級系統的效能,在使用時需要注意。
執行緒併發技能圖譜
多執行緒程式設計領域其實涉及很多計算機知識,執行緒安全只是其中的冰山一角,作為一名技術人員我們有必要系統的學習和攻破併發程式設計這一塊,很多人覺得併發程式設計很難,其實是沒有掌握系統的學習方法,在這裡我放出我之前總結併發知識的一張圖譜,供大家參考學習:
總結
本文主要介紹了什麼是執行緒安全,及實現執行緒安全的一些手段,並結合Java語言描述了相關的知識,最後又總結了Java裡面併發學習的知識圖譜,只要把裡面所有的內容都瞭解掌握,那麼在多執行緒領域就可以從青銅升級到王者段位了,不過學習之路,學無止境,不能急功近利,一定得重基礎,然後循序漸近,日拱一卒,就算慢點也無妨,堅持下去,肯定有所收穫。