複製鏈接
請複製以下鏈接發送給好友

線程安全

鎖定
線程安全是多線程編程時的計算機程序代碼中的一個概念。在擁有共享數據的多條線程並行執行的程序中,線程安全的代碼會通過同步機制保證各個線程都可以正常且正確的執行,不會出現數據污染等意外情況。
中文名
線程安全
外文名
thread
作    用
保證各線程正常且正確的執行

線程安全簡介

多個線程訪問同一個對象時,如果不用考慮這些線程在運行時環境下的調度和交替執行,也不需要進行額外的同步,或者在調用方進行任何其他操作,調用這個對象的行為都可以獲得正確的結果,那麼這個對象就是線程安全的。
或者説:一個類或者程序所提供的接口對於線程來説是原子操作或者多個線程之間的切換不會導致該接口的執行結果存在二義性,也就是説我們不用考慮同步的問題。
線程安全問題大多是由全局變量靜態變量引起的,局部變量逃逸也可能導致線程安全問題。
若每個線程中對全局變量、靜態變量只有讀操作,而無寫操作,一般來説,這個全局變量是線程安全的;若有多個線程同時執行寫操作,一般都需要考慮線程同步,否則的話就可能影響線程安全。
類要成為線程安全的,首先必須在單線程環境中有正確的行為。如果一個類實現正確(這是説它符合規格説明的另一種方式),那麼沒有一種對這個類的對象的操作序列(讀或者寫公共字段以及調用公共方法)可以讓對象處於無效狀態,觀察到對象處於無效狀態、或者違反類的任何不可變量、前置條件或者後置條件的情況。
此外,一個類要成為線程安全的,在被多個線程訪問時,不管運行時環境執行這些線程有什麼樣的時序安排或者交錯,它必須仍然有如上所述的正確行為,並且在調用的代碼中沒有任何額外的同步。其效果就是,在所有線程看來,對於線程安全對象的操作是以固定的、全局一致的順序發生的。
正確性與線程安全性之間的關係非常類似於在描述 ACID(原子性、一致性、獨立性和持久性)事務時使用的一致性與獨立性之間的關係:從特定線程的角度看,由不同線程所執行的對象操作是先後(雖然順序不定)而不是並行執行的。

線程安全線程狀態分類

線程安全性的分類方法包括:不可變、線程安全、有條件線程安全、線程兼容和線程對立。只要明確地記錄下線程安全特性,那麼您是否使用這種系統都沒關係。這種系統有其侷限性 -- 各類之間的界線不是百分之百地明確,而且有些情況它沒照顧到 -- 但是這套系統是一個很好的起點。這種分類系統的核心是調用者是否可以或者必須用外部同步包圍操作(或者一系列操作)。下面幾節分別描述了線程安全性的這五種類別。

線程安全不可變類

一個不可變的對象只要構建正確, 其外部可見狀態永遠不會改變, 永遠也不會看到它處於不一致的狀態。Java 類庫中大多數基本數值類如Integer、String 和BigInteger 都是原子性的, 是不可變的, 但Long 和Double 就不能保證其操作的原子性, 可在聲明變量的時候用volatile 關鍵字。不可變對象上沒有副作用, 並且緩存不可變對象的引用總是安全的。一個不可變的對象的一個引用可以自由共享,而不用擔心被引用的對象要被修改。 [1] 

線程安全線程安全性類

線程安全性類的對象操作序列( 讀或寫其公有字段以及調用其公有方法) 都不會使該對象處於無效狀態, 即任何操作都不會違反該類的任何不可變量、前置條件或者後置條件。 [1] 

線程安全有條件的線程安全類

有條件的線程安全類對於單獨的操作可以是線程安全的, 但是某些操作序列可能需要外部同步。為了保證其它線程不會在遍歷的時候改變集合, 進行迭代的線程應該確保它是獨佔性地訪問集合以實現遍歷的完整性。通常, 獨佔性的訪問是由對鎖的同步機制保證的。 [1] 

線程安全線程兼容類

