Java Concurrency In Practice 第三章讀書筆記

除了保證操作的原子性以外盹牧,同步還可以保證變量在不同線程之間的內(nèi)存可見性通危。原子性和可見性共同構(gòu)成了同步的兩個核心要素熄守。第三章主要講述如何在線程之間安全的發(fā)布和共享變量蜈垮。

首先可以通過書上的例子來看一下什么是可見性。

public class NoVisibility { 
 private static boolean ready; 
 private static int number; 
 private static class ReaderThread extends Thread { 
 public void run() { 
 while (!ready) 
 Thread.yield(); 
 System.out.println(number); 
 } 
 } 
 public static void main(String[] args) { 
 new ReaderThread().start(); 
 number = 42; 
 ready = true; 
 } 
} 

表面上我們啟動了兩個線程裕照,一個線程對ready flag進(jìn)行無限期的檢查攒发,而另一個線程則對number和ready進(jìn)行改變。從邏輯上說晋南,我們可能會期待屏幕上打印出42.但是實(shí)際上由于可見性問題晨继,另一個線程可能永遠(yuǎn)也看不到ready的值,因?yàn)镴MM中線程都擁有自己的工作內(nèi)存搬俊,并且只能對自己的工作內(nèi)存進(jìn)行存取紊扬,工作內(nèi)存中保存的其實(shí)是主內(nèi)存的一個副本。也有可能JVM會對指令進(jìn)行重排序優(yōu)化唉擂,盡管在單線程下會保證得到的最終結(jié)果是我們期待的邏輯結(jié)果(as-if-serial)餐屎,但是其指令的執(zhí)行過程不一定是聲明的順序。所以上面有可能是先對ready進(jìn)行了置位玩祟,導(dǎo)致之后打印出了零腹缩。在多線程中如果沒有進(jìn)行同步或者volatile聲明(在缺乏同步的程序中),就不能對指令的執(zhí)行順序抱有期待空扎。

可見性問題會導(dǎo)致一個問題那就是其他線程得到的數(shù)據(jù)可能是已經(jīng)過期的藏鹊,但其他線程卻一無所知。所以在可見性上java提供了volatile關(guān)鍵字來進(jìn)行可見性的保證转锈。如果將一個變量聲明為volatile盘寡,jvm會在寫入時將線程工作內(nèi)存的最新值拷貝回主內(nèi)存保證值是最新的,以及每次使用時都要刷新主內(nèi)存的值撮慨,并且所有與volatile變量相關(guān)的語句都將禁止重排序竿痰。volatile雖然可以保證可見性,卻不能保證操作的原子性砌溺。

總的來說影涉。volatile作為更加輕量化的線程安全手段,適用的范圍比同步更加有限规伐。書上說:
1.寫入的操作不應(yīng)該依賴于當(dāng)前的值蟹倾,因?yàn)関olatile無法保證原子性。
2.該變量沒有被納入其他變量的不變式關(guān)系之中猖闪,也就是說他是獨(dú)立的鲜棠。
3.訪問時不需要加鎖。

只有都滿足上面三個條件時萧朝,volatile才適用岔留。

發(fā)布指的是發(fā)布一個對象(實(shí)質(zhì)上是發(fā)布對對象的引用)使得對象可以再當(dāng)前作用域之外使用。發(fā)布是會破壞封裝性的检柬。當(dāng)一個不應(yīng)發(fā)布的對象被發(fā)布時就產(chǎn)生了溢出問題献联。發(fā)布一個對象到外部的最簡單的方法是使用一個公有的static變量來保存發(fā)布對象。

public static Set<Secret> knownSecrets; 
public void initialize() { 
 knownSecrets = new HashSet<Secret>(); 
} 

通過一個公有的靜態(tài)變量指向我們新建的HashSet何址,其他線程得到了新建HashSet的引用里逆。

在發(fā)布對象時,可能會間接地發(fā)布其他對象用爪。例如我發(fā)布一個hashset原押,那么hashset里面的值便也被間接的發(fā)布了。

有一種逸出是對于可變的private對象偎血,private訪問權(quán)限將這個對象封裝到當(dāng)前類诸衔,如果將其發(fā)布到外部便使得其他線程得到了關(guān)于一個我們希望是pirvate對象的引用盯漂,這違背了使用private原有的語義。

class UnsafeStates { 
 private String[] states = new String[] { 
 "AK", "AL" ... 
 }; 
 public String[] getStates() { return states; } 
} 

另一種比較隱晦的發(fā)布其他對象的例子是內(nèi)部類笨农,由于內(nèi)部類中保存著對于outerclass的外部應(yīng)用就缆,當(dāng)我發(fā)布一個innnerclass時會間接的發(fā)布外部類的引用,可能會造成意想不到的結(jié)果谒亦。

public class ThisEscape { 
 public ThisEscape(EventSource source) { 
 source.registerListener( 
 new EventListener() { 
 public void onEvent(Event e) { 
 doSomething(e); 
 } 
 }); 
 } 
} 

