3對象共享

同步的作用

  • 確保復(fù)合操作的原子性(復(fù)合操線程間作互斥)
  • 內(nèi)存可見性

volatile

  1. 作用:將當(dāng)前線程對volatile的改變立即通知給其他線程;保證了volatile變量對線程的可見性;volatile是一種比synchronizyed稍弱的同步機制
  2. 對可見性的影響:volatile變量對可見性的影響比volatile變量本身更為重要。當(dāng)線程A首先寫入一個volatile變量并且線程B隨后讀取該變量時,在寫入volatile變量之前對A可見的所有變量(包括volatile變量)的值,在B讀取了volatile變量后,對B也是可見的。因此,從內(nèi)存可見性的角度來看,寫入volatile變量相當(dāng)于退出同步代碼塊,而讀取volatile變量就相當(dāng)于進入同步代碼塊
  3. 典型用法:檢查某個狀態(tài)標(biāo)記以判斷是否退出循環(huán)
  4. 注意:volatile的語義不足以確保遞增操作(count++)的原子性,除非你能確保只有一個線程對變量執(zhí)行寫操作;原子變量提供了“讀-改-寫”的原子操作,并且嘗嘗做一種“更好的volatile變量”;即volatile只能確??梢娦?,而加鎖機制既可以確??梢娦杂挚梢源_保原子性
  5. 使用volatile變量的條件
    • 對變量的寫入操作不依賴變量的當(dāng)前值,或者你能確定只有單個線程更新變量的值
    • 該變量不會與其他變量一起納入不變性條件中
    • 在訪問變量時不需要加鎖

對象發(fā)布與溢出

  1. 發(fā)布(Publish):使對象能夠在當(dāng)前作用于之外的代碼中使用
  2. 溢出(Escape):當(dāng)某個不應(yīng)該發(fā)布的對象被發(fā)布時就成為溢出
  3. 發(fā)布方式
    //方式一:將指向?qū)ο蟮囊帽4娴狡渌a能訪問到的地方
    //發(fā)布了new HashSet<Secret>()對象
    public static Set<Secret> knowSecrets;
    public void initialize() {
        konwnSecretets = new HashSet<Secret>();
    }
    
    //方式二:在某一個非私有的方法中返回對象引用
    //發(fā)布了new HashSet<Secret>()對象
    public Set<Secret> getSecrets() {
        return new HashSet<Secret>();
    }
    
    //方式三:將對象的引用傳遞到其他類的方法中
    //發(fā)布了new User()對象
    public class Caculate {
        public static Object caculate(Object o) {
            System.out.println(o);
            return o;
        }
    }
    Caculate.caculate(new User());
    
    //方式四:在已發(fā)布的對象中的非私有域中引用對象
    //發(fā)布了"AK","AL"
    class UnsafeStates {
        private String[] states = new String[] {"AK", "AL"};
        public String[] getStates() {
            return states;
        }
    }
    
    //方式五:在類的方法內(nèi)發(fā)布匿名內(nèi)部類;因為匿名內(nèi)部類包含了當(dāng)前對象的隱含引用this,隨意發(fā)布匿名內(nèi)部類時也發(fā)布了自己
    //發(fā)布了new EventListener(),在該對象內(nèi)部包含自己的隱試引用this,即當(dāng)前ThisEscape對象
    public class ThisEscape {
        private int value;
        public ThisEscape(EventSource source) {
            //此處已經(jīng)將ThisEscape發(fā)布給了外部的source,存在逃逸現(xiàn)象
            source.registerListener(new EventListener() {
                public void onEvent(Event e) {
                    doSomething(e);
                }
            });
            //尚未構(gòu)造完成
            value = 10;
        }
        private void doSomething(Object o) {
            System.out.println(value);
        }
    }
    

4.對象發(fā)布的風(fēng)險:無論其他線程會對已發(fā)布的引用執(zhí)行何種操作,其實都不重要,因為誤用該引用的風(fēng)險始終存在。當(dāng)某個對象逸出后,你必須假設(shè)有某各類或者線程可能會誤用該對象。這正是需要使用封裝的最主要原因:封裝能夠使得對程序的正確性分析變得可能,并使得無意中破壞設(shè)計約束條件變得更難

