併發安全 【1】什麼是類的線程安全? 當多個線程訪問某個類時,不管運行時環境採用何種調度方式或者這些線程將如何交替執行,並且在調用代碼中不需要任何額外的同步或者協同,這個類都能表現出正確的行為,那麼就稱這個類是線程安全的。 【2】線程不安全引發的問題 死鎖 死鎖是指兩個或兩個以上的進程在執行過程 ...
併發安全
【1】什麼是類的線程安全?
當多個線程訪問某個類時,不管運行時環境採用何種調度方式或者這些線程將如何交替執行,並且在調用代碼中不需要任何額外的同步或者協同,這個類都能表現出正確的行為,那麼就稱這個類是線程安全的。
【2】線程不安全引發的問題
死鎖
死鎖是指兩個或兩個以上的進程在執行過程中,由於競爭資源或者由於彼此通信而造成的一種阻塞的現象,若無外力作用,它們都將無法推進下去。此時稱系統處於死鎖狀態或系統產生了死鎖。
解決:保證加鎖的有序性
動態順序死鎖
動態順序死鎖是在實現時按照某種順序加鎖了,但是因為外部調用的問題,導致無法保證加鎖順序而產生的。
解決方法:
1)通過內在排序,保證加鎖的順序性(使用identityHashCode獲得原生的hashcode,按照這個的hashcode大小制定加鎖順序);
2)通過顯示鎖tryLock()進行嘗試拿鎖
活鎖
嘗試拿鎖的機制中,發生多個線程之間互相謙讓,不斷發生拿鎖,釋放鎖的過程。
解決辦法:每個線程休眠隨機數,錯開拿鎖的時間。
線程饑餓
【3】怎麼才能做到類的線程安全?
棧封閉
所有的變數都是在方法內部聲明和使用,這些變數都處於棧封閉狀態。
無狀態類
沒有任何成員變數的類,就叫無狀態的類
讓類不可變
讓類不可變有兩種方法:
- 加final關鍵字,對於一個類,所有的成員變數應該是私有的,同樣的只要有可能,所有的成員變數應該加上final關鍵字,但是加上final,要註意如果成員變數又是一個對象的引用時,這個對象所對應的類也要是不可變,才能保證整個類是不可變
- 不提供修改成員變數的方法,同時成員變數也不作為方法的返回值
volatile
保證類的可見性,最適合一個線程寫,多個線程讀的情景
對於寫,可以使用Synchronized加鎖等效為一個線程寫
加鎖和CAS
詳情見往期博客
安全的發佈
類中持有的成員變數,特別是對象的引用,如果這個成員對象不是線程安全的,通過get等方法發佈出去,會造成這個成員對象本身持有的數據在多線程下不正確的修改,從而造成整個類線程不安全的問題。
解決方法:
- 用線程安全的容器替換
- 返回副本,深拷貝
- 加鎖
TheadLocal
詳情見往期博客
【4】線程安全的單例模式
餓漢式
- 在聲明的時候就new這個類的實例,因為在JVM中,對類的載入和類初始化由虛擬機保證線程安全。
public class SingletonHungry {
public static SingletonHungry singletonHungry = new SingletonHungry();
private SingletonHungry(){}
}
- 或者使用枚舉。
懶漢式
在單例類的內部由一個私有靜態內部類來持有這個單例類的實例。
public class SingletonLazy {
private SingletonLazy(){}
//定義一個私有類,來持有當前類的實例
private static class InstanceHolder{
public static SingletonLazy instance = new SingletonLazy();
}
public static SingletonLazy getInstance(){
return InstanceHolder.instance;
}
}