中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Java 多線程安全(一) 不共享與不可變

發布時間:2020-06-05 10:02:18 來源:網絡 閱讀:612 作者:wx5c78c8b1dbb1b 欄目:編程語言
  1. 線程不安全

  • 當一個類的狀態(指的存儲在狀態變量里面的數據)是共享的和可變時,那么這個類就是線程不安全的."共享"意味著變量可以由多個線程同時訪問,而"可變"意味著變量的值在生命周期發生變化.

線程安全

  • 在線程安全的定義中,最核心的概念就是正確性,正確性的含義是指:某個類的行為與其規范完全一致.線程安全定義如下:當多個線程訪問某個類時,不管運行時采用何種調度方式或者這些線程將如何交替運行,并且在主調代碼中不需要任何額外的同步或者協同,這個類都能表現出正確的行為,那么那么我們就稱這個類是線程安全的.

解決線程安全問題(一)

  • 不共享與不可變

    • 線程封閉(不共享數據):當訪問的共享的可變數據時,通常需要使用同步.一種避免使用同步的方式就是不共享.如果僅在單線程里面訪問內部數據,就不需要同步.這種技術被稱為線程封閉技術,它是實現線程安全性的最簡單方式之一.

  1. 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).如果某個對象在創建后其狀態就不能被修改,那么這個對象就是不可變對象.線程安全性是不可變對象的固有屬性之一.不可變對象一定是線程安全的.

  • 當滿足一下條件時,對象才是不可變的:

  1. 對象創建以后其狀態就不能修改.

  2. 對象的所有域都是final類型.

  3. 對象是正確創建的(在對象的創建期間,this引用沒有逸出).

Final 域

  1. final 類型的域是不能修改的(但如果final引用的對象是可變的,那么這些被引用的對象是可以修改的).在Java內存模型中,final域能夠確保初始化過程的安全性.即使對象是可變的,通過將對象的某些域聲明為final類型,仍然可以簡化對狀態的判斷.通過將域聲明為final類型,也相當于告訴維護人員這些域是不會變化的.

  2. 某些時候不可變對象提供了一種弱類型的原子性,如下代碼示例:

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關鍵字.)


向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

科技| 梁河县| 太保市| 莎车县| 府谷县| 桃园市| 常德市| 上饶县| 凌海市| 中江县| 阜平县| 舞钢市| 凤翔县| 灵寿县| 清流县| 辰溪县| 夹江县| 商南县| 太仆寺旗| 永靖县| 朔州市| 太康县| 广州市| 宣城市| 铜陵市| 海原县| 饶平县| 新泰市| 安新县| 堆龙德庆县| 昭苏县| 乌鲁木齐市| 五大连池市| 澳门| 高雄县| 思茅市| 桐庐县| 寿阳县| 分宜县| 南雄市| 华亭县|