狂奔 lion

          自強(qiáng)不息

          2010年6月29日

          淺談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ū),可以通過(guò)wait和notify來(lái)退出和準(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。需要說(shuō)明的是AbstractQueuedSynchronizer內(nèi)部實(shí)現(xiàn)采用了CAS機(jī)制,通過(guò)getState, setState, compareAndSetState訪問(wèn)控制一個(gè)32bit int的形式進(jìn)行互斥。

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

          對(duì)于關(guān)鍵字的同步機(jī)制,其實(shí)可見性就是線程和主存之間的同步時(shí)機(jī)問(wèn)題。共有4個(gè)時(shí)間點(diǎn)需要注意:
          1 獲取或釋放類鎖/對(duì)象鎖的時(shí)候。Thread保證reload/flush全部變更
          2 volatile就是flush on write或者reload on read
          3 當(dāng)線程首次訪問(wèn)共享變量時(shí),可以得到最新的結(jié)果。
          題外:所以在構(gòu)造方法中公布this時(shí)很危險(xiǎn)的。簡(jiǎn)單的說(shuō),就是構(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)可見性的問(wè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.”

          不過(guò)這個(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)無(wú)非就是文件的存取和組織結(jié)構(gòu)。
          訪問(wèn)一個(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)遇到過(guò)這個(gè)問(wèn)題了,讀寫第一個(gè)文件時(shí)總是正確的,當(dāng)相同API讀寫第二個(gè)文件時(shí),block住了。這是因?yàn)镕TPClient要求在進(jìn)行流操作之后執(zhí)行completePendingCommand,以確保流處理完畢,因?yàn)榱魈幚聿皇羌磿r(shí)的,所以也沒(méi)有辦法不手工調(diào)用completePendingCommand。問(wèn)題是開發(fā)者把不返回流的方法末尾加上了completePendingCommand,如果不看代碼可能根本不知道。
          文檔上說(shuō):

               * 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)重的問(wèn)題是completePendingCommand調(diào)用了getReply,如果一個(gè)命令通過(guò)socket stream傳了過(guò)去但是沒(méi)有g(shù)etReply,即沒(méi)有completePendingCommand,那么下次發(fā)命令時(shí),將會(huì)受到本次返回碼的干擾,得到無(wú)效的響應(yīng)。而如果在completePendingCommand之后又進(jìn)行了一次無(wú)辜的completePendingCommand,那么因?yàn)镕TP Server上沒(méi)有Reply了,就會(huì)block。所以completePendingCommand并不是可以隨意添加的。

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

          為了解決這個(gè)問(wèn)題,可以對(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ù)量未知,從表象來(lái)看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)超過(guò)threshold時(shí),map會(huì)首先檢查內(nèi)部的ThreadLocal(前文說(shuō)過(guò),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í)(即沒(méi)有和用過(guò)的或未清理的entry命中時(shí)),會(huì)調(diào)用cleanSomeSlots來(lái)遍歷清理空間。此外,當(dāng)Thread本身銷毀時(shí),這個(gè)map也一定被銷毀了(map在Thread之內(nèi)),這樣內(nèi)部所有綁定到該線程的ThreadLocal的Object Value因?yàn)闆](méi)有引用繼續(xù)保持,所以被銷毀。

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

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

          關(guān)于軟件文檔,我的看法

          文檔應(yīng)該包括兩大部分,一部分是清晰的代碼結(jié)構(gòu)和注釋,比如Concurrent API就是這樣,還有一部分是文字文檔,包括三個(gè)小部分:一是開發(fā)文檔,應(yīng)該講架構(gòu)和功能;二是索引文檔,詳細(xì)介紹功能和參數(shù),三是用戶文檔,包括安裝和使用說(shuō)明

          文檔最困難的莫過(guò)于版本的一致性,當(dāng)軟件升級(jí)后,一些obsolete的內(nèi)容和新的feature很難同步。要是架構(gòu)發(fā)生了變化,那就更困難了。一般document team都不是太精于技術(shù),所以也會(huì)產(chǎn)生一些問(wèn)題。

          只能說(shuō)任何事物永遠(yuǎn)都有改進(jìn)的空間,但是同樣也永遠(yuǎn)沒(méi)有達(dá)到完美的程度

          posted @ 2010-06-29 18:26 楊一 閱讀(327) | 評(píng)論 (0)編輯 收藏

          <2010年6月>
          303112345
          6789101112
          13141516171819
          20212223242526
          27282930123
          45678910

          導(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)
          /////////////////////////////////////////
          我的訪問(wèn)者

          常用鏈接

          留言簿(5)

          隨筆分類(55)

          隨筆檔案(55)

          相冊(cè)

          Java

          其他技術(shù)

          生活

          最新隨筆

          搜索

          積分與排名

          最新評(píng)論

          閱讀排行榜

          評(píng)論排行榜

          自強(qiáng)不息


          用心 - 珍惜時(shí)間,勇于創(chuàng)造
          主站蜘蛛池模板: 新乐市| 甘南县| 贡觉县| 泽库县| 姚安县| 阜新市| 杭州市| 健康| 象州县| 昌都县| 穆棱市| 长岭县| 营口市| 隆回县| 博罗县| 临高县| 徐闻县| 禹城市| 阳山县| 合肥市| 徐汇区| 湾仔区| 乃东县| 泰安市| 岳普湖县| 太保市| 寿光市| 朝阳县| 南充市| 探索| 双牌县| 长阳| 天台县| 镇康县| 文安县| 龙南县| 德保县| 凤冈县| 威远县| 澳门| 门源|