安全的對象構(gòu)造過程

  1. 不正確構(gòu)造
    • 概念:如果this引用在對象構(gòu)造過程中逸出,那么這種對象就被認(rèn)為是不正確構(gòu)造
    • 原因:當(dāng)且僅當(dāng)對象的構(gòu)造函數(shù)返回時,對象才處于可預(yù)測的和一致的狀態(tài)。因此,當(dāng)從構(gòu)造函數(shù)中發(fā)布對象時,只是發(fā)布了一個尚未構(gòu)造完成的對象;即使發(fā)布對象的語句位于構(gòu)造函數(shù)的最后一行也是如此
    • 經(jīng)驗:不要在構(gòu)造過程中使this引用逸出;即在構(gòu)造中不要發(fā)布對象
  2. 常見不正確構(gòu)造
    • 在構(gòu)造函數(shù)中發(fā)布對象
    • 在構(gòu)造函數(shù)中啟動線程
    • 在構(gòu)造函數(shù)中調(diào)用當(dāng)前類可改寫的實例方法(既不是私有方法,也不是最終方法)
  3. 解決不安全構(gòu)造的方法
    /**
     * 用一個私有的構(gòu)造函數(shù)和一個工友的工廠方法
     * 構(gòu)造函數(shù)用來實例化對象
     * 工廠方法發(fā)布已經(jīng)構(gòu)造完的this,并返回這個構(gòu)造完的實例
     */
    public class SafeListener {
        private final EventListener listener;
        
        private SafeListener() {
            listener = new EventListener() {
                public void onEvent(Event e) {
                    doSomething(e);
                }
            };
        }
        
        public static SafeListener newInstance(EventSource source) {
            SafeListener safe = new SafeListener();
            source.registerListener(safe.listener);
            return safe;
        }
    }
    

線程封閉

  1. 概念:當(dāng)訪問共享的可變數(shù)據(jù)時,通常需要同步;一種避免使用同步的方式就是不共享數(shù)據(jù);如果僅在單線程內(nèi)訪問數(shù)據(jù),就不需要同步;這就叫線程封閉
  2. 作用:當(dāng)某個對象封閉在一個線程中時,這種用法將自動實現(xiàn)線程安全性,即使被封裝的對象本身不是線程安全的;線程封閉是實現(xiàn)線程安全的最簡單方式之一
  3. 線程封閉的方式
    1. Ad-hoc線程封閉:維護線程封閉性的職責(zé)完全由程序?qū)崿F(xiàn)來承擔(dān)
      • 由與Ad-hoc線程封閉技術(shù)的脆弱性,因此在程序中盡量少用它,在可能的情況下,應(yīng)該使用更強的線程封閉技術(shù)(例如,棧封閉或ThreadLocal類)
    2. 棧封閉:棧封閉是一種特殊的線程封閉,在棧封閉中將對象定義為局部變量,局部變量的固有屬性之一就是封閉在執(zhí)行線程中
      • 基本類型的局部變量始終封閉在線程內(nèi),因為任何方法都無法獲得基本類型的引用
      • 引用類型的局部變量,為了維持線程封閉,需要多做一些工作以確保被引用的對象不會溢出當(dāng)前線程
    3. ThreadLocal

不變性

  1. 不可變對象:如果某個對象在被創(chuàng)建后其狀態(tài)就不能被修改,那么這個對象就成為不可變對象
  2. 不可變對象固有屬性:線程安全性是不可變對象的固有屬性之一,他們的不變性條件是由構(gòu)造函數(shù)創(chuàng)建的,只要他們的狀態(tài)不改變,那么這些不變性條件就能得以維持
  3. 不可變對象的條件
    1. 對象創(chuàng)建以后其狀態(tài)就不能修改
    2. 對象的所有域都是final類型
    3. 對象是正確構(gòu)造的(在對象構(gòu)造期間,this引用沒有逸出)
  4. 習(xí)慣:正如“除非需要更高的可見性,否則應(yīng)將所有的域都聲明為私有域”是一個良好的編程習(xí)慣,“除非需要某個域是可變的,否則應(yīng)將其聲明為final域”也是一個良好的編程習(xí)慣
  5. final域可見性詳解: http://www.infoq.com/cn/articles/java-memory-model-6

使用volatile類型來發(fā)布不可變對象

  1. 應(yīng)用場景:需要對一組相關(guān)的數(shù)據(jù)以原子的方式執(zhí)行某個操作;不適用當(dāng)前狀態(tài)依賴上一個操作時的狀態(tài)的場景,例如i++操作
  2. 方式:通過使用包含多個相關(guān)狀態(tài)變量的容器對象來維持不變性條件,并使用volatile類型的引用來確??梢娦?,就能使該操作在沒有顯示地使用鎖的情況下仍然是線程安全的
  3. 示例
    /**
     * 注意:在構(gòu)造和返回不變狀態(tài)對象時要和線程局部變量斷開關(guān)聯(lián),尤其是引用變量
     * 下面注釋的兩個Arrays.copyOf操作就是
     */
    class OneValueCache {
        private final BigInteger lastNumber;
        private final BigInteger[] lastFactors;
        
        /**
         * 狀態(tài)由構(gòu)造函數(shù)創(chuàng)建
         */
        public OneValueCache(BigInteger i, BigInteger[] factors) {
            lastNumber = i;
            //復(fù)制原因:防止factors被別的線程修改
            //防止線程用factors構(gòu)造完OneValueCache之后修改factors,不會影響緩存數(shù)據(jù)lastFactors
            lastFacotrs = Arrays.copyOf(factors, factors.length);
        }
        
        public BigInteger[] getFactors(BigInteger i) {
            if(lastNumber == null || !lastNumber.equals(i))
                return null;
            else
                //復(fù)制原因:防止lastFacors被別的線程修改
                //返回一個新的結(jié)果數(shù)組給線程,線程想怎么處理怎么處理,不會影響緩存lastFactors
                return Arrays.copyOf(lastFacors, lastFactors.length);
        }
    }
    
    public class VolatileCachedFactorizer implements Servlet {
        private volatile OneValueCache cache = new OneValueCache(null, null);
        
        public void service(ServletRequest req, ServletResponse resp) {
            BigInteger i = exractFromRequest(req);
            //結(jié)果從不可變狀態(tài)對象獲取
            BigInteger factors = cache.getFactors(i);
            if(factors == null) {
                factors = factor(i);
                //構(gòu)造新的不可變狀態(tài)對象
                cache = new OneValueCache(i, factors);
            }
            encodeIntoResponse(resp, factors);
        }
    }
    

