第三章主要講的共享對象,這章有些內(nèi)容比較抽象,我理解其中的一些東西費了一些周折。所以把這些理解記錄下來,以免以后遺忘,有些內(nèi)容是個人的理解,如果您對我的理解有異議,請?zhí)岢鰜砉餐懻摗?
?
3.1? 可見性
???? 這里提到了“重排序”,指的是操作系統(tǒng)對線程分片后,針對不同線程的調(diào)度是沒有特定順序的。
3.1.1 過期數(shù)據(jù)
???? 貌似沒有什么可說的...
3.1.2 非原子的64位操作
???? 這里指的是對double和long類型64位的變量。對于這種數(shù)據(jù)編寫多線程程序的時候最好要加volatile標示。因為現(xiàn)在很多cpu是不對64位操作支持的,64位的數(shù)據(jù)會分成兩個32位的數(shù)據(jù),兩部分會分別讀取、操作,這樣就會有兩個存儲數(shù)據(jù)的地方:內(nèi)存和cpu緩存。volatile關(guān)鍵字指的是強制可見,即內(nèi)存和cpu緩存數(shù)據(jù)強制保持一致。這里內(nèi)存是可以存64位數(shù),但是32位的cpu只能存32位。舉個例子:假設(shè)我們有個64位的數(shù),前32位是H 00 00 00 01,后32位是H 00 00 00 10,現(xiàn)在有兩個線程,分別對前32位的數(shù)加3和加1,那么最后的結(jié)果應(yīng)該是對前32位加4.列舉如下:
??? 最后的結(jié)果:H 00 00 00 05 00 00 00 10
??? 中間結(jié)果(加3):H 00 00 00 03 00 00 00 10
????中間結(jié)果(加1):H 00 00 00 02 00 00 00 10
??? 按照一般的邏輯,如果沒有同步策略,那么最后看到的結(jié)果只有這三種,其實結(jié)果是很亂的,很有可能是這種:
????H 00 00 00 01 10 01 11 11 10
????為何會出現(xiàn)這種結(jié)果呢?原因在于前面所說的,32位的CPU是把64位的數(shù)切開分別做的,那么在低位累加到高位的過程中,會出現(xiàn)重疊,從而造成混亂。
??? 如果不想使用volatile關(guān)鍵字,使用鎖策略的話也可以保證64位數(shù)的正確操作
3.1.3 鎖和可見性
??? 其實大意在于鎖與可見性是密切想關(guān)的,鎖不僅保證了同步與互斥,也保證了內(nèi)存可見性。
3.1.4 volatile變量
??? 這里主要說了volatile變量是一種輕量級別的鎖,不過說的太粗了,以后的章節(jié)會有詳細分析
??? 比較重要的是以下幾點:
??? (1) 主要用于確保對象狀態(tài)的可見性,或者標識重要的生命周期的發(fā)生
??? (2) volatile的語義不足以使自增操作(++)原子化。這句給了個重要的啟示,那就是說你的算法針對這個變量必須是無狀態(tài)的時候才可選用volatile
??? (3)開發(fā)和測試階段啟動JVM的時候請開啟 -server選項,這樣JVM會做優(yōu)化,比如把循環(huán)中的沒有改變的判定變量提到循環(huán)外面,循環(huán)改為無限循環(huán)。
????? 比如
?????
volatile boolean asleep: ... while(!asleep)//JVM優(yōu)化器不會優(yōu)化該判斷 doSomething();
?
??? 這里加了volatile,所以是不會做優(yōu)化,因為JVM認為你這是多線程要使用的變量,但是如果沒有加,很可能就會做上述優(yōu)化,那么這個時候你做多線程,開發(fā)階段可能正確,部署階段和生產(chǎn)階段可能就會出問題。
3.2 發(fā)布和溢出
??? 這一章比較抽象,以往多線程的書重點在同步和互斥,發(fā)布和溢出問題我還是第一次看到這么深入討論的。
??? 先來看看概念:
??? 發(fā)布(publishing)一個對象的意思是使它能夠被當前范圍之外的代碼所使用;
??? 逸出(escape):一個對象在尚未準備好時就將它發(fā)布,這種清況稱作逸出。
??? 接下來舉了一些逸出的例子:
??? (1)發(fā)布到公有區(qū)域,一個對象沒有準備好就將一些東西發(fā)布到公有區(qū)域。
??? (2)從私有區(qū)域獲取(允許內(nèi)部可變的數(shù)據(jù)逸出)
???
class UnsafeStates{ private String[] states = new String[]{"SK","AL"...} public String[] getStates(){ return states;//直接把private數(shù)據(jù)發(fā)布了出去... } }
??? 這樣完全允許私有對象獲取,違背了其私有性質(zhì)。 我寫過的代碼好像有過這種情況....
???"發(fā)布一個對象,同樣也發(fā)布了該對象所有非私有域所引用的對象。更一般的,在一個已經(jīng)發(fā)布的對象中,那些非私有域的引用鏈,和方法調(diào)用鏈中的可獲得對象也都會被發(fā)布。" 這句話很重要,要結(jié)合"非私有域"和"方法調(diào)用鏈"來理解。也就是說非私有域是可被調(diào)用或者可被覆蓋的,要注意。
?? (3)內(nèi)部類實例發(fā)布。這個發(fā)布很抽象,結(jié)合代碼來看看
public class ThisEscape{ public ThisEscape(EventSource source){//構(gòu)造和發(fā)布綁定,造成this泄露 source.registerListener( new EventListener(){ public void onEvent(Event e){ doSomething(2); } } ); } }
?
?? 這段代碼是一個很常見的匿名內(nèi)部類的創(chuàng)建,不過要注意,這里發(fā)生的一切是在構(gòu)造函數(shù)中進行的。在構(gòu)造函數(shù)中這么做會有什么問題呢?
?? 問題在于source會調(diào)用EventListener,而EventListener是ThisEscape的匿名內(nèi)部類,它持有對ThisEscape對象實例的引用。source不僅會調(diào)用EventListener,實際上source是持有EventListener的引用,那么同時source也就持有了ThisEscape的引用。如果ThisEscape還沒做好實例化的情況下,另外一個線程通過source訪問ThisEscape,這樣就造成了逸出。
???我把上面的代碼理解為"愚蠢的黑社會老大模式",這里有三個角色:老大(ThisEscape),小弟(EventListener),警察(EventSource)。老大犯了事,小弟去頂,這個時候警察要來問小弟問題。 關(guān)鍵點就在這里:老大應(yīng)當知道警察一定也會來問他(EventSource也會找到ThisEscape的實例),但是他卻沒有編好理由去應(yīng)對(ThisEscape的構(gòu)造函數(shù)還沒有完成)
3.2.1安全的構(gòu)建實踐
??? 上面的例子指出,一個未完成構(gòu)造的對象不能被發(fā)布出去。
??? 更具體的說,不要讓this引用在構(gòu)造期間逸出。
??? 比如在構(gòu)造函數(shù)中起線程,會造成this的逸出,這樣this就會被新線程共享,由于this的構(gòu)造函數(shù)還未完成,其他線程會獲取this的不正確的狀態(tài)。
???
這里要特別說明的是,上面的意思是:在構(gòu)造函數(shù)中創(chuàng)建線程并沒有問題,但最好不要再構(gòu)造函數(shù)中啟動它。
??? 還有一點,在構(gòu)造函數(shù)中調(diào)用一個可覆蓋的實例方法(非private、final)同樣會造成this在構(gòu)造期間逸出為什么這么說呢?書上沒有給出解釋,但是我簡單想了一下,可以給出如下的例子:
???
public class ThisEscape{ public ThisEscape(EventSource source){ checkSomeThing(); } public void checkSomeThing(){ System.out.println("這個代碼沒有問題,沒有this的逸出"); } }
?這個是你自己的代碼,如果自己用,沒問題,你可以保證你的構(gòu)造函數(shù)引用其他函數(shù)的時候沒有包含,但如果你寫的是jdk代碼會怎么樣?使用jdk的客戶端程序員這樣搞一下:
?
?
public class ShitJDK extends ThisEscape{ public void checkSomeThing(){//這里覆蓋了父類的checkSomeThing this.......//這里可以調(diào)用this,造成this逸出 } }
?這樣客戶端程序員就很郁悶了。所以作為類庫的開發(fā)者首要任務(wù)是考慮好封裝,否則問題會很多。
?那么怎么解決上面問題呢?
?首先要分析一下,上面的代碼犯了一個錯誤,就是構(gòu)造和發(fā)布耦合。構(gòu)造函數(shù)是構(gòu)造的地方,并不是發(fā)布的地方,所以,改造的方法就是構(gòu)造和發(fā)布相分離。
?于是我們有三個基本需求:
? (1)構(gòu)造函數(shù)中只包含初始化相關(guān)內(nèi)容。
??(2)至少有兩個步驟,一個是構(gòu)造,一個是發(fā)布。
??(3)客戶端不關(guān)心黑老大,也就是說它只需要一個小弟,黑老大封裝(最嚴格的封裝方式是構(gòu)造函數(shù)為private,這樣只能通過本類的靜態(tài)方法初始化本類,其他地方根本就不會初始化本類)。
? 書上給出的例子如下:
public class SafeListener{ private final EventListener listener;//老大含有小弟 private SafeListener(){//構(gòu)造函數(shù)私有,并且只包含初始化相關(guān),不包含發(fā)布 listener = new EventListener(){ public void onEvent(Event e){ doSomeThing(e); } } } public static SafeListener newInstance(EventSource source){ SafeListener safe = new SafeListener();//這里是調(diào)用構(gòu)造 source.registerListener(safe.listener);//這里是發(fā)布,構(gòu)造與發(fā)布相分離 return safe;//這里為什么要return 一個safe?推斷是因為safe還有其他代 //碼,不只一個注冊功能。這個safe從命中初始化開始就和一個listener綁定 } }
?
??? 是不是覺得有點復雜,抽象不好理解?這里是因為除了構(gòu)造與發(fā)布相分離,這里還做了一件事就是封裝了構(gòu)造與發(fā)布相分離的過程。也就是說類的責任沒有完全分開
??? 這里有個很有意思的矛盾點,即:
??? 既要構(gòu)造與發(fā)布分離,又要構(gòu)造與發(fā)布綁定(一起調(diào)用)。
??? 第一個點很容易做到,就是分為兩個步驟即可。
??? 第二個點,這里要將兩個步驟合并到一個函數(shù)中,以共同調(diào)用。 ?第二個點其實可以理解為工廠模式的封裝方法,這里這么封裝是為了SafeListener發(fā)布出去的時候就是已經(jīng)注冊好了的, 這段代碼其實寫的不好,因為它將工廠和工廠生產(chǎn)的東西混在一起了,可以加一個工廠,以更深刻的理解:
???
class SafeListener{//*****本類只負責構(gòu)造,注意這里沒有了public,是包訪問權(quán)限,對內(nèi)的 private final EventListener listener;//老大含有小弟,注意這里private SafeListener(){//*****構(gòu)造函數(shù)包內(nèi)可見 listener = new EventListener(){ public void onEvent(Event e){ doSomeThing(e); } } }
EventListener getEventListenerListener(){//*****同樣注意是包訪問權(quán)限,對內(nèi)的
return listener;} ...//其他方法 } public class SafeListenerFactory{//工廠類負責發(fā)布任務(wù)和封裝任務(wù),和SafeListener在同一個包中,是對外的 public static SafeListener newInstance(EventSource source){ SafeListener safe = new SafeListener();//這里是調(diào)用構(gòu)造 source.registerListener(safe.getEventListenerListener());//這里是發(fā)布,構(gòu)造與發(fā)布相分離,注意與上面區(qū)分開 return safe;//這里為什么要return 一個safe?推斷是因為safe還有其他代 //碼,不只一個注冊功能。這個safe從命中初始化開始就和一個listener綁定 } }
?? 上面的代碼是不是更好理解了?因為類的責任分開了,更具備面向?qū)ο蟮奶卣鳎美斫狻?
?? 要注意這里的代碼和上面代碼在訪問權(quán)限修飾符上的不同。(*****為訪問權(quán)限不同的地方)
?? 上面代碼的類圖如下:
?
?3.3 線程封閉
?線程封閉是指將對象封閉在一個線程當中。這樣就避免了線程共享數(shù)據(jù),自動成為線程安全。
?這里舉了兩個例子:
?(1)Swing的線程封閉技術(shù)--事件分發(fā)線程
? 這里簡單介紹了事件分發(fā)線程,講的不是很清楚,有興趣的可以看看這里:
? http://space.itpub.net/13685345/viewspace-374940
?這篇文章對于Swing的事件分發(fā)線程作了詳細介紹,主要思想是:不要EDT里面做出了圖形相關(guān)的任何事情。
?同時,我很佩服Swing的簡單的設(shè)計。
?(2)JDBC應(yīng)用池
? 這里主要講一個Connection對應(yīng)一個線程。
3.3.1 Ad-hoc線程限制
?這里不要被Ad-hoc嚇到,Ad-hoc(譯為:非正式的。好抽象....)主要意思如同mashup一樣大眾化,大意是我的類庫不管線程同步問題,所有同步問題交給類庫的使用者解決。這里作者主要要告訴大家不要用Ad-hoc...
3.3.2 棧限制
?這里主要指把變量限制在方法中,即盡量把不需要共享的數(shù)據(jù)放入方法內(nèi)部。不過真要共享呢?加鎖同步,或者更簡單的本地線程變量(ThreadLocal)。
3.3.3 ThreadLocal
?這個玩意就是給每個線程一個存儲空間,這樣直接避免了同步問題。但是真要線程共享數(shù)據(jù)呢?加鎖同步...
?這里介紹了一個ThreadLocal的使用場景,即用一個ThreadLocal變量持有事務(wù)上下文,這樣不用在每次函數(shù)調(diào)用的時候都傳遞這個上下文,是不是很方便?是的,哈哈!
3.4 不可變性
?這里先介紹了不可變對象,先看概念:
? 創(chuàng)建后狀態(tài)不能被修改的對象叫做不可變對象。
?其實很好理解,本身不可變意味著其他線程不能改變其數(shù)據(jù)
3.4.1 Final域
?沒啥,不過這里給出了兩個編程的最佳實踐:
?將所有的域聲明為私有,除非它們具有更高的可見性;
?將所有的域聲明為final,除非它們是可變的。
3.4.2 示例 : 使用vloatile發(fā)布不可變對象
?這里主要給出了兩個代碼,第二個代碼使用第一個類的時候加了vloatile修飾符,主要因為第一個類為不可變對象,不需要同步,但是有可見性問題,所以一定要加volatile,以保證兩個變量同時可見。代碼就不貼了,感興趣的可以看書。
?
3.5 安全發(fā)布
?這里分了幾小節(jié),把里面的好的思想提出來如下:
?(1)final總是可以安全的用于任意線程(除了容器類)
?(2)安全發(fā)布的類型:
- 通過靜態(tài)初始化器初始化對象的引用(上面的黑老大例子)或者最簡單的:
?????????? public static Holder holder = new Holder();//這里靜態(tài)初始化器由JVM在類的初始階段執(zhí)行,也就是說客戶
???????????????????????????????????????????????????????????????????????????? //寫的線程目前還都沒有啟動。
- 將它的引用存儲到volatile域或者AtomicReference
- 將它的引用存儲到正確創(chuàng)建對象的final域中
- 將它的引用存儲到由鎖正確保護的域中
?(3)高效不可變對象
??????? 概念:一個對象在技術(shù)上是不可變的,但是它的狀態(tài)不會再發(fā)布后改變。
?????? ? 從這里看,與其叫"高效不可變對象",不如叫"偽不可變對象"更合適....
?
?(4)總結(jié)了發(fā)布對象的必要條件依賴于對象的可變性:
- 不可變對象可以通過任意機制發(fā)布
- 高效不可變對象必須要安全發(fā)布
- 可變對象必須要安全發(fā)布,同時必須要線程安全或者被鎖保護
?(5)使用和共享對象的一些最有效的策略:
- 線程限制
- 共享只讀
- 共享線程安全:有自己的同步代碼,客戶端代碼無需寫額外同步代碼
- 被守護的:即加鎖訪問。最一般的措施...
?
更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯(lián)系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
