3對象共享

同步的作用

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

volatile

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

對象發(fā)布與溢出

  1. 發(fā)布(Publish):使對象能夠在當前作用于之外的代碼中使用
  2. 溢出(Escape):當某個不應該發(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)部類包含了當前對象的隱含引用this牺氨,隨意發(fā)布匿名內(nèi)部類時也發(fā)布了自己
    //發(fā)布了new EventListener(),在該對象內(nèi)部包含自己的隱試引用this墩剖,即當前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);
                }
            });
            //尚未構造完成
            value = 10;
        }
        private void doSomething(Object o) {
            System.out.println(value);
        }
    }
    

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

安全的對象構造過程

  1. 不正確構造
    • 概念:如果this引用在對象構造過程中逸出歹篓,那么這種對象就被認為是不正確構造
    • 原因:當且僅當對象的構造函數(shù)返回時,對象才處于可預測的和一致的狀態(tài)揉阎。因此庄撮,當從構造函數(shù)中發(fā)布對象時,只是發(fā)布了一個尚未構造完成的對象毙籽;即使發(fā)布對象的語句位于構造函數(shù)的最后一行也是如此
    • 經(jīng)驗:不要在構造過程中使this引用逸出洞斯;即在構造中不要發(fā)布對象
  2. 常見不正確構造
    • 在構造函數(shù)中發(fā)布對象
    • 在構造函數(shù)中啟動線程
    • 在構造函數(shù)中調(diào)用當前類可改寫的實例方法(既不是私有方法,也不是最終方法)
  3. 解決不安全構造的方法
    /**
     * 用一個私有的構造函數(shù)和一個工友的工廠方法
     * 構造函數(shù)用來實例化對象
     * 工廠方法發(fā)布已經(jīng)構造完的this坑赡,并返回這個構造完的實例
     */
    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. 概念:當訪問共享的可變數(shù)據(jù)時烙如,通常需要同步;一種避免使用同步的方式就是不共享數(shù)據(jù)毅否;如果僅在單線程內(nèi)訪問數(shù)據(jù)亚铁,就不需要同步;這就叫線程封閉
  2. 作用:當某個對象封閉在一個線程中時螟加,這種用法將自動實現(xiàn)線程安全性徘溢,即使被封裝的對象本身不是線程安全的;線程封閉是實現(xiàn)線程安全的最簡單方式之一
  3. 線程封閉的方式
    1. Ad-hoc線程封閉:維護線程封閉性的職責完全由程序?qū)崿F(xiàn)來承擔
      • 由與Ad-hoc線程封閉技術的脆弱性捆探,因此在程序中盡量少用它然爆,在可能的情況下,應該使用更強的線程封閉技術(例如黍图,棧封閉或ThreadLocal類)
    2. 棧封閉:棧封閉是一種特殊的線程封閉曾雕,在棧封閉中將對象定義為局部變量,局部變量的固有屬性之一就是封閉在執(zhí)行線程中
      • 基本類型的局部變量始終封閉在線程內(nèi)雌隅,因為任何方法都無法獲得基本類型的引用
      • 引用類型的局部變量翻默,為了維持線程封閉缸沃,需要多做一些工作以確保被引用的對象不會溢出當前線程
    3. ThreadLocal

不變性

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

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

  1. 應用場景:需要對一組相關的數(shù)據(jù)以原子的方式執(zhí)行某個操作哄芜;不適用當前狀態(tài)依賴上一個操作時的狀態(tài)的場景,例如i++操作
  2. 方式:通過使用包含多個相關狀態(tài)變量的容器對象來維持不變性條件柬唯,并使用volatile類型的引用來確比想可見性,就能使該操作在沒有顯示地使用鎖的情況下仍然是線程安全的
  3. 示例
    /**
     * 注意:在構造和返回不變狀態(tài)對象時要和線程局部變量斷開關聯(lián)锄奢,尤其是引用變量
     * 下面注釋的兩個Arrays.copyOf操作就是
     */
    class OneValueCache {
        private final BigInteger lastNumber;
        private final BigInteger[] lastFactors;
        
        /**
         * 狀態(tài)由構造函數(shù)創(chuàng)建
         */
        public OneValueCache(BigInteger i, BigInteger[] factors) {
            lastNumber = i;
            //復制原因:防止factors被別的線程修改
            //防止線程用factors構造完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
                //復制原因:防止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);
                //構造新的不可變狀態(tài)對象
                cache = new OneValueCache(i, factors);
            }
            encodeIntoResponse(resp, factors);
        }
    }
    

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

  1. 前提:對象被正確構造
  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. 概念:如果對象從技術上來看是可變的盟萨,但其狀態(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ā)布的并且有某個特定的鎖保護的對象
最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末肤无,一起剝皮案震驚了整個濱河市先蒋,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌宛渐,老刑警劉巖竞漾,帶你破解...
    沈念sama閱讀 210,914評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異窥翩,居然都是意外死亡业岁,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,935評論 2 383
  • 文/潘曉璐 我一進店門寇蚊,熙熙樓的掌柜王于貴愁眉苦臉地迎上來笔时,“玉大人,你說我怎么就攤上這事仗岸≡使ⅲ” “怎么了?”我有些...
    開封第一講書人閱讀 156,531評論 0 345
  • 文/不壞的土叔 我叫張陵爹梁,是天一觀的道長右犹。 經(jīng)常有香客問我,道長姚垃,這世上最難降的妖魔是什么念链? 我笑而不...
    開封第一講書人閱讀 56,309評論 1 282
  • 正文 為了忘掉前任,我火速辦了婚禮积糯,結(jié)果婚禮上掂墓,老公的妹妹穿的比我還像新娘。我一直安慰自己看成,他們只是感情好君编,可當我...
    茶點故事閱讀 65,381評論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著川慌,像睡著了一般吃嘿。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上梦重,一...
    開封第一講書人閱讀 49,730評論 1 289
  • 那天兑燥,我揣著相機與錄音,去河邊找鬼琴拧。 笑死降瞳,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的蚓胸。 我是一名探鬼主播挣饥,決...
    沈念sama閱讀 38,882評論 3 404
  • 文/蒼蘭香墨 我猛地睜開眼除师,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了扔枫?” 一聲冷哼從身側(cè)響起汛聚,我...
    開封第一講書人閱讀 37,643評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎茧吊,沒想到半個月后贞岭,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,095評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡搓侄,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,448評論 2 325
  • 正文 我和宋清朗相戀三年瞄桨,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片讶踪。...
    茶點故事閱讀 38,566評論 1 339
  • 序言:一個原本活蹦亂跳的男人離奇死亡芯侥,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出乳讥,到底是詐尸還是另有隱情柱查,我是刑警寧澤,帶...
    沈念sama閱讀 34,253評論 4 328
  • 正文 年R本政府宣布云石,位于F島的核電站唉工,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏汹忠。R本人自食惡果不足惜淋硝,卻給世界環(huán)境...
    茶點故事閱讀 39,829評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望宽菜。 院中可真熱鬧谣膳,春花似錦、人聲如沸铅乡。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,715評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽阵幸。三九已至花履,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間挚赊,已是汗流浹背臭挽。 一陣腳步聲響...
    開封第一講書人閱讀 31,945評論 1 264
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留咬腕,地道東北人。 一個月前我還...
    沈念sama閱讀 46,248評論 2 360
  • 正文 我出身青樓葬荷,卻偏偏與公主長得像涨共,于是被迫代替她去往敵國和親纽帖。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,440評論 2 348

推薦閱讀更多精彩內(nèi)容

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