狂奔 lion

          自強(qiáng)不息

          2010年7月2日

          淺談Java中的同步的方法和原理

          Java的內(nèi)存模型中Thread會(huì)附有自己的堆棧,寄存器,必要時(shí)需要和主存即heap之間同步。
          可以使用Synchornized關(guān)鍵字和Concurrent包中的Lock可以保證線程互斥和可見性。

          互斥性體現(xiàn)在類鎖或者對(duì)象鎖上,每個(gè)對(duì)象自身都包含一個(gè)監(jiān)視器,該監(jiān)視器是一個(gè)每次只能被一個(gè)線程所獲取進(jìn)入的臨界區(qū),可以通過wait和notify來退出和準(zhǔn)入臨界區(qū)。可以看出這是一個(gè)生產(chǎn)者-消費(fèi)者的模型。而Concurrent包中的Lock為了能夠獲得更好的性能和更好的擴(kuò)展性,以及不依賴于關(guān)鍵字的可讀代碼,自己實(shí)現(xiàn)了這樣一個(gè)生產(chǎn)消費(fèi)隊(duì)列,也就是AbstractQueuedSynchronizer,被稱為AQS的機(jī)制。每個(gè)Lock都內(nèi)置了一個(gè)AbstractQueuedSynchronizer。需要說明的是AbstractQueuedSynchronizer內(nèi)部實(shí)現(xiàn)采用了CAS機(jī)制,通過getState, setState, compareAndSetState訪問控制一個(gè)32bit int的形式進(jìn)行互斥。

          那么可見性是如何保證的呢?

          對(duì)于關(guān)鍵字的同步機(jī)制,其實(shí)可見性就是線程和主存之間的同步時(shí)機(jī)問題。共有4個(gè)時(shí)間點(diǎn)需要注意:
          1 獲取或釋放類鎖/對(duì)象鎖的時(shí)候。Thread保證reload/flush全部變更
          2 volatile就是flush on write或者reload on read
          3 當(dāng)線程首次訪問共享變量時(shí),可以得到最新的結(jié)果。
          題外:所以在構(gòu)造方法中公布this時(shí)很危險(xiǎn)的。簡(jiǎn)單的說,就是構(gòu)造時(shí)不逃脫任何變量,不開啟新的線程,只做封裝。關(guān)于安全構(gòu)造,請(qǐng)參考
          http://www.ibm.com/developerworks/cn/java/j-jtp0618/#resources
          4 線程結(jié)束時(shí),所有變更會(huì)寫回主存

          關(guān)于Concurrent Lock如何實(shí)現(xiàn)可見性的問題,Doug Lea大俠,只在他的論文中提到,按照J(rèn)SR133,Unsafe在getState, setState, compareAndSetState時(shí)保證了線程的變量的可見性,不需要額外的volatile支持,至于具體這些native做了哪些magic就不得而知了,總之,最后的contract就是保證lock區(qū)間的共享變量可見性。開發(fā)團(tuán)隊(duì)被逼急了就這樣回答:
          There seems to be a real reluctance to explain the dirty details. I think the question was definitely understood on the concurrent interest thread, and the answer is that synchronized and concurrent locking are intended to be interchangable in terms of memory semantics when implemented correctly. The answer to matfud's question seems to be "trust us.”

          不過這個(gè)地方的確是開發(fā)團(tuán)隊(duì)給我們用戶迷惑的地方,在同樣應(yīng)用了CAS機(jī)制的Atomic類中,都內(nèi)嵌了volatile變量,但是再lock塊中,他告訴我們可以保證可見性。

          感興趣的同學(xué)可以下面的兩個(gè)thread和Doug Lea的thesis:
          http://altair.cs.oswego.edu/pipermail/concurrency-interest/2005-June/001587.html
          http://forums.sun.com/thread.jspa?threadID=631014&start=15&tstart=0
          http://gee.cs.oswego.edu/dl/papers/aqs.pdf

          posted @ 2010-07-09 19:49 楊一 閱讀(1868) | 評(píng)論 (0)編輯 收藏

          commons-net FTPClient API存取設(shè)計(jì)

          文件系統(tǒng)無非就是文件的存取和組織結(jié)構(gòu)。
          訪問一個(gè)文件系統(tǒng)的API也應(yīng)該是寫,讀,定位方法(Pathname?/URI?)

          FTPClient針對(duì)文件的保存和獲取各提供了兩個(gè)方法,分別是:

          public boolean storeFile(String remote, InputStream local)
          public OutputStream storeFileStream(String remote)

          public boolean retrieveFile(String remote, OutputStream local)
          public InputStream retrieveFileStream(String remote)

           

          兩個(gè)方法貌似相同,實(shí)際不同,返回流的那個(gè)因?yàn)椴荒荞R上處理流,所以需要用戶手工調(diào)用completePendingCommand,而另一個(gè)傳遞流進(jìn)去的則不需要。可能有同學(xué)已經(jīng)遇到過這個(gè)問題了,讀寫第一個(gè)文件時(shí)總是正確的,當(dāng)相同API讀寫第二個(gè)文件時(shí),block住了。這是因?yàn)镕TPClient要求在進(jìn)行流操作之后執(zhí)行completePendingCommand,以確保流處理完畢,因?yàn)榱魈幚聿皇羌磿r(shí)的,所以也沒有辦法不手工調(diào)用completePendingCommand。問題是開發(fā)者把不返回流的方法末尾加上了completePendingCommand,如果不看代碼可能根本不知道。
          文檔上說:

               * There are a few FTPClient methods that do not complete the
               
          * entire sequence of FTP commands to complete a transaction.  These
               
          * commands require some action by the programmer after the reception
               
          * of a positive intermediate command.  After the programmer's code
               * completes its actions, it must call this method to receive
               
          * the completion reply from the server and verify the success of the
               
          * entire transaction.


          但是這樣仍然還是讓人有點(diǎn)困惑,為什么都是存儲(chǔ)/讀取的方法,有時(shí)候要調(diào)用completePendingCommand,有時(shí)候不調(diào)用?更嚴(yán)重的問題是completePendingCommand調(diào)用了getReply,如果一個(gè)命令通過socket stream傳了過去但是沒有g(shù)etReply,即沒有completePendingCommand,那么下次發(fā)命令時(shí),將會(huì)受到本次返回碼的干擾,得到無效的響應(yīng)。而如果在completePendingCommand之后又進(jìn)行了一次無辜的completePendingCommand,那么因?yàn)镕TP Server上沒有Reply了,就會(huì)block。所以completePendingCommand并不是可以隨意添加的。

          現(xiàn)在出現(xiàn)了兩個(gè)問題:
          1 completePendingCommand很容易多出來或遺漏
          2 顯式調(diào)用completePendingCommand暴露了底層實(shí)現(xiàn),給用戶帶來不便,用戶只想要InputStream或者OutputStream

          為了解決這個(gè)問題,可以對(duì)InputStream進(jìn)行擴(kuò)展,建立一個(gè)ReplyOnCloseInputStream,如下:

          private static ReplyOnCloseInputStream extends InputStream{
            
          //
            public ReplyOnCloseInputStream(InputStream is, FTPClient c){
              
          //
            }

            
          //
            @override
            
          public void close(){
              
          if(c.completePendingCommand){
                is.close();
              }
          else{
                
          //throw Exception
              }

            }

          }
           
          //
          return new ReplyOnCloseInputStream(is, client);


          這樣封裝之后,F(xiàn)TPClient的用戶只需要正常在處理完流之后關(guān)閉即可,而不必暴露實(shí)現(xiàn)細(xì)節(jié)。保存文件也可以用相同的方法封裝OutputStream。

          posted @ 2010-07-07 23:08 楊一 閱讀(3481) | 評(píng)論 (1)編輯 收藏

          關(guān)于ThreadLocal的內(nèi)存泄露

          ThreadLocal是一種confinement,confinement和local及immutable都是線程安全的(如果JVM可信的話)。因?yàn)閷?duì)每個(gè)線程和value之間存在hash表,而線程數(shù)量未知,從表象來看ThreadLocal會(huì)存在內(nèi)存泄露,讀了代碼,發(fā)現(xiàn)實(shí)際上也可能會(huì)內(nèi)存泄露。

          事實(shí)上每個(gè)Thread實(shí)例都具備一個(gè)ThreadLocal的map,以ThreadLocal Instance為key,以綁定的Object為Value。而這個(gè)map不是普通的map,它是在ThreadLocal中定義的,它和普通map的最大區(qū)別就是它的Entry是針對(duì)ThreadLocal弱引用的,即當(dāng)外部ThreadLocal引用為空時(shí),map就可以把ThreadLocal交給GC回收,從而得到一個(gè)null的key。

          這個(gè)threadlocal內(nèi)部的map在Thread實(shí)例內(nèi)部維護(hù)了ThreadLocal Instance和bind value之間的關(guān)系,這個(gè)map有threshold,當(dāng)超過threshold時(shí),map會(huì)首先檢查內(nèi)部的ThreadLocal(前文說過,map是弱引用可以釋放)是否為null,如果存在null,那么釋放引用給gc,這樣保留了位置給新的線程。如果不存在slate threadlocal,那么double threshold。除此之外,還有兩個(gè)機(jī)會(huì)釋放掉已經(jīng)廢棄的threadlocal占用的內(nèi)存,一是當(dāng)hash算法得到的table index剛好是一個(gè)null key的threadlocal時(shí),直接用新的threadlocal替換掉已經(jīng)廢棄的。另外每次在map中新建一個(gè)entry時(shí)(即沒有和用過的或未清理的entry命中時(shí)),會(huì)調(diào)用cleanSomeSlots來遍歷清理空間。此外,當(dāng)Thread本身銷毀時(shí),這個(gè)map也一定被銷毀了(map在Thread之內(nèi)),這樣內(nèi)部所有綁定到該線程的ThreadLocal的Object Value因?yàn)闆]有引用繼續(xù)保持,所以被銷毀。

          從上可以看出Java已經(jīng)充分考慮了時(shí)間和空間的權(quán)衡,但是因?yàn)橹脼閚ull的threadlocal對(duì)應(yīng)的Object Value無法及時(shí)回收。map只有到達(dá)threshold時(shí)或添加entry時(shí)才做檢查,不似gc是定時(shí)檢查,不過我們可以手工輪詢檢查,顯式調(diào)用map的remove方法,及時(shí)的清理廢棄的threadlocal內(nèi)存。需要說明的是,只要不往不用的threadlocal中放入大量數(shù)據(jù),問題不大,畢竟還有回收的機(jī)制。

          綜上,廢棄threadlocal占用的內(nèi)存會(huì)在3中情況下清理:
          1 thread結(jié)束,那么與之相關(guān)的threadlocal value會(huì)被清理
          2 GC后,thread.threadlocals(map) threshold超過最大值時(shí),會(huì)清理
          3 GC后,thread.threadlocals(map) 添加新的Entry時(shí),hash算法沒有命中既有Entry時(shí),會(huì)清理

          那么何時(shí)會(huì)“內(nèi)存泄露”?當(dāng)Thread長(zhǎng)時(shí)間不結(jié)束,存在大量廢棄的ThreadLocal,而又不再添加新的ThreadLocal(或新添加的ThreadLocal恰好和一個(gè)廢棄ThreadLocal在map中命中)時(shí)。

          posted @ 2010-07-02 18:27 楊一 閱讀(2292) | 評(píng)論 (2)編輯 收藏

          <2010年7月>
          27282930123
          45678910
          11121314151617
          18192021222324
          25262728293031
          1234567

          導(dǎo)航

          公告

          本人在blogjava上發(fā)表的文章及隨筆除特別聲明外均為原創(chuàng)或翻譯,作品受知識(shí)產(chǎn)權(quán)法保護(hù)并被授權(quán)遵從 知識(shí)分享協(xié)議:署名-非商業(yè)性使用-相同方式共享 歡迎轉(zhuǎn)載,請(qǐng)?jiān)谵D(zhuǎn)載時(shí)注明作者姓名(楊一)及出處(www.aygfsteel.com/yangyi)
          /////////////////////////////////////////
          我的訪問者

          常用鏈接

          留言簿(5)

          隨筆分類(55)

          隨筆檔案(55)

          相冊(cè)

          Java

          其他技術(shù)

          生活

          最新隨筆

          搜索

          積分與排名

          最新評(píng)論

          閱讀排行榜

          評(píng)論排行榜

          自強(qiáng)不息


          用心 - 珍惜時(shí)間,勇于創(chuàng)造
          主站蜘蛛池模板: 汝州市| 济源市| 莱州市| 隆回县| 高碑店市| 宜良县| 芜湖县| 淮滨县| 谷城县| 文登市| 上饶市| 阿尔山市| 宣化县| 金昌市| 周至县| 平舆县| 普宁市| 张家口市| 西青区| 怀化市| 肥东县| 健康| 武隆县| 景东| 长兴县| 英吉沙县| 永新县| 桑日县| 保定市| 鄂伦春自治旗| 克什克腾旗| 乌海市| 常山县| 蕉岭县| 永德县| 兴安县| 凤凰县| 义乌市| 抚顺县| 桃园市| 巴马|