線程兼容類不是線程安全的, 但可以通過正確使用同步從而在併發環境中安全地使用。或用一個synchronized 塊包含每一個方法調用。 [1] 

線程安全線程對立類

線程對立類是那些不管是否調用了外部同步都不能在併發使用時保證其安全的類。線程對立類很少見, 當類修改靜態數據,而靜態數據會影響在其它線程中執行的其它類的行為時, 通常會出現線程對立。 [1] 

線程安全線程安全與可重入

如果一個函數能夠安全地同時被多個線程調用而得到正確的結果,那麼,我們説這個函數是線程安全的。所謂“安全”,一切可能導致結果不正確的因素都是不安全的調用。 [2] 
線程安全,是針對多線程而言的。與可重入聯繫起來,我們可以斷定:可重入函數必定是線程安全的,但線程安全的不一定是可重入的。不可重入函數,函數調用結果不具有可再現性,可以通過互斥鎖等機制使之能安全地同時被多個線程調用,那麼,這個不可重入函數就是轉換成了線程安全。 [2] 
可重入(Reentrant)函數可以由多於一個任務併發使用,而不必擔心數據錯誤。相反地,不可重入( Non - Reentrant)函數不能由超過一個任務所共享,除非能確保函數的互斥(或者使用信號量,或者在代碼的關鍵部分禁用中斷)。可重入函數可以在任意時刻被中斷,稍後再繼續運行,不會丟失數據。可重入函數要麼使用本地變量,要麼在使用全局變量時保護自己的數據。 [2] 
可重入函數特徵: [2] 
①不為連續的調用持有靜態數據; [2] 
②不返回指向靜態數據的指針;所有數據都由函數的調用者提供; [2] 
③使用本地數據,或者通過製作全局數據的本地拷貝來保護全局數據; [2] 
④如果必須訪問全局變量,記住利用互斥信號量來保護全局變量; [2] 
⑤絕不調用任何不可重入函數。 [2] 
不可重入函數特徵: [2] 
①函數中使用了靜態變量,無論是全局靜態變量還是局部靜態變量; [2] 
②函數返回靜態變量; [2] 
③函數中調用了不可重入函數; [2] 
④函數體內使用了靜態的數據結構; [2] 
⑤函數體內調用了malloc()或者free()函數; [2] 
⑥函數體內調用了其他標準I/O函數; [2] 
⑦函數是singleton中的成員函數而且使用了未採用線程獨立存儲的成員變量。 [2] 
總的來説,如果一個函數在重入條件下使用了未受保護的共享資源,那麼它是不可重入的。 [2] 

線程安全線程安全意義

線程安全, 是指變量或方法( 這些變量或方法是多線程共享的) 可以在多線程的環境下被安全有效的訪問。這説明了兩方面的問題:
(1)可以從多個線程中調用, 無需調用方有任何操作;
(2)可以同時被多個線程調用, 無需線程之不必要的交互。 [1] 

線程安全舉例

比如一個 ArrayList 類,在添加一個元素的時候,它可能會有兩步來完成:1. 在 Items[Size] 的位置存放此元素;2. 增大 Size 的值。
單線程運行的情況下,如果 Size = 0,添加一個元素後,此元素在位置 0,而且 Size=1;
而如果是在多線程情況下,比如有兩個線程,線程 A 先將元素存放在位置 0。但是此時 CPU 調度線程A暫停,線程 B 得到運行的機會。線程B也向此 ArrayList 添加元素,因為此時 Size 仍然等於 0 (注意哦,我們假設的是添加一個元素是要兩個步驟哦,而線程A僅僅完成了步驟1),所以線程B也將元素存放在位置0。然後線程A和線程B都繼續運行,都增加 Size 的值。
那好,我們來看看 ArrayList 的情況,元素實際上只有一個,存放在位置 0,而 Size 卻等於 2。這就是“線程不安全”了。
參考資料
  • 1.    Java的多線程及安全性  .知網[引用日期2019-08-13]
  • 2.    張劍主編;丁鋒,周福才,於春剛副主編.軟件安全開發:電子科技大學出版社,2015.02:第240頁