qileilove

          blog已經(jīng)轉(zhuǎn)移至github,大家請(qǐng)?jiān)L問(wèn) http://qaseven.github.io/

          細(xì)節(jié)優(yōu)化提升資源利用率

          v\:* {behavior:url(#default#VML);} o\:* {behavior:url(#default#VML);} w\:* {behavior:url(#default#VML);} .shape {behavior:url(#default#VML);}

          細(xì)節(jié)優(yōu)化提升資源利用率

          Author: 放翁(文初)

          Email: fangweng@taobao.com

          Mblogweibo.com/fangweng

           

                            這里通過(guò)介紹對(duì)于淘寶開(kāi)放平臺(tái)基礎(chǔ)設(shè)置之一的TOPAnalyzer的代碼優(yōu)化,來(lái)談一下對(duì)于海量數(shù)據(jù)處理的Java應(yīng)用可以共享的一些細(xì)節(jié)設(shè)計(jì)(一個(gè)系統(tǒng)能夠承受的處理量級(jí)別往往取決于細(xì)節(jié),一個(gè)系統(tǒng)能夠支持的業(yè)務(wù)形態(tài)往往取決于設(shè)計(jì)目標(biāo))。

                            先介紹一下整個(gè)TOPAnalyzer的背景,目標(biāo)和初始設(shè)計(jì),為后面的演變做一點(diǎn)鋪墊。

                            開(kāi)放平臺(tái)從內(nèi)部開(kāi)放到正式對(duì)外開(kāi)放,逐步從每天幾千萬(wàn)的服務(wù)調(diào)用量發(fā)展到了上億到現(xiàn)在的15億,開(kāi)放的服務(wù)也從幾十個(gè)到了幾百個(gè),應(yīng)用接入從幾百個(gè)增加到了幾十萬(wàn)個(gè)。此時(shí),對(duì)于原始服務(wù)訪問(wèn)數(shù)據(jù)的分析需求就凸現(xiàn)出來(lái):

          <!--[if !supportLists]-->1. <!--[endif]-->應(yīng)用維度分析(應(yīng)用的正常業(yè)務(wù)調(diào)用行為和異常調(diào)用行為分析)

          <!--[if !supportLists]-->2. <!--[endif]-->服務(wù)維度分析(服務(wù)RT,總量,成功失敗率,業(yè)務(wù)錯(cuò)誤及子錯(cuò)誤等)

          <!--[if !supportLists]-->3. <!--[endif]-->平臺(tái)維度分析(平臺(tái)消耗時(shí)間,平臺(tái)授權(quán)等業(yè)務(wù)統(tǒng)計(jì)分析,平臺(tái)錯(cuò)誤分析,平臺(tái)系統(tǒng)健康指標(biāo)分析等)

          <!--[if !supportLists]-->4. <!--[endif]-->業(yè)務(wù)維度分析(用戶,應(yīng)用,服務(wù)之間關(guān)系分析,應(yīng)用歸類分析,服務(wù)歸類分析等)

          上面只是一部分,從上面的需求來(lái)看需要一個(gè)系統(tǒng)能夠靈活的運(yùn)行期配置分析策略,對(duì)海量數(shù)據(jù)作即時(shí)分析,將接過(guò)用于告警,監(jiān)控,業(yè)務(wù)分析。

           

          下圖是最原始的設(shè)計(jì)圖,很簡(jiǎn)單,但還是有些想法在里面:




          Master:管理任務(wù)(分析任務(wù)),合并結(jié)果(Reduce),輸出結(jié)果(全量統(tǒng)計(jì),增量片段統(tǒng)計(jì))

          SlaveRequire Job + Do Job + Return Result,隨意加入,退出集群。

          Job(Input + Analysis Rule + Output)的定義。

           

          幾個(gè)設(shè)計(jì)點(diǎn):

          <!--[if !supportLists]-->1.           <!--[endif]-->后臺(tái)系統(tǒng)任務(wù)分配:無(wú)負(fù)載分配算法,采用細(xì)化任務(wù)+工作者按需自取+粗暴簡(jiǎn)單任務(wù)重置策略。

          <!--[if !supportLists]-->2.           <!--[endif]-->SlaveMaster采用單向通信,便于容量擴(kuò)充和縮減。

          <!--[if !supportLists]-->3.           <!--[endif]-->Job自描述性,從任務(wù)數(shù)據(jù)來(lái)源,分析規(guī)則,結(jié)果輸出都定義在任務(wù)中,使得Slave適用與各種分析任務(wù),一個(gè)集群分析多種日志,多個(gè)集群共享Slave

          <!--[if !supportLists]-->4.           <!--[endif]-->數(shù)據(jù)存儲(chǔ)無(wú)業(yè)務(wù)性(意味著存儲(chǔ)的時(shí)候不定義任何業(yè)務(wù)含義),分析規(guī)則包含業(yè)務(wù)含義(在執(zhí)行分析的時(shí)候告知不同列是什么含義,怎么統(tǒng)計(jì)和計(jì)算),優(yōu)勢(shì)在于可擴(kuò)展,劣勢(shì)在于全量掃描日志(無(wú)預(yù)先索引定義)。

          <!--[if !supportLists]-->5.           <!--[endif]-->透明化整個(gè)集群運(yùn)行狀況,保證簡(jiǎn)單粗暴的方式下能夠快速定位出節(jié)點(diǎn)問(wèn)題或者任務(wù)問(wèn)題。(雖然沒(méi)有心跳,但是每個(gè)節(jié)點(diǎn)的工作都會(huì)輸出信息,通過(guò)外部收集方式快速定位問(wèn)題,防止集群為了監(jiān)控耦合不利于擴(kuò)展)

          <!--[if !supportLists]-->6.           <!--[endif]-->Master單點(diǎn)采用冷備方式解決。單點(diǎn)不可怕,可怕的是丟失現(xiàn)場(chǎng)和重啟或重選Master周期長(zhǎng)。因此采用分析數(shù)據(jù)和任務(wù)信息簡(jiǎn)單周期性外部存儲(chǔ)的方式將現(xiàn)場(chǎng)保存與外部(信息盡量少,保證恢復(fù)時(shí)快速),另一方面采用外部系統(tǒng)通知方式修改Slave集群MasterIP,人工快速切換到冷備。

           

          Master的生活軌跡:




           

          Slave的生活軌跡:

           



           

          有人會(huì)覺(jué)得這玩意兒簡(jiǎn)單,系統(tǒng)就是簡(jiǎn)單+透明才會(huì)高效,往往就是因?yàn)橄到y(tǒng)復(fù)雜才會(huì)帶來(lái)更多看似很高深的設(shè)計(jì),最終無(wú)非是折騰了自己,苦了一線。廢話不多說(shuō),背景介紹完了,開(kāi)始講具體的演變過(guò)程。

          數(shù)據(jù)量:2千萬(wàn) à 1 à 8 à15億。報(bào)表輸出結(jié)果:10份配置à30à60à100份。統(tǒng)計(jì)后的數(shù)據(jù)量:10k à 10M à 9G。統(tǒng)計(jì)周期的要求:1à5分鐘à3分鐘à1分半。

          從上面這些數(shù)據(jù)可以知道從網(wǎng)絡(luò)和磁盤(pán)IO,到內(nèi)存,到CPU都會(huì)經(jīng)歷很大的考驗(yàn),由于Master是縱向擴(kuò)展的,因此優(yōu)化Master成為每個(gè)數(shù)據(jù)跳動(dòng)的必然要求。由于是用Java寫(xiě)的,因此內(nèi)存對(duì)于整體分析的影響更加嚴(yán)重,GC的停頓直接可以使得系統(tǒng)掛掉(因?yàn)閿?shù)據(jù)在不斷流入內(nèi)存)。

           

          優(yōu)化過(guò)程:

           

          縱向系統(tǒng)的工作的分擔(dān):

                            Master的生活軌跡可以看到,它負(fù)荷最大的一步就是要去負(fù)責(zé)Reduce,無(wú)論如何都需要交給一個(gè)單節(jié)點(diǎn)來(lái)完成所有的Reduce,但并不表示對(duì)于多個(gè)Slave的所有的Reduce都需要Master來(lái)做。有同學(xué)給過(guò)建議說(shuō)讓Master再去分配給不同的Slave去做Slave之間的Reduce,但一旦引入Master對(duì)Slave的通信和管理,這就回到了復(fù)雜的老路。因此這里用最簡(jiǎn)單的方式,一個(gè)機(jī)器可以部署多個(gè)Slave,一個(gè)Slave可以一次獲取多個(gè)Job,執(zhí)行完畢后本地合并再匯報(bào)給Master。(優(yōu)勢(shì):MasterJob合并所產(chǎn)生的內(nèi)存消耗可以減輕,因?yàn)檫@是統(tǒng)計(jì),所以合并后數(shù)據(jù)量一定大幅下降,此時(shí)Master合并越少的Job數(shù)量,內(nèi)存消耗越小),因此Slave的生活軌跡變化了一點(diǎn):




           

           

          流程中間數(shù)據(jù)優(yōu)化:

                            這里舉兩個(gè)例子來(lái)說(shuō)明對(duì)于處理中中間數(shù)據(jù)優(yōu)化的意義。

                            在統(tǒng)計(jì)分析中往往會(huì)有對(duì)分析后的數(shù)據(jù)做再次處理的需求,例如一個(gè)API報(bào)表里面會(huì)有API訪問(wèn)總量,API訪問(wèn)成功數(shù),同時(shí)會(huì)要有API的成功率,這個(gè)數(shù)據(jù)最早設(shè)計(jì)的時(shí)候和普通的MapReduce字段一樣處理,計(jì)算和存儲(chǔ)在每一行數(shù)據(jù)分析的時(shí)候都做,但其實(shí)這類數(shù)據(jù)只有在最后輸出的時(shí)候才有統(tǒng)計(jì)和存儲(chǔ)價(jià)值,因?yàn)檫@些數(shù)據(jù)都可以通過(guò)已有數(shù)據(jù)計(jì)算得到,而中間反復(fù)做計(jì)算在存儲(chǔ)和計(jì)算上都是一種浪費(fèi),因此對(duì)于這種特殊的Lazy處理字段,中間不計(jì)算也不存儲(chǔ),在周期輸出時(shí)做一次分析,降低了計(jì)算和存儲(chǔ)的壓力。

                            對(duì)于MapReduce中的Key存儲(chǔ)的壓縮。由于很多統(tǒng)計(jì)的Key是很多業(yè)務(wù)數(shù)據(jù)的組合,例如APPAPIUser的統(tǒng)計(jì)報(bào)表,它的Key就是三個(gè)字段的串聯(lián):taobao.user.get—12132342—fangweng,這時(shí)候大量的Key會(huì)占用內(nèi)存,而Key的目的就是產(chǎn)生這個(gè)業(yè)務(wù)統(tǒng)計(jì)中的唯一標(biāo)識(shí),因此考慮這些API的名稱等等是否可以替換成唯一的短內(nèi)容就可以減少內(nèi)存占用。過(guò)程中就不多說(shuō)了,最后在分析器里面實(shí)現(xiàn)了兩種策略:

          <!--[if !supportLists]-->1.     <!--[endif]-->不可逆數(shù)字摘要采樣。

          有點(diǎn)類似與短連接轉(zhuǎn)換的方式,對(duì)數(shù)據(jù)做Md5數(shù)字摘要,獲得16個(gè)byte,然后根據(jù)壓縮配置來(lái)采樣16個(gè)byte部分,用可見(jiàn)字符定義出64進(jìn)制來(lái)標(biāo)識(shí)這些采樣,最后形成較短的字符串。

          由于Slave是數(shù)據(jù)分析者,因此用SlaveCPU來(lái)?yè)QMaster的內(nèi)存,將中間結(jié)果用不可逆的短字符串方式表示。弱點(diǎn):當(dāng)最后分析出來(lái)的數(shù)據(jù)量越大,采樣md5后的數(shù)據(jù)越少,越容易產(chǎn)生沖突,導(dǎo)致統(tǒng)計(jì)不準(zhǔn)確。

           

          <!--[if !supportLists]-->2.     <!--[endif]-->提供需要壓縮的業(yè)務(wù)數(shù)據(jù)列表。

          業(yè)務(wù)方提供日志中需要替換的列定義及一組定義內(nèi)容。簡(jiǎn)單來(lái)說(shuō),當(dāng)日志某一列可以被枚舉,那么就意味者這一列可以被簡(jiǎn)單的替換成短標(biāo)識(shí)。例如配置APIName這列在分析生成key的時(shí)候可以被替換,并且提供了500多個(gè)api的名稱文件載入到內(nèi)存中,那么每次api在生成key的時(shí)候就會(huì)被替換掉名稱組合在key中,大大縮短key。那為什么要提供這些api的名稱呢?首先分析生成keySlave,是分布式的,如果采用自學(xué)習(xí)的模式,勢(shì)必要引入集中式唯一索引生成器,其次還要做好足夠的并發(fā)控制,另一方面也會(huì)由并發(fā)控制帶來(lái)性能損耗。這種模式雖然很原始,但不會(huì)影響統(tǒng)計(jì)結(jié)果的準(zhǔn)確性,因此在分析器中被使用,這個(gè)列表會(huì)隨著任務(wù)規(guī)則每次發(fā)送到Slave中,保證所有節(jié)點(diǎn)分析結(jié)果的一致性。

           

          特殊化處理特殊的流程:

                            Master的生活軌跡中可以看出,影響一輪輸出時(shí)間和內(nèi)存使用的包括分析合并數(shù)據(jù)結(jié)果,導(dǎo)出報(bào)表和導(dǎo)出中間結(jié)果。在數(shù)據(jù)上升到1億的時(shí)候,SlaveMaster之間數(shù)據(jù)通信以及Master的中間結(jié)果磁盤(pán)化的過(guò)程中都采用了壓縮的方式來(lái)減少數(shù)據(jù)交互對(duì)IO緩沖的影響,但一直考慮是否還可以再壓榨一點(diǎn)。首先導(dǎo)出中間結(jié)果的時(shí)候最初采用簡(jiǎn)單的Object序列化導(dǎo)出,從內(nèi)存使用,外部數(shù)據(jù)大小,輸出時(shí)間上來(lái)說(shuō)都有不少的消耗,仔細(xì)看了一下中間結(jié)果是Map<String,Map<String,Obj>>,其實(shí)最后一個(gè)Obj無(wú)非只有兩種類型DoubleString,既然這樣,序列化完全可以簡(jiǎn)單來(lái)作,因此直接很簡(jiǎn)單的實(shí)現(xiàn)了類似Json簡(jiǎn)化版的序列化,從序列化速度,內(nèi)存占用減少上,外部磁盤(pán)存儲(chǔ)都有了極大的提高,外部磁盤(pán)存儲(chǔ)越小,所消耗的IO和過(guò)程中需要的臨時(shí)內(nèi)存都會(huì)下降,序列化速度加快,那么內(nèi)存中的數(shù)據(jù)就會(huì)被盡快釋放。總體上來(lái)說(shuō)就是特殊化處理了可以特殊化對(duì)待的流程,提高了資源利用率。(同時(shí)中間結(jié)果在前期優(yōu)化階段的作用就是為了備份,因此不需要每個(gè)周期都做,當(dāng)時(shí)做成可配置的周期值輸出)

                            再接著來(lái)談一下中間結(jié)果合并時(shí)候?qū)τ趦?nèi)存使用的優(yōu)化。Master會(huì)從多個(gè)Slave得到多個(gè)Map<Key,Map<Key,Value>>,合并過(guò)程就是對(duì)多個(gè)Map將第一級(jí)Key相同的數(shù)據(jù)做整合,例如第一級(jí)Key的一個(gè)值是API訪問(wèn)總量,那么它對(duì)應(yīng)的Map中就是不同的api名稱和總量的統(tǒng)計(jì),而多個(gè)Map直接合并就是將一級(jí)keyAPI訪問(wèn)總量)下的Map數(shù)據(jù)合并起來(lái)(同樣的api總量相加最后保存一份)。最簡(jiǎn)單的做法就是多個(gè)Map<Key,Map<Key,Value>>遞歸的來(lái)合并,但如果要節(jié)省內(nèi)存和計(jì)算可以有兩個(gè)小改進(jìn),首先選擇其中一個(gè)作為最終的結(jié)果集合(避免申請(qǐng)新空間,也避免輪詢這個(gè)Map的數(shù)據(jù)),其次每一次遞歸時(shí)候,將合并后的后面的Map中數(shù)據(jù)移出(減少后續(xù)無(wú)用的循環(huán)對(duì)比,同時(shí)也節(jié)省空間)。看似小改動(dòng),但效果很不錯(cuò)。

                            再談一下在輸出結(jié)果時(shí)候的內(nèi)存節(jié)省。在輸出結(jié)果的時(shí)候,是基于內(nèi)存中一份Map<Key,Map<Key,Value>>來(lái)構(gòu)建的。其實(shí)將傳統(tǒng)的MapReduceKV結(jié)果如何轉(zhuǎn)換成為傳統(tǒng)的Report,只需要看看Sql中的Group設(shè)計(jì),將多個(gè)KV通過(guò)Group by key,就可以得到傳統(tǒng)意義上的KeyValueValueValue。例如:KV可以是<apiName,apiTotalCount>,<apiName,apiResponse>,<apiName,apiFailCount>,如果Group by apiName,那么就可以得到 apiName,apiTotalCount,apiResponse,apiFailCount的報(bào)表行結(jié)果。這種歸總的方式可以類似填字游戲,因?yàn)槲覀兘Y(jié)果是KV,所以這個(gè)填字游戲默認(rèn)從列開(kāi)始填寫(xiě),遍歷所有的KV以后就可以完整的得到一個(gè)大的矩陣并按照行輸出,但代價(jià)是KV沒(méi)有遍歷完成以前,無(wú)法輸出。因此考慮是否可以按照行來(lái)填寫(xiě),然后每一行填寫(xiě)完畢之后直接輸出,節(jié)省申請(qǐng)內(nèi)存。按行填寫(xiě)最大的問(wèn)題就是如何在對(duì)KV中已經(jīng)處理過(guò)的數(shù)據(jù)打上標(biāo)識(shí),不要重復(fù)處理。(一種方式引入外部存儲(chǔ)來(lái)標(biāo)識(shí)這個(gè)值已經(jīng)被處理過(guò),因?yàn)檫@些KV不可以類似合并的時(shí)候刪除,后續(xù)還會(huì)繼續(xù)要用,另一種方式就是完全備份一份數(shù)據(jù),合并完成后就刪除),但本來(lái)就是為了節(jié)約內(nèi)存的,引入更多的存儲(chǔ),就和目標(biāo)有悖了。因此做了一個(gè)計(jì)算換存儲(chǔ)的做法,例如填充時(shí)輪訓(xùn)的順序?yàn)椋?/span>K1V1K2V2K3V3,到K2V2遍歷的時(shí)候,判斷是否要處理當(dāng)前這個(gè)數(shù)據(jù),就只要判斷這個(gè)K是否在K1里面出現(xiàn)過(guò),而到K3V3遍歷的時(shí)候,判斷是否要處理,就輪詢K1K2是否存在這個(gè)K,由于都是Map結(jié)構(gòu),因此這種查找的消耗很小,由此改為行填寫(xiě),逐行輸出。

           

          最后再談一下最重頭的優(yōu)化,合并調(diào)度及磁盤(pán)內(nèi)存互換的優(yōu)化

                      Master的生活軌跡可以看到,原來(lái)的主線程負(fù)責(zé)檢查外部分析數(shù)據(jù)結(jié)果狀態(tài),合并數(shù)據(jù)結(jié)果這個(gè)循環(huán),考慮到最終合并后數(shù)據(jù)只有一個(gè)主干,因此采用單線程合并模式來(lái)運(yùn)作,見(jiàn)下圖:




                            這張圖大致描述了一下處理流程,Slave隨時(shí)都會(huì)將分析后的結(jié)果掛到結(jié)果緩沖隊(duì)列上,然后主線程負(fù)責(zé)批量獲取結(jié)果并且合并。雖然是批量獲取,但是為了節(jié)省內(nèi)存,也不能等待太久,因?yàn)槊恳稽c(diǎn)等待就意味著大量沒(méi)有合并的數(shù)據(jù)將會(huì)存在與內(nèi)存中,但合并的太頻繁也會(huì)導(dǎo)致在合并過(guò)程中,新加入的結(jié)果會(huì)等待很久,導(dǎo)致內(nèi)存吃緊。或許這個(gè)時(shí)候會(huì)考慮,為什么不直接用多線程來(lái)合并,的確,多線程合并并非不可行,但要考慮如何兼顧到主干合并的并發(fā)控制,因?yàn)槎鄠€(gè)線程不可能同時(shí)都合并到數(shù)據(jù)主干上,由此引入了下面的設(shè)計(jì)實(shí)現(xiàn),半并行模式的合并:




                            從上圖可以發(fā)現(xiàn)增加了兩個(gè)角色:Merge Worker Thread PoolBranch merged ResultList,與上面設(shè)計(jì)的差別就在于主線程不再負(fù)責(zé)合并數(shù)據(jù),而是批量的獲取數(shù)據(jù)交給合并線程池來(lái)合并,而合并線程池中的工作者在合并的過(guò)程中會(huì)競(jìng)爭(zhēng)主干合并鎖,成功獲得的就和主干合并,不成功的就將結(jié)果合并后放到分支合并隊(duì)列上,等待下次合并時(shí)被主干合并或者分支合并獲得再次合并。這樣改進(jìn)后,發(fā)現(xiàn)由于數(shù)據(jù)掛在隊(duì)列沒(méi)有得到及時(shí)處理產(chǎn)生的內(nèi)存壓力大大下降,同時(shí)也充分利用了多核,多線程榨干了多核的計(jì)算能力(線程池大小根據(jù)cpu核來(lái)設(shè)置的小一點(diǎn),預(yù)留一點(diǎn)給GC用)。這種設(shè)計(jì)中還多了一些小的調(diào)優(yōu)配置,例如是否允許被合并過(guò)的數(shù)據(jù)多次被再次合并(防止無(wú)畏的計(jì)算消耗),每次并行合并最小結(jié)果數(shù)是多少,等待堆積到最小結(jié)果數(shù)的最大時(shí)間等等。(有興趣看代碼)

                            至上面的優(yōu)化為止,感覺(jué)合并這塊已經(jīng)被榨干了,但分析日志數(shù)據(jù)的增多,對(duì)及時(shí)性要求的加強(qiáng),使得我又要重新審視是否還有能力繼續(xù)榨出這個(gè)流程的水份。因此有了一個(gè)大膽的想法,磁盤(pán)換內(nèi)存。因?yàn)樵谡{(diào)度合并上已經(jīng)找不到更多可以優(yōu)化的點(diǎn)了,但是有一點(diǎn)還可以考慮,就是主干的那點(diǎn)數(shù)據(jù)是否要貫穿于整個(gè)合并周期,而且主干的數(shù)據(jù)隨著增量分析不斷增大(在最近這次優(yōu)化的過(guò)程中也就是發(fā)現(xiàn)GC的頻繁導(dǎo)致合并速度下降,合并速度下降導(dǎo)致內(nèi)存中臨時(shí)數(shù)據(jù)保存的時(shí)間久,反過(guò)來(lái)又影響GC,最后變成了惡性循環(huán))。盡管覺(jué)得靠譜,但不測(cè)試不得而知。于是得到了以下的設(shè)計(jì)和實(shí)現(xiàn):




                            這個(gè)流程發(fā)現(xiàn)和第二個(gè)流程就多了最后兩個(gè)步驟,判斷是否是最后的一次合并,如果是載入磁盤(pán)數(shù)據(jù),然后合并,合并完后將主干輸出到磁盤(pán),清空主干內(nèi)存。(此時(shí)發(fā)現(xiàn)導(dǎo)出中間結(jié)果原來(lái)不是每次必須的,但是這種模式下卻成為每次必須的了)

                            這個(gè)改動(dòng)的優(yōu)勢(shì)在什么地方?例如一個(gè)分析周期是2分鐘,那么在2分鐘內(nèi),主干龐大的數(shù)據(jù)被外置到磁盤(pán),內(nèi)存大量空閑,極大提高了當(dāng)前時(shí)間片結(jié)果合并的效率(GC少了)。缺點(diǎn)是什么?會(huì)在每個(gè)周期產(chǎn)生兩次磁盤(pán)大量的讀寫(xiě),但配合上優(yōu)化過(guò)的中間結(jié)果載入載出(前面的私有序列化)會(huì)適當(dāng)緩和。

                            由于線下無(wú)法模擬,就嘗試著線上測(cè)試,發(fā)現(xiàn)GC減少,合并過(guò)程加速達(dá)到預(yù)期,但是每輪的磁盤(pán)和內(nèi)存的換入換出由于也記入在一輪分析時(shí)間之內(nèi),每輪寫(xiě)出最大時(shí)候70m數(shù)據(jù),需要消耗10多秒,甚至20秒,讀入最大需要10s,這個(gè)時(shí)間如果算在要求一輪兩分鐘內(nèi),那也是不可接受的),重新審視是否有疏漏的細(xì)節(jié)。首先載入是否可以異步,如果可以異步,而不是在最后一輪才載入,那么就不會(huì)納入到分析周期中,因此配置了一個(gè)可以調(diào)整的比例值,當(dāng)任務(wù)完成到達(dá)或者超過(guò)這個(gè)比例值的時(shí)候,將開(kāi)始并行載入數(shù)據(jù),最后一輪等到異步載入后開(kāi)始分析,發(fā)現(xiàn)果然可行,因此這個(gè)時(shí)間被排除在周期之外(雖然也帶來(lái)了一點(diǎn)內(nèi)存消耗)。然后再考慮輸出是否可以異步,以前輸出不可以異步的原因是這份數(shù)據(jù)是下一輪分析的主干,如果異步輸出,下一輪數(shù)據(jù)開(kāi)始處理,很難保證下一輪的第一個(gè)任務(wù)是否會(huì)引發(fā)數(shù)據(jù)修改,導(dǎo)致并發(fā)問(wèn)題,所以一直鎖定主干輸出,直到完成再開(kāi)始,但現(xiàn)在每次合并都是空主干開(kāi)始的,因此輸出完全可以異步,主干可以立刻清空,進(jìn)入下一輪合并,只要在下一個(gè)周期開(kāi)始載入主干前異步導(dǎo)出主干完成即可,這個(gè)時(shí)間是很長(zhǎng)的,完全可以把控,因此輸出也可以變成異步,不納入分析周期。

                            至此完成了所有的優(yōu)化,分析器高峰期的指標(biāo)發(fā)生了改變:一輪分析從2分鐘左右降低到了110秒,JVMO區(qū)在合并過(guò)程中從5080的占用率下降到2060的占用率,GC次數(shù)明顯大幅減少。

           

          總結(jié):

          <!--[if !supportLists]-->1.     <!--[endif]-->利用可橫向擴(kuò)展的系統(tǒng)來(lái)分擔(dān)縱向擴(kuò)展系統(tǒng)的工作。

          <!--[if !supportLists]-->2.     <!--[endif]-->流程中中間數(shù)據(jù)的優(yōu)化處理。

          <!--[if !supportLists]-->3.     <!--[endif]-->特殊化處理可以特殊處理的流程。

          <!--[if !supportLists]-->4.     <!--[endif]-->從整體流程上考慮不同策略的消耗,提高整體處理能力。

          <!--[if !supportLists]-->5.     <!--[endif]-->資源的快用快放,提高同一類資源利用率。

          <!--[if !supportLists]-->6.     <!--[endif]-->不同階段不同資源的互換,提高不同資源的利用率。

           

          其實(shí)很多細(xì)節(jié)也許看了代碼才會(huì)有更深的體會(huì),分析器只是一個(gè)典型的消耗性案例,每一點(diǎn)改進(jìn)都是在數(shù)據(jù)和業(yè)務(wù)驅(qū)動(dòng)下不斷的考驗(yàn)。例如縱向的Master也許真的有一天就到了它的極限,那么就交給Slave將數(shù)據(jù)產(chǎn)出到外部存儲(chǔ),交由其他系統(tǒng)或者另一個(gè)分析集群去做二次分析。對(duì)于海量數(shù)據(jù)的處理來(lái)說(shuō)都需要經(jīng)歷初次篩選,再次分析,展示關(guān)聯(lián)幾個(gè)階段,Java的應(yīng)用擺脫不了內(nèi)存約束帶來(lái)對(duì)計(jì)算的影響,因此就要考慮好自己的頂在什么地方。但優(yōu)化一定是全局的,例如磁盤(pán)換內(nèi)存,磁盤(pán)帶來(lái)的消耗在總體上來(lái)說(shuō)還是可以接受的化,那么就可以被采納(當(dāng)然如果用上SSD效果估計(jì)會(huì)更好)。

          最后還是想說(shuō)的是,很多事情是簡(jiǎn)單做到復(fù)雜,復(fù)雜再回歸到簡(jiǎn)單,對(duì)系統(tǒng)提出的挑戰(zhàn)就是如何能夠用最直接的方式簡(jiǎn)單的搞定,而不是做一個(gè)臃腫依賴龐大的系統(tǒng),簡(jiǎn)單才看的清楚,看的清楚才有機(jī)會(huì)不斷改進(jìn)。


          @import url(http://www.aygfsteel.com/CuteSoft_Client/CuteEditor/Load.ashx?type=style&file=SyntaxHighlighter.css);@import url(/css/cuteeditor.css);

          posted @ 2011-09-27 09:23 順其自然EVO| 編輯 收藏

          抽象工廠模式

          public interface IMobilePhone {
              
          public void call();
          }

          public class NokiaMPhone implements IMobilePhone{
              
          public void call(){
              }
          }

          public class OppoMPhone implements IMobilePhone{
              
          public void call(){
              }
          }

          public interface IFactory {
              
          public IMobilePhone createNokiaPhone();
              
          public IMobilePhone createOppoPhone();
          }

          public class MobilePhoneFactory implements IFactory{
              
          public IMobilePhone createNokiaPhone(){
                  
          return new NokiaMPhone();
              }
              
          public IMobilePhone createOppoPhone(){
                  
          return new OppoMPhone();
              }
          }

          public class Client {
              
          public static void main(String args[]){
                  IFactory factory 
          = new MobilePhoneFactory();
                  IMobilePhone nokia 
          = factory.createNokiaPhone();
                  IMobilePhone oppo 
          = factory.createOppoPhone();
                  nokia.call();
                  oppo.call();
              }
          }

          就代碼上而言,工廠模式和抽象工廠模式功能上是差不多的。都通過(guò)借口遍程實(shí)現(xiàn)了比較好的代碼隔離。不同的地方在于factory類的組織。工廠模式對(duì)每一個(gè)產(chǎn)品均實(shí)現(xiàn)一個(gè)工廠。而抽象工廠在一個(gè)工廠類中建立多個(gè)方法返回多種產(chǎn)品。

          本實(shí)例需要根據(jù)植物園的種類確定植物園的布局, 而植物園的種類有很多,對(duì)應(yīng)的植物園布局也各不相同。在這里假設(shè)有3種植物園:蔬菜園,一年生植物園,多年生植物園。這3garden中種植的植物各不相同,體現(xiàn)在程序中即返回不同的關(guān)于“植物園布局”的對(duì)象。生成植物園的布局是我們的目的,如果把蔬菜園、一年生植物園、多年生植物園看作是3個(gè)“產(chǎn)品族”,而某一時(shí)刻只需要其中一個(gè)產(chǎn)品族。由于每個(gè)產(chǎn)品族需要一個(gè)生產(chǎn)的工廠,所以生產(chǎn)這3個(gè)產(chǎn)品族需要3個(gè)工廠,分別是蔬菜園工廠(VegieGarden.java),一年生植物園工廠(AnnualGarden.java),多年生植物園工廠(PerennialGarden.java)。由于這3個(gè)工廠都是生產(chǎn)植物園的工廠,每個(gè)工廠生產(chǎn)的產(chǎn)品又基本相同(都生產(chǎn)“中心布局”、“邊沿布局”、“陰處布局”3個(gè)產(chǎn)品),所以可以抽象一個(gè)工廠類(Garden.java),這個(gè)工廠類也具備生產(chǎn)“中心布局”、“邊沿布局”和“陰處布局”3個(gè)產(chǎn)品的能力,而生產(chǎn)的方法由具體的工廠來(lái)實(shí)現(xiàn)。這樣,再通過(guò)一個(gè)總控程序(GardenMaker.java)(這個(gè)總控程序也可以放到抽象的工廠中)指定需要調(diào)用的具體工廠。

          首先來(lái)創(chuàng)建Plant.java類,它只有一個(gè)name屬性

          public class Plant {
              String name;

              
          public Plant(String name) {        
                  
          this.name = name;
              }

              
          public String getName() {
                  
          return name;
              }
          }


          然后是Garden類,3個(gè)方法分別是得到中心處、邊沿處和陰暗處的植物

          public abstract class Garden {
              
          public abstract Plant getCenter();
              
          public abstract Plant getBorder();
              
          public abstract Plant getShade();
          }

          下面是3個(gè)工廠類
          /**
           * 一年生植物園工廠
           * 
          @author Heinvo Lee
           *
           
          */
          public class AnnualGarden extends Garden {

              @Override
              
          public Plant getCenter() {
                  
          // TODO Auto-generated method stub
                  return new Plant("Marigold");//萬(wàn)壽菊
              }
              
              @Override
              
          public Plant getBorder() {
                  
          // TODO Auto-generated method stub
                  return new Plant("Alyssum");//十字花科植物;庭薺
              }

              @Override
              
          public Plant getShade() {
                  
          // TODO Auto-generated method stub
                  return new Plant("Coleus");//薄荷科植物
              }

          }

          /**
           * 常年生植物園工廠
           * 
          @author Heinvo Lee
           *
           
          */
          public class PerennialGarden extends Garden {



              @Override
              
          public Plant getCenter() {
                  
          // TODO Auto-generated method stub
                  return new Plant("Dicentrum");
              }
              
              @Override
              
          public Plant getBorder() {
                  
          // TODO Auto-generated method stub
                  return new Plant("Sedum");//景天屬植物;八寶
              }

              @Override
              
          public Plant getShade() {
                  
          // TODO Auto-generated method stub
                  return new Plant("Astilbe");//落新婦屬植物
              }

          }

          /**
           * 蔬菜園工廠
           * 
          @author Heinvo Lee
           *
           
          */
          public class VegieGarden extends Garden {

              
          public Plant getCenter() {
                  
          return new Plant("Corn");
                  }
              
          public Plant getBorder() {
                  
          return new Plant("Peas");
                  }
              
              
          public Plant getShade() {        
                  
          return new Plant("Broccoli");//花椰菜
                  }

          }

          接下來(lái)創(chuàng)建總控類GardenMaker
          public class GardenMaker {
              
          private Garden garden;
              
          public Garden getGarden(String gtype)
              {
                  garden 
          = new VegieGarden(); //default
              if(gtype.equals("Perennial"))
                  garden 
          = new PerennialGarden();
              
          if(gtype.equals("Annual"))
                  garden 
          = new AnnualGarden();
              
          return garden;
              }
          }

          然后是一個(gè)測(cè)試類了
          public class Main {

              
          /**
               * 
          @param args
               
          */
              
          public static void main(String[] args) {
                  GardenMaker gm
          =new GardenMaker();
                  Garden vg
          =gm.getGarden("Vegie");
                  Garden ag
          =gm.getGarden("Annual");
                  Garden pg
          =gm.getGarden("Perennial");
                  
                  System.out.println(
          "Vegie garden, center: "+vg.getCenter().getName());
                  System.out.println(
          "Vegie garden, border: "+vg.getBorder().getName());
                  System.out.println(
          "Vegie garden, shade: "+vg.getShade().getName());
                  System.out.println(
          "------------------------------------");
                  System.out.println(
          "Annual garden, center: "+ag.getCenter().getName());
                  System.out.println(
          "Annual garden, border: "+ag.getBorder().getName());
                  System.out.println(
          "Annual garden, shade: "+ag.getShade().getName());
                  System.out.println(
          "------------------------------------");
                  System.out.println(
          "Perennial garden, center: "+pg.getCenter().getName());
                  System.out.println(
          "Perennial garden, border: "+pg.getBorder().getName());
                  System.out.println(
          "Perennial garden, shade: "+pg.getShade().getName());
                  
                  
          // TODO Auto-generated method stub

              }

          }

          執(zhí)行這個(gè)測(cè)試類,可以在控制臺(tái)看到以下效果(我以紅色標(biāo)紅)

          Vegie garden, center: Corn
          Vegie garden, border: Peas
          Vegie garden, shade: Broccoli
          ------------------------------------
          Annual garden, center: Marigold
          Annual garden, border: Alyssum
          Annual garden, shade: Coleus
          ------------------------------------
          Perennial garden, center: Dicentrum
          Perennial garden, border: Sedum
          Perennial garden, shade: Astilbe

          從這個(gè)例子我看出以下:
            GardenMaker gm=new GardenMaker();
            Garden vg=gm.getGarden("Vegie");
            Garden ag=gm.getGarden("Annual");
            Garden pg=gm.getGarden("Perennial");
          上面4句代碼是測(cè)試類里面的

          當(dāng)需要生產(chǎn)某一種植物園的布局時(shí),只需要將植物園的類型告訴總控程序(GardenMaker.java),這里是傳入?yún)?shù)Vegie、Annual、Perennial,總控程序決定使用生產(chǎn)指定類型植物園的工廠生產(chǎn)出需要的“產(chǎn)品族”,而在此過(guò)程中,不需要了解具體工廠中的方法是如何實(shí)現(xiàn)的。

          posted @ 2011-09-26 11:06 順其自然EVO| 編輯 收藏

          JAVA反射機(jī)制的學(xué)習(xí)(2)

               摘要: JAVA反射機(jī)制的學(xué)習(xí) JAVA語(yǔ)言中的反射機(jī)制:    在Java 運(yùn)行時(shí) 環(huán)境中,對(duì)于任意一個(gè)類,能否知道這個(gè)類有哪些屬性和方法?    對(duì)于任意一個(gè)對(duì)象,能否調(diào)用他的方法?這些答案是肯定的,這種動(dòng)態(tài)獲取類的信息,以及動(dòng)態(tài)調(diào)用類的方法的功能來(lái)源于JAVA的反射。從而使java具有動(dòng)態(tài)語(yǔ)言的特性。   ...  閱讀全文

          posted @ 2011-09-23 17:50 順其自然EVO| 編輯 收藏

          Java反射機(jī)制的學(xué)習(xí)

               摘要: Java反射機(jī)制的學(xué)習(xí) Java反射機(jī)制是Java語(yǔ)言被視為準(zhǔn)動(dòng)態(tài)語(yǔ)言的關(guān)鍵性質(zhì)。Java反射機(jī)制的核心就是允許在運(yùn)行時(shí)通過(guò)Java Reflection APIs來(lái)取得已知名字的class類的相關(guān)信息,動(dòng)態(tài)地生成此類,并調(diào)用其方法或修改其域(甚至是本身聲明為private的域或方法)。 也許你使用Java已經(jīng)很長(zhǎng)時(shí)間了,可是幾乎不會(huì)用到Java反射機(jī)制。你會(huì)嗤之以鼻地告訴...  閱讀全文

          posted @ 2011-09-23 17:14 順其自然EVO| 編輯 收藏

          Java內(nèi)存分配、管理小結(jié)

               摘要: Java內(nèi)存分配、管理小結(jié)   2010-12-22 10:39:54|  分類: 默認(rèn)分類 |  標(biāo)簽:內(nèi)存管理  jvm  變量  對(duì)象  java   |字號(hào)大中小 訂閱 想寫(xiě)這篇總...  閱讀全文

          posted @ 2011-09-23 17:02 順其自然EVO| 編輯 收藏

          java反射機(jī)制的實(shí)現(xiàn)原理

          java反射機(jī)制的實(shí)現(xiàn)原理
          反射機(jī)制:
          所謂的反射機(jī)制就是java語(yǔ)言在運(yùn)行時(shí)擁有一項(xiàng)自觀的能力。
          通過(guò)這種能力可以徹底的了解自身的情況為下一步的動(dòng)作做準(zhǔn)備。
          下面具體介紹一下java的反射機(jī)制。這里你將顛覆原來(lái)對(duì)java的理解。
          Java的反射機(jī)制的實(shí)現(xiàn)要借助于4個(gè)類:class,Constructor,F(xiàn)ield,Method;
          其中class代表的時(shí)類對(duì)象,
          Constructor-類的構(gòu)造器對(duì)象,
          Field-類的屬性對(duì)象,
          Method-類的方法對(duì)象。
          通過(guò)這四個(gè)對(duì)象我們可以粗略的看到一個(gè)類的各個(gè)組 成部分。
          Class:程序運(yùn)行時(shí),java運(yùn)行時(shí)系統(tǒng)會(huì)對(duì)所有的對(duì)象進(jìn)行運(yùn)行時(shí)類型的處理。
          這項(xiàng)信息記錄了每個(gè)對(duì)象所屬的類,虛擬機(jī)通常使用運(yùn)行時(shí)類型信息選擇正 確的方法來(lái)執(zhí)行(摘自:白皮書(shū))。
          但是這些信息我們?cè)趺吹玫桨。鸵柚赾lass類對(duì)象了啊。
          在Object類中定義了getClass()方法。我 們可以通過(guò)這個(gè)方法獲得指定對(duì)象的類對(duì)象。然后我們通過(guò)分析這個(gè)對(duì)象就可以得到我們要的信息了。

          比如:ArrayList arrayList;

          Class clazz = arrayList.getClass();

          然后我來(lái)處理這個(gè)對(duì)象clazz。

          當(dāng)然了Class類具有很多的方法,這里重點(diǎn)將和Constructor,F(xiàn)ield,Method類有關(guān)系的方法。
          Reflection 是 Java 程序開(kāi)發(fā)語(yǔ)言的特征之一,它允許運(yùn)行中的 Java 程序?qū)ψ陨磉M(jìn)行檢查,或者說(shuō)“自審”,并能直接操作程序的內(nèi)部屬性。Java 的這一能力在實(shí)際應(yīng)用中也許用得不是很多,但是個(gè)人認(rèn)為要想對(duì)java有個(gè)更加深入的了解還是應(yīng)該掌握的。

          1.檢測(cè)類:

          reflection的工作機(jī)制

          考慮下面這個(gè)簡(jiǎn)單的例子,讓我們看看 reflection 是如何工作的。

          import java.lang.reflect.*;

          public class DumpMethods {

          public static void main(String args[]) {

          try {
          //forName("java.lang.String")獲取指定的類的對(duì)象
          Class c = Class.forName("java.lang.String");

          Method m[] = c.getDeclaredMethods();

          for (int i = 0; i < m.length; i++)

          System.out.println(m[i].toString());

          } catch (Throwable e) {

          System.err.println(e);

          }

          }

          }

          按如下語(yǔ)句執(zhí)行:

          java DumpMethods java.util.ArrayList

          這個(gè)程序使用 Class.forName 載入指定的類,然后調(diào)用 getDeclaredMethods 來(lái)獲取這個(gè)類中定義了的方法列表。java.lang.reflect.Methods 是用來(lái)描述某個(gè)類中單個(gè)方法的一個(gè)類。

          Java類反射中的主要方法

          對(duì)于以下三類組件中的任何一類來(lái)說(shuō)
          -- 構(gòu)造函數(shù)、字段和方法
          -- java.lang.Class 提供四種獨(dú)立的反射調(diào)用,以不同的方式來(lái)獲得信息。調(diào)用都遵循一種標(biāo)準(zhǔn)格式。以下是用于查找構(gòu)造函數(shù)的一組反射調(diào)用:

          Constructor getConstructor(Class[] params) -- 獲得使用特殊的參數(shù)類型的公共構(gòu)造函數(shù),

          Constructor[] getConstructors() -- 獲得類的所有公共構(gòu)造函數(shù)

          Constructor getDeclaredConstructor(Class[] params) -- 獲得使用特定參數(shù)類型的構(gòu)造函數(shù)(與接入級(jí)別無(wú)關(guān))

          Constructor[] getDeclaredConstructors() -- 獲得類的所有構(gòu)造函數(shù)(與接入級(jí)別無(wú)關(guān))

          獲得字段信息的Class 反射調(diào)用不同于那些用于接入構(gòu)造函數(shù)的調(diào)用,在參數(shù)類型數(shù)組中使用了字段名:

          Field getField(String name) -- 獲得命名的公共字段

          Field[] getFields() -- 獲得類的所有公共字段

          Field getDeclaredField(String name) -- 獲得類聲明的命名的字段

          Field[] getDeclaredFields() -- 獲得類聲明的所有字段

          用于獲得方法信息函數(shù):

          Method getMethod(String name, Class[] params) -- 使用特定的參數(shù)類型,獲得命名的公共方法

          Method[] getMethods() -- 獲得類的所有公共方法

          Method getDeclaredMethod(String name, Class[] params) -- 使用特寫(xiě)的參數(shù)類型,獲得類聲明的命名的方法

          Method[] getDeclaredMethods() -- 獲得類聲明的所有方法

          使用 Reflection:

          用于 reflection 的類,如 Method,可以在 java.lang.relfect 包中找到。使用這些類的時(shí)候必須要遵循三個(gè)步驟:
          第一步是獲得你想操作的類的 java.lang.Class 對(duì)象。
          在運(yùn)行中的 Java 程序中,用 java.lang.Class 類來(lái)描述類和接口等。

          下面就是獲得一個(gè) Class 對(duì)象的方法之一:

          Class c = Class.forName("java.lang.String");

          這條語(yǔ)句得到一個(gè) String 類的類對(duì)象。還有另一種方法,如下面的語(yǔ)句:

          Class c = int.class;

          或者

          Class c = Integer.TYPE;

          它們可獲得基本類型的類信息。其中后一種方法中訪問(wèn)的是基本類型的封裝類 (如 Intege ) 中預(yù)先定義好的 TYPE 字段。

          第二步是調(diào)用諸如 getDeclaredMethods 的方法,以取得該類中定義的所有方法的列表。

          一旦取得這個(gè)信息,就可以進(jìn)行第三步了——使用 reflection API 來(lái)操作這些信息,如下面這段代碼:

          Class c = Class.forName("java.lang.String");

          Method m[] = c.getDeclaredMethods();

          System.out.println(m[0].toString());

          它將以文本方式打印出 String 中定義的第一個(gè)方法的原型。

          處理對(duì)象:

          a.創(chuàng)建一個(gè)Class對(duì)象

          b.通過(guò)getField 創(chuàng)建一個(gè)Field對(duì)象

          c.調(diào)用Field.getXXX(Object)方法(XXX是Int,Float等,如果是對(duì)象就省略;Object是指實(shí)例).

          例如:
          import java.lang.reflect.*;

          import java.awt.*;

          class SampleGet {

          public static void main(String[] args) throws Exception {

          Rectangle r = new Rectangle(100, 325);

          printHeight(r);
          printWidth( r);
          }

          static void printHeight(Rectangle r)throws Exception {
          //Field屬性名
          Field heightField;
          //Integer屬性值
          Integer heightValue;
          //創(chuàng)建一個(gè)Class對(duì)象
          Class c = r.getClass();

          //.通過(guò)getField 創(chuàng)建一個(gè)Field對(duì)象
          heightField = c.getField("height");
          //調(diào)用Field.getXXX(Object)方法(XXX是Int,Float等,如果是對(duì)象就省略;Object是指實(shí)例).
          heightValue = (Integer) heightField.get(r);

          System.out.println("Height: " + heightValue.toString());

          }
          static void printWidth(Rectangle r) throws Exception{

           Field widthField;

           Integer widthValue;

           Class c = r.getClass();

           

           widthField = c.getField("width");

           widthValue = (Integer) widthField.get(r);

           System.out.println("Height: " + widthValue.toString());

           
          }

          }
          安全性和反射:

          在處理反射時(shí)安全性是一個(gè)較復(fù)雜的問(wèn)題。反射經(jīng)常由框架型代碼使用,由于這一點(diǎn),我們可能希望框架能夠全面接入代碼,無(wú)需考慮常規(guī)的接入限制。但是,在其它情況下,不受控制的接入會(huì)帶來(lái)嚴(yán)重的安全性風(fēng)險(xiǎn),例如當(dāng)代碼在不值得信任的代碼共享的環(huán)境中運(yùn)行時(shí)。

          由于這些互相矛盾的需求,Java編程語(yǔ)言定義一種多級(jí)別方法來(lái)處理反射的安全性。基本模式是對(duì)反射實(shí)施與應(yīng)用于源代碼接入相同的限制:

          從任意位置到類公共組件的接入

          類自身外部無(wú)任何到私有組件的接入

          受保護(hù)和打包(缺省接入)組件的有限接入

          不過(guò)至少有些時(shí)候,圍繞這些限制還有一種簡(jiǎn)單的方法。我們可以在我們所寫(xiě)的類中,擴(kuò)展一個(gè)普通的基本類 java.lang.reflect.AccessibleObject 類。這個(gè)類定義了一種setAccessible方法,使我們能夠啟動(dòng)或關(guān)閉對(duì)這些類中其中一個(gè)類的實(shí)例的接入檢測(cè)。唯一的問(wèn)題在于如果使用了安全性管理 器,它將檢測(cè)正在關(guān)閉接入檢測(cè)的代碼是否許可了這樣做。如果未許可,安全性管理器拋出一個(gè)例外。

          下面是一段程序,在TwoString 類的一個(gè)實(shí)例上使用反射來(lái)顯示安全性正在運(yùn)行:

          public class ReflectSecurity {

          public static void main(String[] args) {

          try {

          TwoString ts = new TwoString("a", "b");

          Field field = clas.getDeclaredField("m_s1");

          // field.setAccessible(true);

          System.out.println("Retrieved value is " +

          field.get(inst));

          } catch (Exception ex) {

          ex.printStackTrace(System.out);

          }

          }

          }

          如果我們編譯這一程序時(shí),不使用任何特定參數(shù)直接從命令行運(yùn)行,它將在field .get(inst)調(diào)用中拋出一個(gè)IllegalAccessException異常。如果我們不注釋 field.setAccessible(true)代碼行,那么重新編譯并重新運(yùn)行該代碼,它將編譯成功。最后,如果我們?cè)诿钚刑砑恿薐VM參數(shù) -Djava.security.manager以實(shí)現(xiàn)安全性管理器,它仍然將不能通過(guò)編譯,除非我們定義了ReflectSecurity類的許可權(quán) 限。

          反射性能:(轉(zhuǎn)錄別人的啊)

          反射是一種強(qiáng)大的工具,但也存在一些不足。一個(gè)主要的缺點(diǎn)是對(duì)性能有影響。使用反射基本上是一種解釋操作,我們可以告訴JVM,我們希望做什么并且它滿足我們的要求。這類操作總是慢于只直接執(zhí)行相同的操作。

          下面的程序是字段接入性能測(cè)試的一個(gè)例子,包括基本的測(cè)試方法。每種方法測(cè)試字段接入的一種形式 -- accessSame 與同一對(duì)象的成員字段協(xié)作,accessOther 使用可直接接入的另一對(duì)象的字段,accessReflection 使用可通過(guò)反射接入的另一對(duì)象的字段。在每種情況下,方法執(zhí)行相同的計(jì)算 -- 循環(huán)中簡(jiǎn)單的加/乘順序。

          程序如下:

          public int accessSame(int loops) {

          m_value = 0;

          for (int index = 0; index < loops; index++) {

          m_value = (m_value + ADDITIVE_VALUE) *

          MULTIPLIER_VALUE;

          }

          return m_value;

          }

          public int acces

          sReference(int loops) {

          TimingClass timing = new TimingClass();

          for (int index = 0; index < loops; index++) {

          timing.m_value = (timing.m_value + ADDITIVE_VALUE) *

          MULTIPLIER_VALUE;

          }

          return timing.m_value;

          }

          public int accessReflection(int loops) throws Exception {

          TimingClass timing = new TimingClass();

          try {

          Field field = TimingClass.class.

          getDeclaredField("m_value");

          for (int index = 0; index < loops; index++) {

          int value = (field.getInt(timing) +

          ADDITIVE_VALUE) * MULTIPLIER_VALUE;

          field.setInt(timing, value);

          }

          return timing.m_value;

          } catch (Exception ex) {

          System.out.println("Error using reflection");

          throw ex;

          }

          }

          在上面的例子中,測(cè)試程序重復(fù)調(diào)用每種方法,使用一個(gè)大循環(huán)數(shù),從而平均多次調(diào)用的時(shí)間衡量結(jié)果。平均值中不包括每種方法第一次調(diào)用的時(shí)間,因此初始化時(shí)間不是結(jié)果中的一個(gè)因素。下面的圖清楚的向我們展示了每種方法字段接入的時(shí)間:

          圖 1:字段接入時(shí)間 :

          我們可以看出:在前兩副圖中(Sun JVM),使用反射的執(zhí)行時(shí)間超過(guò)使用直接接入的1000倍以上。通過(guò)比較,IBM JVM可能稍好一些,但反射方法仍舊需要比其它方法長(zhǎng)700倍以上的時(shí)間。任何JVM上其它兩種方法之間時(shí)間方面無(wú)任何顯著差異,但I(xiàn)BM JVM幾乎比Sun JVM快一倍。最有可能的是這種差異反映了Sun Hot Spot JVM的專業(yè)優(yōu)化,它在簡(jiǎn)單基準(zhǔn)方面表現(xiàn)得很糟糕。反射性能是Sun開(kāi)發(fā)1.4 JVM時(shí)關(guān)注的一個(gè)方面,它在反射方法調(diào)用結(jié)果中顯示。在這類操作的性能方面,Sun 1.4.1 JVM顯示了比1.3.1版本很大的改進(jìn)。

          如果為為創(chuàng)建使用反射的對(duì)象編寫(xiě)了類似的計(jì)時(shí)測(cè)試程序,我們會(huì)發(fā)現(xiàn)這種情況下的差異不象字段和方法調(diào)用情況下那么顯著。使用newInstance()調(diào) 用創(chuàng)建一個(gè)簡(jiǎn)單的java.lang.Object實(shí)例耗用的時(shí)間大約是在Sun 1.3.1 JVM上使用new Object()的12倍,是在IBM 1.4.0 JVM的四倍,只是Sun 1.4.1 JVM上的兩部。使用Array.newInstance(type, size)創(chuàng)建一個(gè)數(shù)組耗用的時(shí)間是任何測(cè)試的JVM上使用new type[size]的兩倍,隨著數(shù)組大小的增加,差異逐步縮小。隨著jdk6.0的推出,反射機(jī)制的性能也有了很大的提升。期待中….

          總結(jié):

          Java語(yǔ)言反射提供一種動(dòng)態(tài)鏈接程序組件的多功能方法。它允許程序創(chuàng)建和控制任何類的對(duì)象(根據(jù)安全性限制),無(wú)需提前硬編碼目標(biāo)類。這些特性使得反射 特別適用于創(chuàng)建以非常普通的方式與對(duì)象協(xié)作的庫(kù)。例如,反射經(jīng)常在持續(xù)存儲(chǔ)對(duì)象為數(shù)據(jù)庫(kù)、XML或其它外部格式的框架中使用。Java reflection 非常有用,它使類和數(shù)據(jù)結(jié)構(gòu)能按名稱動(dòng)態(tài)檢索相關(guān)信息,并允許在運(yùn)行著的程序中操作這些信息。Java 的這一特性非常強(qiáng)大,并且是其它一些常用語(yǔ)言,如 C、C++、Fortran 或者 Pascal 等都不具備的。

          但反射有兩個(gè)缺點(diǎn)。第一個(gè)是性能問(wèn)題。用于字段和方法接入時(shí)反射要遠(yuǎn)慢于直接代碼。性能問(wèn)題的程度取決于程序中是如何使用反射的。如果它作為程序運(yùn)行中相 對(duì)很少涉及的部分,緩慢的性能將不會(huì)是一個(gè)問(wèn)題。即使測(cè)試中最壞情況下的計(jì)時(shí)圖顯示的反射操作只耗用幾微秒。僅反射在性能關(guān)鍵的應(yīng)用的核心邏輯中使用時(shí)性 能問(wèn)題才變得至關(guān)重要。

          許多應(yīng)用中更嚴(yán)重的一個(gè)缺點(diǎn)是使用反射會(huì)模糊程序內(nèi)部實(shí)際要發(fā)生的事情。程序人員希望在源代碼中看到程序的邏輯,反射等繞過(guò)了源代碼的技術(shù)會(huì)帶來(lái)維護(hù)問(wèn) 題。反射代碼比相應(yīng)的直接代碼更復(fù)雜,正如性能比較的代碼實(shí)例中看到的一樣。解決這些問(wèn)題的最佳方案是保守地使用反射——僅在它可以真正增加靈活性的地方 ——記錄其在目標(biāo)類中的使用。

          一下是對(duì)應(yīng)各個(gè)部分的例子:

          具體的應(yīng)用:

          1、 模仿instanceof 運(yùn)算符號(hào)

          class A {}

          public class instance1 {

          public static void main(String args[])

          {

          try {

          Class cls = Class.forName("A");

          boolean b1

          = cls.isInstance(new Integer(37));

          System.out.println(b1);

          boolean b2 = cls.isInstance(new A());

          System.out.println(b2);

          }

          catch (Throwable e) {

          System.err.println(e);

          }

          }

          }

          2、 在類中尋找指定的方法,同時(shí)獲取該方法的參數(shù)列表,例外和返回值

          import java.lang.reflect.*;

          public class method1 {

          private int f1(

          Object p, int x) throws NullPointerException

          {

          if (p == null)

          throw new NullPointerException();

          return x;

          }

          public static void main(String args[])

          {

          try {

          Class cls = Class.forName("method1");

          Method methlist[]

          = cls.getDeclaredMethods();

          for (int i = 0; i < methlist.length;

          i++)

          Method m = methlist[i];

          System.out.println("name

          = " + m.getName());

          System.out.println("decl class = " +

          m.getDeclaringClass());

          Class pvec[] = m.getParameterTypes();

          for (int j = 0; j < pvec.length; j++)

          System.out.println("

          param #" + j + " " + pvec[j]);

          Class evec[] = m.getExceptionTypes();

          for (int j = 0; j < evec.length; j++)

          System.out.println("exc #" + j

          + " " + evec[j]);

          System.out.println("return type = " +

          m.getReturnType());

          System.out.println("-----");

          }

          }

          catch (Throwable e) {

          System.err.println(e);

          }

          }

          }

          3、 獲取類的構(gòu)造函數(shù)信息,基本上與獲取方法的方式相同

          import java.lang.reflect.*;

          public class constructor1 {

          public constructor1()

          {

          }

          protected constructor1(int i, double d)

          {

          }

          public static void main(String args[])

          {

          try {

          Class cls = Class.forName("constructor1");

          Constructor ctorlist[]

          = cls.getDeclaredConstructors();

          for (int i = 0; i < ctorlist.length; i++) {

          Constructor ct = ctorlist[i];

          System.out.println("name

          = " + ct.getName());

          System.out.println("decl class = " +

          ct.getDeclaringClass());

          Class pvec[] = ct.getParameterTypes();

          for (int j = 0; j < pvec.length; j++)

          System.out.println("param #"

          + j + " " + pvec[j]);

          Class evec[] = ct.getExceptionTypes();

          for (int j = 0; j < evec.length; j++)

          System.out.println(

          "exc #" + j + " " + evec[j]);

          System.out.println("-----");

          }

          }

          catch (Throwable e) {

          System.err.println(e);

          }

          }

          }

          4、 獲取類中的各個(gè)數(shù)據(jù)成員對(duì)象,包括名稱。類型和訪問(wèn)修飾符號(hào)

          import java.lang.reflect.*;

          public class field1 {

          private double d;

          public static final int i = 37;

          String s = "testing";

          public static void main(String args[])

          {

          try {

          Class cls = Class.forName("field1");

          Field fieldlist[]

          = cls.getDeclaredFields();

          for (int i

          = 0; i < fieldlist.length; i++) {

          Field fld = fieldlist[i];

          System.out.println("name

          = " + fld.getName());

          System.out.println("decl class = " +

          fld.getDeclaringClass());

          System.out.println("type

          = " + fld.getType());

          int mod = fld.getModifiers();

          System.out.println("modifiers = " +

          Modifier.toString(mod));

          System.out.println("-----");

          }

          }

          catch (Throwable e) {

          System.err.println(e);

          }

          }

          }

          5、 通過(guò)使用方法的名字調(diào)用方法

          import java.lang.reflect.*;

          public class method2 {

          public int add(int a, int b)

          {

          return a + b;

          }

          public static void main(String args[])

          {

          try {

          Class cls = Class.forName("method2");

          Class partypes[] = new Class[2];

          partypes[0] = Integer.TYPE;

          partypes[1] = Integer.TYPE;

          Method meth = cls.getMethod(

          "add", partypes);

          method2 methobj = new method2();

          Object arglist[] = new Object[2];

          arglist[0] = new Integer(37);

          arglist[1] = new Integer(47);

          Object retobj

          = meth.invoke(methobj, arglist);

          Integer retval = (Integer)retobj;

          System.out.println(retval.intValue());

          }

          catch (Throwable e) {

          System.err.println(e);

          }

          }

          }

          6、 創(chuàng)建新的對(duì)象

          import java.lang.reflect.*;

          public class constructor2 {

          public constructor2()

          {

          }

          public constructor2(int a, int b)

          {

          System.out.println(

          "a = " + a + " b = " + b);

          }

          public static void main(String args[])

          {

          try {

          Class cls = Class.forName("constructor2");

          Class partypes[] = new Class[2];

          partypes[0] = Integer.TYPE;

          partypes[1] = Integer.TYPE;

          Constructor ct

          = cls.getConstructor(partypes);

          Object arglist[] = new Object[2];

          arglist[0] = new Integer(37);

          arglist[1] = new Integer(47);

          Object retobj = ct.newInstance(arglist);

          }

          catch (Throwable e) {

          System.err.println(e);

          }

          }

          }

          7、 變更類實(shí)例中的數(shù)據(jù)的值

          import java.lang.reflect.*;

          public class field2 {

          public double d;

          public static void main(String args[])

          {

          try {

          Class cls = Class.forName("field2");

          Field fld = cls.getField("d");

          field2 f2obj = new field2();

          System.out.println("d = " + f2obj.d);

          fld.setDouble(f2obj, 12.34);

          System.out.println("d = " + f2obj.d);

          }

          catch (Throwable e) {

          System.err.println(e);

          }

          }

          }

          使用反射創(chuàng)建可重用代碼:

          1、 對(duì)象工廠

          Object factory(String p) {

          Class c;

          Object o=null;

          try {

          c = Class.forName(p);// get class def

          o = c.newInstance(); // make a new one

          } catch (Exception e) {

          System.err.println("Can't make a " + p);

          }

          return o;

          }

          public class ObjectFoundry {

          public static Object factory(String p)

          throws ClassNotFoundException,

          InstantiationException,

          IllegalAccessException {

          Class c = Class.forName(p);

          Object o = c.newInstance();

          return o;

          }

          }

          2、 動(dòng)態(tài)檢測(cè)對(duì)象的身份,替代instanceof

          public static boolean

          isKindOf(Object obj, String type)

          throws ClassNotFoundException {

          // get the class def for obj and type

          Class c = obj.getClass();

          Class tClass = Class.forName(type);

          while ( c!=null ) {

          if ( c==tClass ) return true;

          c = c.getSuperclass();

          }

          return false;

          }

          posted @ 2011-09-23 14:53 順其自然EVO| 編輯 收藏

          java中的工廠模式

               摘要: 舉兩個(gè)例子以快速明白Java中的工廠模式:女?huà)z摶土造人話說(shuō):“天地開(kāi)辟,未有人民,女?huà)z摶土為人。”女?huà)z需要用土造出一個(gè)個(gè)的人,但在女?huà)z造出人之前,人的概念只存在于女?huà)z的思想里面。女?huà)z造人,這就是簡(jiǎn)單工廠模式的應(yīng)用。  首先,在這個(gè)造人的思想里面,有幾個(gè)重要的角色:女?huà)z本身、抽象的人的概念和女?huà)z所造出的一個(gè)個(gè)具體的人。  1.)女?huà)z是一個(gè)工廠類,也就是簡(jiǎn)單工廠模式的核心角色。...  閱讀全文

          posted @ 2011-09-23 13:38 順其自然EVO| 編輯 收藏

          簡(jiǎn)單靜態(tài)代理與動(dòng)態(tài)代理

          靜態(tài):
          首先需要一個(gè)接口
          public interface Neting {

           public void netting();
           
           public String playGame(String name);
          }
          然后需要一個(gè)實(shí)例去繼承:

          public class NetImpl implements Neting {

           @Override
           public void netting() {
            System.out.println("上網(wǎng)中....");
           }

           @Override
           public String playGame(String name) {
            System.out.println("游戲中....");
            return "game over";
           }

          }

          最后需要一個(gè)同樣實(shí)現(xiàn)了相同接口的類來(lái)做代理,并在每個(gè)方法中加入需要的操作:

          public class NetingImplProxy implements Neting {

           private Neting neting;
           
           public NetingImplProxy(Neting neting){
            this.neting=neting;
           }
           @Override
           public void netting() {
            this.up();
            neting.netting();
            this.down();
           }

           @Override
           public String playGame(String name) {
            this.up();
            String str=neting.playGame(name);
            this.down();
            return str;
           }
           
           private void up(){
            System.out.println("撥號(hào)中");
           }
           
           private void down(){
            System.out.println("斷開(kāi)中");
           }

          }

          這樣產(chǎn)生的結(jié)果是每次更改方法中固定的操作時(shí),都需要更改類的源代碼,帶來(lái)很多不便,由此產(chǎn)生動(dòng)態(tài)代理。
          動(dòng)態(tài)代理需要一個(gè)Proxy類和invocationhandle接口來(lái)支持:

          同樣,上述的接口和實(shí)現(xiàn)類都不變,不再需要代理類,而是由下面這個(gè)類動(dòng)態(tài)產(chǎn)生:
          public class NetHandle implements InvocationHandler {
           
           //首先需要傳一個(gè)目標(biāo)對(duì)象的引用
           private Object targetObject;
           
          //設(shè)置構(gòu)造方法時(shí),直接將目標(biāo)對(duì)象作為參數(shù)傳入
           public NetHandle(Object targetObject){
            this.targetObject=targetObject;
           }
           
           //這是至關(guān)重要的,由此類實(shí)例的本方法產(chǎn)生一個(gè)$...代理對(duì)象:
           public Object createProxyInstance(){
            return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(),
                     targetObject.getClass().getInterfaces(),
                     this);
            
           }
          //代理對(duì)象會(huì)自動(dòng)調(diào)用復(fù)寫(xiě)后的invoke方法,類似filter中的dofilter;
           @Override
           public Object invoke(Object proxy, Method method, Object[] args)
             throws Throwable {
            up(); //在此加入需要增加的操作
            Object ret=method.invoke(targetObject, args);
            down();//在此加入需要增加的操作
            return ret;// 如果沒(méi)有返回值,則返回null。
           }
           
           private void up(){
            System.out.println("撥號(hào)中");
           }
           
           private void down(){
            System.out.println("斷開(kāi)中");
           }
          }
          測(cè)試:
           public static void main(String[] args) {
            
            NetHandle n=new NetHandle(new NetImpl());
            Neting net=(Neting) n.createProxyInstance();
            net.netting();
            System.out.println(net.playGame(""));
           }
          運(yùn)行結(jié)果:
          撥號(hào)中
          上網(wǎng)中....
          斷開(kāi)中
          撥號(hào)中
          游戲中....
          斷開(kāi)中
          game over

          posted @ 2011-09-23 11:47 順其自然EVO| 編輯 收藏

          singleton模式

          這個(gè)單例模式一般是只能創(chuàng)建一個(gè)實(shí)例的實(shí)現(xiàn),如網(wǎng)絡(luò)游戲里每個(gè)特定的地圖,對(duì)于任意一個(gè)玩家,這個(gè)地圖都是唯一的. 其特點(diǎn)是有一個(gè)私有的構(gòu)造器和一個(gè)公開(kāi)的獲取實(shí)例的方法。比如:
          
          //這是第一種,叫餓漢模式,無(wú)論是否需要,都弄一個(gè)出來(lái)
          
          
          public class Singleton{
           private static final Singleton st=new Singleton();
           private Singleton(){
           
           }
           public static Singleton getInstance(){
            if(st==null)
            {
              return st;
          
            }
           }
          }
          
          //這是另外一種,叫懶漢模式,你不要我就不給弄,你要了再說(shuō).
          
          public class SingleTon{
           private static SingleTon st;
           private SingleTon(){
          
           }
           public static SingleTon getInstance(){
             if (st==null){
               st=new SingleTon();
             }
             return st;
            }
          }
          
          二者在大多數(shù)情況沒(méi)區(qū)別,但是餓漢模式是線程安全的,懶漢模式是要加同步鎖的.不同步的話,在這個(gè)類的實(shí)例還沒(méi)有創(chuàng)建的時(shí)候,多個(gè)線程同時(shí)去調(diào)用getInstance, 這時(shí),由于沒(méi)有同步鎖,有可能會(huì)創(chuàng)建出多個(gè)實(shí)例來(lái)。 
          對(duì)于餓漢式,由于實(shí)例是在load class的時(shí)候創(chuàng)建的,所以就沒(méi)有這個(gè)問(wèn)題。 當(dāng)然,除非2個(gè)線程采用了不同的class loader. 
          當(dāng)然, 無(wú)論餓漢還是懶漢, 幾率都不高, 但幾率低不代表不會(huì)出錯(cuò), 邏輯上也不對(duì), 因此, 編程者還是應(yīng)該避免。 
          package test;
          
          public class Singleton {
           private Singleton s;
           private Singleton()
           {
            
           }
           public static Singleton getSigleton()
           {
            if(s==null)s=new Singleton();
            return s;
           }
          
           
          }
          這就是一個(gè)單例模式,我想應(yīng)該不用注釋了,原理就是這個(gè)類的構(gòu)造方法private了,所有在外邊不能調(diào)用,也就不能new Singleton();得到實(shí)例,那么
          想得到實(shí)例就得調(diào)用它的靜態(tài)方法getSigleton();即Singleton.getSigleton();就會(huì)返回一個(gè)Singleton的實(shí)例,注意此方法中的語(yǔ)句,
          即如果你是第一次調(diào)用這個(gè)方法那么它會(huì)給你new一個(gè)實(shí)例,以后再調(diào)用得到的都是這個(gè)實(shí)例,也就是說(shuō)從始至終就只有一個(gè)Singleton的實(shí)例,這就是單例模式。
          1、禁止掉可被調(diào)用的構(gòu)建方法
          2、自動(dòng)初始化一個(gè)實(shí)例
          public class EagerSingleton{
            private static final EagerSingletonst = new EagerSingleton();
            private EagerSingleton(){}
            public static EagerSingletongetInstance(){
              return st;
            }
          }
          
          public class LazySingleton{
            private static LazySingletonst;
            private LazySingleton(){}
            synchronized public static LazySingleton getInstance(){
              if (st==null){
                st = new LazySingleton();
              }
              return st;
            }
          }
          
          
          EagerSingleton比較適合JAVA 

          posted @ 2011-09-23 11:29 順其自然EVO| 編輯 收藏

          同步塊

          打個(gè)比方:一個(gè)object就像一個(gè)大房子,大門(mén)永遠(yuǎn)打開(kāi)。房子里有很多房間(也就是方法)。這些房間有上鎖的(synchronized方法),和不上鎖之分(普通方法)。房門(mén)口放著一把鑰匙(key),這把鑰匙可以打開(kāi)所有上鎖的房間。另外我把所有想調(diào)用該對(duì)象方法的線程比喻成想進(jìn)入這房子某個(gè)房間的人。所有的東西就這么多了,下面我們看看這些東西之間如何作用的。

          在此我們先來(lái)明確一下我們的前提條件。該對(duì)象至少有一個(gè)synchronized方法,否則這個(gè)key還有啥意義。當(dāng)然也就不會(huì)有我們的這個(gè)主題了。

          一個(gè)人想進(jìn)入某間上了鎖的房間,他來(lái)到房子門(mén)口,看見(jiàn)鑰匙在那兒(說(shuō)明暫時(shí)還沒(méi)有其他人要使用上鎖的房間)。于是他走上去拿到了鑰匙,并且按照自己的計(jì)劃使用那些房間。注意一點(diǎn),他每次使用完一次上鎖的房間后會(huì)馬上把鑰匙還回去。即使他要連續(xù)使用兩間上鎖的房間,中間他也要把鑰匙還回去,再取回來(lái)。

          因此,普通情況下鑰匙的使用原則是:“隨用隨借,用完即還。”

          這時(shí)其他人可以不受限制的使用那些不上鎖的房間,一個(gè)人用一間可以,兩個(gè)人用一間也可以,沒(méi)限制。但是如果當(dāng)某個(gè)人想要進(jìn)入上鎖的房間,他就要跑到大門(mén)口去看看了。有鑰匙當(dāng)然拿了就走,沒(méi)有的話,就只能等了。

          要是很多人在等這把鑰匙,等鑰匙還回來(lái)以后,誰(shuí)會(huì)優(yōu)先得到鑰匙?Not guaranteed。象前面例子里那個(gè)想連續(xù)使用兩個(gè)上鎖房間的家伙,他中間還鑰匙的時(shí)候如果還有其他人在等鑰匙,那么沒(méi)有任何保證這家伙能再次拿到。(JAVA規(guī)范在很多地方都明確說(shuō)明不保證,象Thread.sleep()休息后多久會(huì)返回運(yùn)行,相同優(yōu)先權(quán)的線程那個(gè)首先被執(zhí)行,當(dāng)要訪問(wèn)對(duì)象的鎖被釋放后處于等待池的多個(gè)線程哪個(gè)會(huì)優(yōu)先得到,等等。我想最終的決定權(quán)是在JVM,之所以不保證,就是因?yàn)镴VM在做出上述決定的時(shí)候,絕不是簡(jiǎn)簡(jiǎn)單單根據(jù)一個(gè)條件來(lái)做出判斷,而是根據(jù)很多條。而由于判斷條件太多,如果說(shuō)出來(lái)可能會(huì)影響JAVA的推廣,也可能是因?yàn)橹R(shí)產(chǎn)權(quán)保護(hù)的原因吧。SUN給了個(gè)不保證就混過(guò)去了。無(wú)可厚非。但我相信這些不確定,并非完全不確定。因?yàn)橛?jì)算機(jī)這東西本身就是按指令運(yùn)行的。即使看起來(lái)很隨機(jī)的現(xiàn)象,其實(shí)都是有規(guī)律可尋。學(xué)過(guò)計(jì)算機(jī)的都知道,計(jì)算機(jī)里隨機(jī)數(shù)的學(xué)名是偽隨機(jī)數(shù),是人運(yùn)用一定的方法寫(xiě)出來(lái)的,看上去隨機(jī)罷了。另外,或許是因?yàn)橐肱拇_定太費(fèi)事,也沒(méi)多大意義,所以不確定就不確定了吧。)

          再來(lái)看看同步代碼塊。和同步方法有小小的不同。

          1.從尺寸上講,同步代碼塊比同步方法小。你可以把同步代碼塊看成是沒(méi)上鎖房間里的一塊用帶鎖的屏風(fēng)隔開(kāi)的空間。

          2.同步代碼塊還可以人為的指定獲得某個(gè)其它對(duì)象的key。就像是指定用哪一把鑰匙才能開(kāi)這個(gè)屏風(fēng)的鎖,你可以用本房的鑰匙;你也可以指定用另一個(gè)房子的鑰匙才能開(kāi),這樣的話,你要跑到另一棟房子那兒把那個(gè)鑰匙拿來(lái),并用那個(gè)房子的鑰匙來(lái)打開(kāi)這個(gè)房子的帶鎖的屏風(fēng)。

          記住你獲得的那另一棟房子的鑰匙,并不影響其他人進(jìn)入那棟房子沒(méi)有鎖的房間。

          為什么要使用同步代碼塊呢?我想應(yīng)該是這樣的:首先對(duì)程序來(lái)講同步的部分很影響運(yùn)行效率,而一個(gè)方法通常是先創(chuàng)建一些局部變量,再對(duì)這些變量做一些操作,如運(yùn)算,顯示等等;而同步所覆蓋的代碼越多,對(duì)效率的影響就越嚴(yán)重。因此我們通常盡量縮小其影響范圍。如何做?同步代碼塊。我們只把一個(gè)方法中該同步的地方同步,比如運(yùn)算。

          另外,同步代碼塊可以指定鑰匙這一特點(diǎn)有個(gè)額外的好處,是可以在一定時(shí)期內(nèi)霸占某個(gè)對(duì)象的key。還記得前面說(shuō)過(guò)普通情況下鑰匙的使用原則嗎。現(xiàn)在不是普通情況了。你所取得的那把鑰匙不是永遠(yuǎn)不還,而是在退出同步代碼塊時(shí)才還。

          還用前面那個(gè)想連續(xù)用兩個(gè)上鎖房間的家伙打比方。怎樣才能在用完一間以后,繼續(xù)使用另一間呢。用同步代碼塊吧。先創(chuàng)建另外一個(gè)線程,做一個(gè)同步代碼塊,把那個(gè)代碼塊的鎖指向這個(gè)房子的鑰匙。然后啟動(dòng)那個(gè)線程。只要你能在進(jìn)入那個(gè)代碼塊時(shí)抓到這房子的鑰匙,你就可以一直保留到退出那個(gè)代碼塊。也就是說(shuō)你甚至可以對(duì)本房?jī)?nèi)所有上鎖的房間遍歷,甚至再sleep(10*60*1000),而房門(mén)口卻還有1000個(gè)線程在等這把鑰匙呢。很過(guò)癮吧。

          在此對(duì)sleep()方法和鑰匙的關(guān)聯(lián)性講一下。一個(gè)線程在拿到key后,且沒(méi)有完成同步的內(nèi)容時(shí),如果被強(qiáng)制sleep()了,那key還一直在它那兒。直到它再次運(yùn)行,做完所有同步內(nèi)容,才會(huì)歸還key。記住,那家伙只是干活干累了,去休息一下,他并沒(méi)干完他要干的事。為了避免別人進(jìn)入那個(gè)房間把里面搞的一團(tuán)糟,即使在睡覺(jué)的時(shí)候他也要把那唯一的鑰匙戴在身上。

          最后,也許有人會(huì)問(wèn),為什么要一把鑰匙通開(kāi),而不是一個(gè)鑰匙一個(gè)門(mén)呢?我想這純粹是因?yàn)閺?fù)雜性問(wèn)題。一個(gè)鑰匙一個(gè)門(mén)當(dāng)然更安全,但是會(huì)牽扯好多問(wèn)題。鑰匙的產(chǎn)生,保管,獲得,歸還等等。其復(fù)雜性有可能隨同步方法的增加呈幾何級(jí)數(shù)增加,嚴(yán)重影響效率。

          這也算是一個(gè)權(quán)衡的問(wèn)題吧。為了增加一點(diǎn)點(diǎn)安全性,導(dǎo)致效率大大降低,是多么不可取啊。

          摘自:http://www.54bk.com/more.asp?name=czp&id=2097

          一、當(dāng)兩個(gè)并發(fā)線程訪問(wèn)同一個(gè)對(duì)象object中的這個(gè)synchronized(this)同步代碼塊時(shí),一個(gè)時(shí)間內(nèi)只能有一個(gè)線程得到執(zhí)行。另一個(gè)線程必須等待當(dāng)前線程執(zhí)行完這個(gè)代碼塊以后才能執(zhí)行該代碼塊。

          二、然而,當(dāng)一個(gè)線程訪問(wèn)object的一個(gè)synchronized(this)同步代碼塊時(shí),另一個(gè)線程仍然可以訪問(wèn)該object中的非synchronized(this)同步代碼塊。

          三、尤其關(guān)鍵的是,當(dāng)一個(gè)線程訪問(wèn)object的一個(gè)synchronized(this)同步代碼塊時(shí),其他線程對(duì)object中所有其它synchronized(this)同步代碼塊的訪問(wèn)將被阻塞。

          四、第三個(gè)例子同樣適用其它同步代碼塊。也就是說(shuō),當(dāng)一個(gè)線程訪問(wèn)object的一個(gè)synchronized(this)同步代碼塊時(shí),它就獲得了這個(gè)object的對(duì)象鎖。結(jié)果,其它線程對(duì)該object對(duì)象所有同步代碼部分的訪問(wèn)都被暫時(shí)阻塞。

          五、以上規(guī)則對(duì)其它對(duì)象鎖同樣適用.

          舉例說(shuō)明:

          一、當(dāng)兩個(gè)并發(fā)線程訪問(wèn)同一個(gè)對(duì)象object中的這個(gè)synchronized(this)同步代碼塊時(shí),一個(gè)時(shí)間內(nèi)只能有一個(gè)線程得到執(zhí)行。另一個(gè)線程必須等待當(dāng)前線程執(zhí)行完這個(gè)代碼塊以后才能執(zhí)行該代碼塊。

          package ths;

          public class Thread1 implements Runnable {
          public void run() {
          synchronized(this) {
          for (int i = 0; i < 5; i++) {
          System.out.println(Thread.currentThread().getName() + " synchronized loop " + i);
          }
          }
          }
          public static void main(String[] args) {
          Thread1 t1 = new Thread1();
          Thread ta = new Thread(t1, "A");
          Thread tb = new Thread(t1, "B");
          ta.start();
          tb.start();
          }
          }

          結(jié)果:

          A synchronized loop 0
          A synchronized loop 1
          A synchronized loop 2
          A synchronized loop 3
          A synchronized loop 4
          B synchronized loop 0
          B synchronized loop 1
          B synchronized loop 2
          B synchronized loop 3
          B synchronized loop 4

          二、然而,當(dāng)一個(gè)線程訪問(wèn)object的一個(gè)synchronized(this)同步代碼塊時(shí),另一個(gè)線程仍然可以訪問(wèn)該object中的非synchronized(this)同步代碼塊。

          package ths;

          public class Thread2 {
          public void m4t1() {
          synchronized(this) {
          int i = 5;
          while( i-- > 0) {
          System.out.println(Thread.currentThread().getName() + " : " + i);
          try {
          Thread.sleep(500);
          } catch (InterruptedException ie) {
          }
          }
          }
          }
          public void m4t2() {
          int i = 5;
          while( i-- > 0) {
          System.out.println(Thread.currentThread().getName() + " : " + i);
          try {
          Thread.sleep(500);
          } catch (InterruptedException ie) {
          }
          }
          }
          public static void main(String[] args) {
          final Thread2 myt2 = new Thread2();
          Thread t1 = new Thread(
          new Runnable() {
          public void run() {
          myt2.m4t1();
          }
          }, "t1"
          );
          Thread t2 = new Thread(
          new Runnable() {
          public void run() {
          myt2.m4t2();
          }
          }, "t2"
          );
          t1.start();
          t2.start();
          }
          }

          結(jié)果:

          t1 : 4
          t2 : 4
          t1 : 3
          t2 : 3
          t1 : 2
          t2 : 2
          t1 : 1
          t2 : 1
          t1 : 0
          t2 : 0

          三、尤其關(guān)鍵的是,當(dāng)一個(gè)線程訪問(wèn)object的一個(gè)synchronized(this)同步代碼塊時(shí),其他線程對(duì)object中所有其它synchronized(this)同步代碼塊的訪問(wèn)將被阻塞。

          //修改Thread2.m4t2()方法:

          public void m4t2() {
          synchronized(this) {
          int i = 5;
          while( i-- > 0) {
          System.out.println(Thread.currentThread().getName() + " : " + i);
          try {
          Thread.sleep(500);
          } catch (InterruptedException ie) {
          }
          }
          }

          }

          結(jié)果:

          t1 : 4
          t1 : 3
          t1 : 2
          t1 : 1
          t1 : 0
          t2 : 4
          t2 : 3
          t2 : 2
          t2 : 1
          t2 : 0

          四、第三個(gè)例子同樣適用其它同步代碼塊。也就是說(shuō),當(dāng)一個(gè)線程訪問(wèn)object的一個(gè)synchronized(this)同步代碼塊時(shí),它就獲得了這個(gè)object的對(duì)象鎖。結(jié)果,其它線程對(duì)該object對(duì)象所有同步代碼部分的訪問(wèn)都被暫時(shí)阻塞。

          //修改Thread2.m4t2()方法如下:

          public synchronized void m4t2() {
          int i = 5;
          while( i-- > 0) {
          System.out.println(Thread.currentThread().getName() + " : " + i);
          try {
          Thread.sleep(500);
          } catch (InterruptedException ie) {
          }
          }
          }

          結(jié)果:

          t1 : 4
          t1 : 3
          t1 : 2
          t1 : 1
          t1 : 0
          t2 : 4
          t2 : 3
          t2 : 2
          t2 : 1
          t2 : 0

          五、以上規(guī)則對(duì)其它對(duì)象鎖同樣適用:

          package ths;

          public class Thread3 {
          class Inner {
          private void m4t1() {
          int i = 5;
          while(i-- > 0) {
          System.out.println(Thread.currentThread().getName() + " : Inner.m4t1()=" + i);
          try {
          Thread.sleep(500);
          } catch(InterruptedException ie) {
          }
          }
          }
          private void m4t2() {
          int i = 5;
          while(i-- > 0) {
          System.out.println(Thread.currentThread().getName() + " : Inner.m4t2()=" + i);
          try {
          Thread.sleep(500);
          } catch(InterruptedException ie) {
          }
          }
          }
          }
          private void m4t1(Inner inner) {
          synchronized(inner) { //使用對(duì)象鎖
          inner.m4t1();
          }
          }
          private void m4t2(Inner inner) {
          inner.m4t2();
          }
          public static void main(String[] args) {
          final Thread3 myt3 = new Thread3();
          final Inner inner = myt3.new Inner();
          Thread t1 = new Thread(
          new Runnable() {
          public void run() {
          myt3.m4t1(inner);
          }
          }, "t1"
          );
          Thread t2 = new Thread(
          new Runnable() {
          public void run() {
          myt3.m4t2(inner);
          }
          }, "t2"
          );
          t1.start();
          t2.start();
          }
          }

          結(jié)果:

          盡管線程t1獲得了對(duì)Inner的對(duì)象鎖,但由于線程t2訪問(wèn)的是同一個(gè)Inner中的非同步部分。所以兩個(gè)線程互不干擾。

          t1 : Inner.m4t1()=4
          t2 : Inner.m4t2()=4
          t1 : Inner.m4t1()=3
          t2 : Inner.m4t2()=3
          t1 : Inner.m4t1()=2
          t2 : Inner.m4t2()=2
          t1 : Inner.m4t1()=1
          t2 : Inner.m4t2()=1
          t1 : Inner.m4t1()=0
          t2 : Inner.m4t2()=0

          現(xiàn)在在Inner.m4t2()前面加上synchronized:

          private synchronized void m4t2() {
          int i = 5;
          while(i-- > 0) {
          System.out.println(Thread.currentThread().getName() + " : Inner.m4t2()=" + i);
          try {
          Thread.sleep(500);
          } catch(InterruptedException ie) {
          }
          }
          }

          結(jié)果:

          盡管線程t1與t2訪問(wèn)了同一個(gè)Inner對(duì)象中兩個(gè)毫不相關(guān)的部分,但因?yàn)閠1先獲得了對(duì)Inner的對(duì)象鎖,所以t2對(duì)Inner.m4t2()的訪問(wèn)也被阻塞,因?yàn)閙4t2()是Inner中的一個(gè)同步方法。

          t1 : Inner.m4t1()=4
          t1 : Inner.m4t1()=3
          t1 : Inner.m4t1()=2
          t1 : Inner.m4t1()=1
          t1 : Inner.m4t1()=0
          t2 : Inner.m4t2()=4
          t2 : Inner.m4t2()=3
          t2 : Inner.m4t2()=2
          t2 : Inner.m4t2()=1
          t2 : Inner.m4t2()=0

          同步買(mǎi)票問(wèn)題

          public class TicketsSystem {
           public static void main(String[] args) {
            SellThread st = new SellThread();
            Thread th1 = new Thread(st);
            th1.start();

            Thread th2 = new Thread(st);
            th2.start();

            Thread th3 = new Thread(st);
            th3.start();
           }

          }

          class SellThread implements Runnable {
           private int number=10;
           String s = new String();

           public void run() {
            while (number > 0) {
             synchronized (s) {
              System.out.println("第" + number + "個(gè)人在"
                + Thread.currentThread().getName() + "買(mǎi)票");
             }
             number--;
             try {
              Thread.sleep(10);
             } catch (InterruptedException e) {
              e.printStackTrace();
             }
            }
           }
          }

           

          Java線程:線程的同步-同步塊
           
          對(duì)于同步,除了同步方法外,還可以使用同步代碼塊,有時(shí)候同步代碼塊會(huì)帶來(lái)比同步方法更好的效果。
           
          追其同步的根本的目的,是控制競(jìng)爭(zhēng)資源的正確的訪問(wèn),因此只要在訪問(wèn)競(jìng)爭(zhēng)資源的時(shí)候保證同一時(shí)刻只能一個(gè)線程訪問(wèn)即可,因此Java引入了同步代碼快的策略,以提高性能。
           
          在上個(gè)例子的基礎(chǔ)上,對(duì)oper方法做了改動(dòng),由同步方法改為同步代碼塊模式,程序的執(zhí)行邏輯并沒(méi)有問(wèn)題。
           
           
          /**
          * Java線程:線程的同步-同步代碼塊
          *
          * @author leizhimin 2009-11-4 11:23:32
          */

          public class Test {
                  public static void main(String[] args) {
                          User u = new User("張三", 100);
                          MyThread t1 = new MyThread("線程A", u, 20);
                          MyThread t2 = new MyThread("線程B", u, -60);
                          MyThread t3 = new MyThread("線程C", u, -80);
                          MyThread t4 = new MyThread("線程D", u, -30);
                          MyThread t5 = new MyThread("線程E", u, 32);
                          MyThread t6 = new MyThread("線程F", u, 21);

                          t1.start();
                          t2.start();
                          t3.start();
                          t4.start();
                          t5.start();
                          t6.start();
                  }
          }

          class MyThread extends Thread {
                  private User u;
                  private int y = 0;

                  MyThread(String name, User u, int y) {
                          super(name);
                          this.u = u;
                          this.y = y;
                  }

                  public void run() {
                          u.oper(y);
                  }
          }

          class User {
                  private String code;
                  private int cash;

                  User(String code, int cash) {
                          this.code = code;
                          this.cash = cash;
                  }

                  public String getCode() {
                          return code;
                  }

                  public void setCode(String code) {
                          this.code = code;
                  }

                  /**
                   * 業(yè)務(wù)方法
                   *
                   * @param x 添加x萬(wàn)元
                   */

                  public void oper(int x) {
                          try {
                                  Thread.sleep(10L);
                                  synchronized (this) {
                                          this.cash += x;
                                          System.out.println(Thread.currentThread().getName() + "運(yùn)行結(jié)束,增加“" + x + "”,當(dāng)前用戶賬戶余額為:" + cash);
                                  }
                                  Thread.sleep(10L);
                          } catch (InterruptedException e) {
                                  e.printStackTrace();
                          }
                  }

                  @Override
                  public String toString() {
                          return "User{" +
                                          "code='" + code + '\'' +
                                          ", cash=" + cash +
                                          '}';
                  }
          }
           
          線程E運(yùn)行結(jié)束,增加“32”,當(dāng)前用戶賬戶余額為:132
          線程B運(yùn)行結(jié)束,增加“-60”,當(dāng)前用戶賬戶余額為:72
          線程D運(yùn)行結(jié)束,增加“-30”,當(dāng)前用戶賬戶余額為:42
          線程F運(yùn)行結(jié)束,增加“21”,當(dāng)前用戶賬戶余額為:63
          線程C運(yùn)行結(jié)束,增加“-80”,當(dāng)前用戶賬戶余額為:-17
          線程A運(yùn)行結(jié)束,增加“20”,當(dāng)前用戶賬戶余額為:3

          Process finished with exit code 0
           
          注意:
          在使用synchronized關(guān)鍵字時(shí)候,應(yīng)該盡可能避免在synchronized方法或synchronized塊中使用sleep或者yield方法,因?yàn)閟ynchronized程序塊占有著對(duì)象鎖,你休息那么其他的線程只能一邊等著你醒來(lái)執(zhí)行完了才能執(zhí)行。不但嚴(yán)重影響效率,也不合邏輯。
          同樣,在同步程序塊內(nèi)調(diào)用yeild方法讓出CPU資源也沒(méi)有意義,因?yàn)槟阏加弥i,其他互斥線程還是無(wú)法訪問(wèn)同步程序塊。當(dāng)然與同步程序塊無(wú)關(guān)的線程可以獲得更多的執(zhí)行時(shí)間。

           

          posted @ 2011-09-23 10:36 順其自然EVO| 編輯 收藏

          僅列出標(biāo)題
          共394頁(yè): First 上一頁(yè) 386 387 388 389 390 391 392 393 394 下一頁(yè) 
          <2025年7月>
          293012345
          6789101112
          13141516171819
          20212223242526
          272829303112
          3456789

          導(dǎo)航

          統(tǒng)計(jì)

          常用鏈接

          留言簿(55)

          隨筆分類

          隨筆檔案

          文章分類

          文章檔案

          搜索

          最新評(píng)論

          閱讀排行榜

          評(píng)論排行榜

          主站蜘蛛池模板: 绥阳县| 邵东县| 奎屯市| 曲松县| 香港 | 南和县| 耒阳市| 岱山县| 无极县| 十堰市| 黑水县| 讷河市| 民乐县| 平远县| 虹口区| 洛隆县| 清流县| 锦州市| 久治县| 惠安县| 当阳市| 清水县| 玉屏| 博爱县| 沙坪坝区| 阿荣旗| 临朐县| 新密市| 阳信县| 叙永县| 彰化县| 定结县| 工布江达县| 南昌市| 吉安市| 开平市| 北票市| 盱眙县| 卢龙县| 江安县| 鞍山市|