您好,登錄后才能下訂單哦!
線程不安全
當一個類的狀態(指的存儲在狀態變量里面的數據)是共享的和可變時,那么這個類就是線程不安全的."共享"意味著變量可以由多個線程同時訪問,而"可變"意味著變量的值在生命周期發生變化.
線程安全
在線程安全的定義中,最核心的概念就是正確性,正確性的含義是指:某個類的行為與其規范完全一致.線程安全定義如下:當多個線程訪問某個類時,不管運行時采用何種調度方式或者這些線程將如何交替運行,并且在主調代碼中不需要任何額外的同步或者協同,這個類都能表現出正確的行為,那么那么我們就稱這個類是線程安全的.
解決線程安全問題(一)
不共享與不可變
線程封閉(不共享數據):當訪問的共享的可變數據時,通常需要使用同步.一種避免使用同步的方式就是不共享.如果僅在單線程里面訪問內部數據,就不需要同步.這種技術被稱為線程封閉技術,它是實現線程安全性的最簡單方式之一.
Ad-hoc 線程封閉
Ad-hoc 線程封閉是指,維護線程的封閉性的職責完全由程序來承擔.例如volatile變量上存在一種特殊的線程封閉,只要你能確保只有單個線程對共享的volatile的變量執行寫操作,那么就可以安全地在這些共享的volatile變量上執行"讀取-修改-寫入"的操作.在這種情況下,相當于變量封閉在單個線程中防止發生競態條件,并且volatile的變量的可見性保證還確保了其它線程能看見最新的值.由于Ad-hoc 線程封閉技術的脆弱性,沒有任何一種語言的特性是能將對象封閉到目標線程上,因此盡量少用,在可能的情況下,使用更強的封閉技術(棧封閉和ThreadLocal).
棧封閉(常用)
棧封閉是線程封閉的一種特例,在棧封閉中,只能通過局部變量才能訪問對象.正如封裝能使得代碼更容易維護不變性條件那樣,同步變量也能使對象更易于封閉在線程中.局部變量的固有屬性之一就是封閉在執行程序中.他們位于執行線程的棧中,其它線程無法訪問這個棧.棧封閉(也被稱為線程內部使用或者線程局部使用)比 Ad-hoc 線程更易于維護,也更加健壯.
如下代碼實例:
/** ?*?獲取user總數 ?*?@param?userList ?*?@return ?*/ public?int?getTotalUser?(List<User>?userList)?{ ????List<User>?userLists?=?null; ????int??totalUser?=?0; ????userLists?=?userList; ????for?(User?user?:?userList)?{ ????????totalUser?++; ????} ????return?totalUser; }
該方法userLists是一個局部變量,存在于每個線程的棧中,是每一個線程私有的,別的線程獲取不到,只要不把這個對象的發布出去,也就是返回,這樣這個userLists 閉在了這個線程棧中,就是線程安全的.而對于totalUser 這個基本類型來說,發布出去也沒有關系,因為由于任何線程都無法獲取對基本類型的引用,因此Java語言
的這種機制就確保了基本類型的局部變量始終封閉在線程內,也是線程安全的.
ThreadLocal類
維持線程封閉的一種更規范方法是使用ThreadLocal,這個類能使線程中的某個值與保存值的對象關聯起來.ThreadLocal類為每一個線程都維護了自己獨有的變量拷貝,競爭條件被徹底消除了,那就沒有任何必要對這些線程同步,他們也能最大限度的cpu調度,并發執行,并且有每個線程在訪問變量時,讀取和修改,都是自己獨有的那一份變量拷貝,變量就徹底封閉在了每個線程中,也就是線程安全的了,此方案是空間(內存)來換取線程安全的策略.
代碼示例:多線程獲取數據庫連接.
public?class?ConnectionUtils?{ ????private?static?ThreadLocal<Connection>?connectionThreadLocal ????????????=?new?ThreadLocal<Connection>(){ ????????protected??Connection?initialValue?()?{ ????????????Connection?connection?=?null; ????????????try?{ ????????????????Class.forName("org.postgresql.Driver").newInstance(); ????????????????connection?=?DriverManager.getConnection ????????????????("jdbc:postgresql://localhost:5432/postgres", ????????????????????????"postgres",?"test"); ????????????}?catch?(Exception?e)?{ ????????????????e.printStackTrace(); ????????????} ????????????return?connection; ????????} ????}; ????public?static?Connection?getConnection?()?{ ????????return?connectionThreadLocal.get(); ????} ????public?static?void?main?(String[]?args)?throws?Exception?{ ????????for?(int?i?=?0;?i?<?2;?i++)?{ ????????????Thread?thread?=?new?Thread(()?->?{ ????????????????Connection?connection?=?ConnectionUtils.getConnection(); ????????????????System.out.println(Thread.currentThread().getName()?+? ????????????????"--------"?+?connection.toString()); ????????????},?"thread"?+?i); ????????????thread.start(); ????????} ????} }
thread0--------org.postgresql.jdbc4.Jdbc4Connection@4fce58ae
thread1--------org.postgresql.jdbc4.Jdbc4Connection@257f7c5b
通過代碼可以看見兩個線程獲取了各自的連接對象,都是綁定在當前線程上的,第一次獲取是調用initialValue這個方法的返回值來設定值的,如果調用set方法也會和當前
線程綁定.ThreadLocal源碼實現分析參考:敬請期待Smile
不可變的對象
滿足同步需求的另一種方法是使用不可變對象 (Immutable Object).如果某個對象在創建后其狀態就不能被修改,那么這個對象就是不可變對象.線程安全性是不可變對象的固有屬性之一.不可變對象一定是線程安全的.
當滿足一下條件時,對象才是不可變的:
對象創建以后其狀態就不能修改.
對象的所有域都是final類型.
對象是正確創建的(在對象的創建期間,this引用沒有逸出).
Final 域
final 類型的域是不能修改的(但如果final引用的對象是可變的,那么這些被引用的對象是可以修改的).在Java內存模型中,final域能夠確保初始化過程的安全性.即使對象是可變的,通過將對象的某些域聲明為final類型,仍然可以簡化對狀態的判斷.通過將域聲明為final類型,也相當于告訴維護人員這些域是不會變化的.
某些時候不可變對象提供了一種弱類型的原子性,如下代碼示例:
public?class?OneValueCache?{ ????private?final?BigInteger?lastNumber; ????private?final?BigInteger[]?lastFactors; ????public?OneValueCache?(BigInteger?i?,?BigInteger[]?fastFactors)?{ ????????lastNumber?=?i; ????????lastFactors?=?Arrays.copyOf(fastFactors,fastFactors.length); ????} ????public?BigInteger[]?getFactors?(BigInteger?i)?{ ????????if?(lastNumber?==?null?||?!lastNumber.equals(i))?{ ????????????return?null; ????????}?else?{ ????????????return?Arrays.copyOf(lastFactors,lastFactors.length); ????????} ????} ? }
代碼分析:OneValueCache 有兩個final 域的變量,并在構造函數時初始化它們(沒有提供其它初始化數據方案,因為要保證初始化后狀態的不可變),在getFactors 方法里面沒有返回原數組引用,如果這樣那就不安全了因為lastFactors數組的域是不可變的,但是引用對應的內容是可以修改的,所以要是有copyOf方法,返回一個新數組(也可以使用clone方法).如果我們要修改lastNumber和lastFactors只有調用構造方法重新構造一個不可變對象,而構造對象需要這兩個變量一起傳入,要么成功要么失敗,所以說不可變對象是一種弱類型的原子性.
對于訪問和更新多個相關變量時出現的競爭問題,可以通過將這些變量全部保存在一個不可變對象中來消除.如果是一個可變對象,那么就必須使用鎖來確保原子性.如果是一個不可變對象,那么當前獲得了帶對象的引用后,就不必擔心另一個線程會修改對象的狀態.如果要更新這些變量,那么只有重新建一個新的容器對象,但其他使用原有對象的線程仍然看到對象處于一致狀態(其它線程看見的還是原來的對象,如果要保證可見性,可以使用volatile關鍵字.)
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。