安全發(fā)布對象的方法

  1. 前提:對象被正確構(gòu)造
  2. 安全發(fā)布機制:
    1. 在靜態(tài)代碼塊或者靜態(tài)域中初始化一個對象的引用
      • 靜態(tài)初始化器有JVM在類的初始化階段執(zhí)行。由與在JVM內(nèi)部存在著同步機制,因此通過這種方式初始化的任何對象都可以被安全地發(fā)布
    2. 將對象的引用保存到volatile類型的域
    3. 將對象的引用保存到AtomicReference類型的域中
    4. 將對象的引用保存到final類型的域中
    5. 將對象的引用保存到一個由鎖保護的域中
    6. 通過線程安全庫的容器類發(fā)布對象
  3. 線程安全庫提供的安全發(fā)布保證
    1. 通過將一個鍵或者值放入Hashtable、synchronizecdMap或者ConcurrentMap中,可以安全地將它發(fā)布給任何從這些容器中訪問它的線程(無論是直接訪問還是通過迭代器訪問)
    2. 通過將某個元素放入Vector、CopyOnWriteArrayList、CopyOnWriteArraySet、synchronizedList或synchronizedSet中,可以將元素安全地發(fā)布到任何從這些容器中訪問元素的線程
    3. 通過將某個元素放入BlockingQueue或者ConcurrentLinkedQueue中,可以將元素安全地發(fā)布到任何從這些隊列中訪問元素的線程
    4. 類庫中的其他數(shù)據(jù)傳遞機制(例如Futrue和Exchanger)同樣能實現(xiàn)安全發(fā)布

事實不可變對象

  1. 概念:如果對象從技術(shù)上來看是可變的,但其狀態(tài)在發(fā)布后不會再改變,那么這種對象稱為“事實不可變對象(Effectively Immutable Object)”
  2. 結(jié)論1:在沒有額外同步的情況下,任何線程都可以安全地使用被安全發(fā)布的事實不可變對象
  3. 結(jié)論2:通過使用事實不可變對象,不僅可以簡化開發(fā)過程,而且還能由于減少了同步而提高性能

對象的發(fā)布需求取決于它的可變性

  1. 不可變對象可以通過任意機制來發(fā)布
  2. 事實不可變對象必須通過安全的發(fā)布機制來發(fā)布
  3. 可變對象必須通過安全的發(fā)布機制來發(fā)布,并且必須是線程安全的或者有某個鎖保護起來

并發(fā)編程中共享對象的一些使用策略

  1. 線程封閉:線程封閉的對象只能由一個線程擁有,對象被封閉在該線程中,并且只能由這個線程修改
  2. 只讀共享:在沒有額外同步的情況下,共享的只讀對象可以有多個線程并發(fā)訪問,但任何線程都不能修改它。共享的只讀對象包括不可變對象和事實不可變對象
  3. 線程安全共享:線程安全的對象在其內(nèi)部實現(xiàn)同步,因此多個線程可以通過對象的公有接口進行訪問而不需要進一步的同步
  4. 保護對象:被保護的對象只能通過持有特定的鎖來訪問。保護對象包括封裝在其他安全對象中的對象,以及已發(fā)布的并且有某個特定的鎖保護的對象
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 多線程之 Final變量 詳解 原文: http://www.tuicool.com/articles/2Yjmq...
    朦朧蜜桃閱讀 1,463評論 0 2
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對...
    cosWriter閱讀 11,680評論 1 32
  • vue學(xué)習(xí)及源碼分析 https://segmentfault.com/a/1190000006747096 ht...
    black白先森閱讀 1,478評論 0 4
  • 終于按耐不住心里的渴望,叫了輛專車奔赴現(xiàn)場——我的母校今天120歲生日!我真的是很想很想去,大半個城市的距離也擋不...
    討討閱讀 737評論 2 1
  • 要畢業(yè)的時候,空氣中彌漫著的是各種不明所以的氣息,莫名的,有興奮,有焦慮,有傷感,以及無奈,我不知道我們還...
    沉念棠閱讀 338評論 0 1

友情鏈接更多精彩內(nèi)容