併發安全(一) 如何保證執行緒安全

2021-09-27 12:20:13 字數 2998 閱讀 4565

我們在併發程式設計中,首先不可避免的就是如何保證在併發中的執行緒安全問題。我們之前說過太多的併發工具類、併發容器等。這裡我們就分析我們在併發程式設計中,看看在哪些方面才能保證我們的執行緒安全。

首先我們在併發中,一般都是對於共享資源等進行操作,可能會有執行緒安全的問題,那我們我們就不使用共享資源,是不是就沒有執行緒安全的問題呢?對的,當我們乙個類的資源,不對其他的執行緒進行共享,肯定就不會有執行緒安全問題了。

public

class

client

}

比如我們的******dateformat在多執行緒下就是不安全的,如上我們在併發程式設計中,就很容易出現問題,那麼最簡單的就是將其放在我們的方法內呀。

這裡就可以保證我們的執行緒安全了,還有人認為我們 new 出乙個物件,不應該存在在堆記憶體中麼,沒錯是在堆記憶體中,我們方法棧中存在是對這個物件的引用,那麼在堆記憶體中不就意味著所有物件都可以訪問麼?

是的,堆記憶體中是所有執行緒共享的,但是其他執行緒不知道這個物件在堆記憶體中的位址呀。只要我們不把這個物件給返回出去就是執行緒安全的。

無狀態是什麼意思呢,很簡單,就是沒有任何成員變數的類,它就可以稱為無狀態的類。

上面在棧封閉中,提到乙個類中的區域性變數是執行緒安全的,那我們都使用區域性變數,不使用成員變數,就是無狀態的類。如下。

public

class

client

}

還有上面我們將******dateformat放入了方法內,也是乙個無狀態類。

無狀態類可以保證其執行緒安全,但是我們發現我們實際的工作中,這種肯定非常少吧,那我們要是有成員變數,需要如何保證其執行緒安全呢?

public

class

client

public

inttest

(int a,

int b)

}

我們使用了 final 進行修飾,保證其變數的不可變,要注意的是 final 修飾後,我們一定需要給其賦乙個初始值,我們可以給其設值乙個預設值,或者通過建構函式初始化賦值。賦完值後,我們就無法修改了,所以我們就無法有無參構造方法,或者 setter 方法了。

那麼加上了 final 修飾一定是不可以修改的麼?我們要清楚的是我們 final 修飾後,不變的是引用,而不是值。

public

class

client

}class

dog//省略setter/getter方法..

.}

我們在上述的 dog 成員變數中加上了 final 進行修飾,但是執行緒安全麼,不安全,因為我們 final 是引用不安全,它指的是我們 dog 無法變成其第一次賦值的引用物件,但是我們還可以改變 dog 其中的值,如 age,當其在獲得 dog 物件裡面的變數值,另乙個變數又來修改,肯定造成執行緒安全問題了。

我們現在看看如何解決,最簡單的 dog 類就是加上 final 關鍵字,那麼其 setter 方法就不能有了。

所以我們在程式中,主要有可能,即我們可以保證這個變數如果在賦值後就不需要改變時,我們就可以給其加上 final 關鍵字。

上面我們說過了在一定的情況下,我們可以使用 final 關鍵字,我們可以其引用不可變,另外除了 final 關鍵字,我們還可以直接不提供修改的方法。

public

class

client

public

boolean

iscontains

(string value)

}

這個類我們既沒有使用 final 關鍵字修飾,同時 arraylist 就是乙個執行緒不安全的容器,那麼它會有執行緒安全問題麼?

不會,因為我們壓根就沒有提供任何修改的方法,我們使用了 private 僅本類可見,所以它就是執行緒安全的。

這裡我們不能把這個 list 直接返回出來,因為讓其他的執行緒能夠拿到乙個 list 的引用,它們就可以對其進行修改了。同樣成員變數需使用 private,使其不可對其他類可見。

保證類的可見性,最適合乙個執行緒寫,多個執行緒讀的情景。這個關鍵字我們之前已經詳細介紹過,這裡就不在贅述。

至於我們對程式進行加鎖,或者使用其cas原子操作。這裡我們之前也以及詳細介紹,也不再贅述了。

這個其實就是我們上面說過的問題,如下

public

class

client

public

boolean

iscontains

(string value)

public list getlist()

}

雖然我們這個類中,沒有存在 setter 方法,我們還使用了 private 進行修飾,甚至我們還可以加上 final 關鍵字修飾。

但是我們通過 getlist 方法將 list 的引用發布出去了,這裡會讓別的執行緒可以拿到這個 list 應用,它雖然無法修改其引用,但是可以修改其中的值呀。

那我們希望要把值返回出去,怎麼辦呢?我們可以進行複製乙份,將複製的資料給它,而不是將其引用發布出去,發布的其實乙個副本。

或者就是更換安全的容器,如我們針對 arraylist 是執行緒不安全的,那麼我們就用 vector 之類的執行緒安全容器來替換它。

上述提到了,安全發布的問題,我們可以複製乙份副本發布出去,看到副本,是不是就想到我們的 threadlocal 了,我們可以使用 threadlocal 為每份執行緒提供了乙個份副本,這樣每個執行緒都在自己的副本內進行操作,避免了其執行緒安全問題。關於 threadlocal 之前已有介紹。

如何保證執行緒安全?

執行緒安全 一般說來,確保執行緒安全的方法有這幾個 競爭與原子操作 同步與鎖 可重入 過度優化。競爭與原子操作 多個執行緒同時訪問和修改乙個資料,可能造成很嚴重的後果。出現嚴重後果的原因是很多操作被作業系統編譯為彙編 之後不止一條指令,因此在執行的時候可能執行了一半就被排程系統打斷了而去執行別的 了...

如何保證Java執行緒安全

不要跨執行緒訪問共享變數 使共享變數是final型別的 使共享變數唯讀 將共享變數的操作加上同步 對於volatile宣告的數值型別變數進行運算,往往是不安全的 volatile只能保證可見性,不能保證原子性 使用普通同步容器 vector,hashtable 的迭代器,需要外部鎖來保證其原子性。原...

如何保證ArrayList執行緒安全

一 繼承arraylist,然後重寫或按需求編寫自己的方法,這些方法要寫成synchronized,在這些synchronized的方法中呼叫arraylist的方法。二 使用collections.synchronizedlist 使用方法如下 假如你建立的 如下 list data new ar...