最后是動態(tài)的construct初始化過程竭宰,如果我們在constructor的構(gòu)造過程中向外發(fā)布this引用,那么發(fā)布出去的對象很有可能是部分構(gòu)造的份招,即使是發(fā)布代碼是最后一行切揭,由于重排序也有可能是部分構(gòu)造的。所以對象的this引用只有在完成constructor的構(gòu)造之后才可以發(fā)布锁摔。
一個比較常見的錯誤是在構(gòu)造體內(nèi)啟動一個線程廓旬,由于線程是共享this引用的,所以啟動的線程可以看到未完全構(gòu)造好的對象鄙漏。所以在線程的啟動方法一般是start嗤谚。

對于這種情況,作者的建議是如果要對外發(fā)布對象,那么使用靜態(tài)的工廠方法來完成這個操作怔蚌。

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; 
  } 
} 

還有其他技術(shù)可以完成線程安全巩步。例如線程封閉將對象封閉在一個線程中,這樣無需其他同步手段就是線程安全的桦踊。1.stack封閉.由于stack是線程私有的椅野,被封閉在stack內(nèi)的對象自然只有當(dāng)前線程可以訪問。2.使用ThreadLocal類籍胯。3.在線程內(nèi)發(fā)布不可變對象竟闪,他們一定是線程安全的。滿足下列三條的是不可變對象:1杖狼。對象創(chuàng)建之后就不可變炼蛤。2.域都是final類型3.在對象構(gòu)造的過程中其this引用沒有逸出。

例如這種發(fā)布將public引用發(fā)布蝶涩,由于不可見性理朋,其他線程不一定可以看到最新的對象。靜態(tài)初始化和動態(tài)初始化不同绿聘,靜態(tài)初始化是JVM在類的load階段初始化的嗽上,JVM可以保證內(nèi)部的同步機(jī)制不出錯。

// Unsafe publication 
public Holder holder; 
public void initialize() { 
 holder = new Holder(42); 
} 

但是我們可以知道的是熄攘,對于一個對象兽愤,即便將這個對象的引用發(fā)布到其他線程,它的狀態(tài)也不一定是對于其他線程可見的。因?yàn)槠渌€程只能通過私有的對象公有API來訪問對象浅萧。所以即便我們發(fā)布了一個對象到其他線程逐沙,那么通過同步API方法和同步發(fā)布過程(安全發(fā)布),同樣可以讓這個對象是線程安全的惯殊。~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末酱吝,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子土思,更是在濱河造成了極大的恐慌,老刑警劉巖忆嗜,帶你破解...
    沈念sama閱讀 217,907評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件己儒,死亡現(xiàn)場離奇詭異,居然都是意外死亡捆毫,警方通過查閱死者的電腦和手機(jī)闪湾,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,987評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來绩卤,“玉大人途样,你說我怎么就攤上這事”舯铮” “怎么了何暇?”我有些...
    開封第一講書人閱讀 164,298評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長凛驮。 經(jīng)常有香客問我裆站,道長,這世上最難降的妖魔是什么黔夭? 我笑而不...
    開封第一講書人閱讀 58,586評論 1 293
  • 正文 為了忘掉前任宏胯,我火速辦了婚禮,結(jié)果婚禮上本姥,老公的妹妹穿的比我還像新娘肩袍。我一直安慰自己,他們只是感情好婚惫,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,633評論 6 392
  • 文/花漫 我一把揭開白布氛赐。 她就那樣靜靜地躺著,像睡著了一般辰妙。 火紅的嫁衣襯著肌膚如雪鹰祸。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,488評論 1 302
  • 那天密浑,我揣著相機(jī)與錄音蛙婴,去河邊找鬼。 笑死尔破,一個胖子當(dāng)著我的面吹牛街图,可吹牛的內(nèi)容都是我干的浇衬。 我是一名探鬼主播,決...
    沈念sama閱讀 40,275評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼餐济,長吁一口氣:“原來是場噩夢啊……” “哼耘擂!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起絮姆,我...
    開封第一講書人閱讀 39,176評論 0 276
  • 序言:老撾萬榮一對情侶失蹤醉冤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后篙悯,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體蚁阳,經(jīng)...
    沈念sama閱讀 45,619評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,819評論 3 336
  • 正文 我和宋清朗相戀三年鸽照,在試婚紗的時候發(fā)現(xiàn)自己被綠了螺捐。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,932評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡矮燎,死狀恐怖定血,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情诞外,我是刑警寧澤澜沟,帶...
    沈念sama閱讀 35,655評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站浅乔,受9級特大地震影響倔喂,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜靖苇,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,265評論 3 329
  • 文/蒙蒙 一席噩、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧贤壁,春花似錦悼枢、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,871評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至名船,卻和暖如春绰上,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背渠驼。 一陣腳步聲響...
    開封第一講書人閱讀 32,994評論 1 269
  • 我被黑心中介騙來泰國打工蜈块, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 48,095評論 3 370
  • 正文 我出身青樓百揭,卻偏偏與公主長得像爽哎,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子器一,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,884評論 2 354

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