56.?惰性初始化
- public ? class ?Lazy?{??
- ? private ? static ? boolean ?initial?=? false ;??
- ? static ?{??
- ??Thread?t?=? new ?Thread( new ?Runnable()?{??
- ??? public ? void ?run()?{??
- ????System.out.println( "befor..." ); //此句會(huì)輸出 ??
- ???? /* ?
- ?????*?由于使用Lazy.initial靜態(tài)成員,又因?yàn)長azy還未?初 ?
- ?????*?始化完成,所以該線程會(huì)在這里等待主線程初始化完成 ?
- ?????*/ ??
- ????initial?=? true ;??
- ????System.out.println( "after..." ); //此句不會(huì)輸出 ??
- ???}??
- ??});??
- ??t.start();??
- ?? try ?{??
- ???t.join(); //?主線程等待t線程結(jié)束 ??
- ??}? catch ?(InterruptedException?e)?{??
- ???e.printStackTrace();??
- ??}??
- ?}??
- ??
- ? public ? static ? void ?main(String[]?args)?{??
- ??System.out.println(initial);??
- ?}??
- }??
看看上面變態(tài)的程序,一個(gè)靜態(tài)變量的初始化由靜態(tài)塊里的線程來初始化,最后的結(jié)果怎樣?
?
當(dāng)一個(gè)線程訪問一個(gè)類的某個(gè)成員的時(shí)候,它會(huì)去檢查這個(gè)類是否已經(jīng)被初始化,在這一過程中會(huì)有以下四種情況:
1、?這個(gè)類尚未被初始化
2、?這個(gè)類正在被當(dāng)前線程初始化:這是對(duì)初始化的遞歸請(qǐng)求,會(huì)直接忽略掉(另,請(qǐng)參考《構(gòu)造器中靜態(tài)常量的引用問題》一節(jié))
3、?這個(gè)類正在被其他線程而不是當(dāng)前線程初始化:需等待其他線程初始化完成再使用類的Class對(duì)象,而不會(huì)兩個(gè)線程都會(huì)去初始化一遍(如果這樣,那不類會(huì)初始化兩遍,這顯示不合理)
4、?這個(gè)類已經(jīng)被初始化
當(dāng)主線程調(diào)用Lazy.main,它會(huì)檢查Lazy類是否已經(jīng)被初始化。此時(shí)它并沒有被初始化(情況1),所以主線程會(huì)記錄下當(dāng)前正在進(jìn)行的初始化,并開始對(duì)這個(gè)類進(jìn)行初始化。這個(gè)過程是:主線程會(huì)將initial的值設(shè)為false,然后在靜態(tài)塊中創(chuàng)建并啟動(dòng)一個(gè)初始化initial的線程t,該線程的run方法會(huì)將initial設(shè)為true,然后主線程會(huì)等待t線程執(zhí)行完畢,此時(shí),問題就來了。
由于t線程將Lazy.initial設(shè)為true之前,它也會(huì)去檢查Lazy類是否已經(jīng)被初始化。這時(shí),這個(gè)類正在被另外一個(gè)線程(mian線程)進(jìn)行初始化(情況3)。在這種情況下,當(dāng)前線程,也就是t線程,會(huì)等待Class對(duì)象直到初始化完成,可惜的是,那個(gè)正在進(jìn)行初始化工作的main線程,也正在等待t線程的運(yùn)行結(jié)束。因?yàn)檫@兩個(gè)線程現(xiàn)在正相互等待,形成了死鎖。
?
修正這個(gè)程序的方法就是讓主線程在等待線程前就完成初始化操作:
- public ? class ?Lazy?{??
- ? private ? static ? boolean ?initial?=? false ;??
- ? static ?Thread?t?=? new ?Thread( new ?Runnable()?{??
- ?? public ? void ?run()?{??
- ???initial?=? true ;??
- ??}??
- ?});??
- ? static ?{??
- ??t.start();??
- ?}??
- ??
- ? public ? static ? void ?main(String[]?args)?{??
- ?? //?讓Lazy類初始化完成后再調(diào)用join方法 ??
- ?? try ?{??
- ???t.join(); //?主線程等待t線程結(jié)束 ??
- ??}? catch ?(InterruptedException?e)?{??
- ???e.printStackTrace();??
- ??}??
- ??System.out.println(initial);??
- ?}??
- }??
雖然修正了該程序掛起問題,但如果還有另一線程要訪問Lazy的initial時(shí),則還是很有可能不等initial最后賦值就被使用了。
?
總之,在類的初始化期間等待某個(gè)線程很可能會(huì)造成死鎖,要讓類初始化的動(dòng)作序列盡可能地簡單。
57.?繼承內(nèi)部類
一般地,要想實(shí)例化一個(gè)內(nèi)部類,如類Inner1,需要提供一個(gè)外圍類的實(shí)例給構(gòu)造器。一般情況下,它是隱式地傳遞給內(nèi)部類的構(gòu)造器,但是它也是可以以 expression.super(args) 的方式即通過調(diào)用超類的構(gòu)造器顯式的傳遞。
- public ? class ?Outer?{??
- ? class ?Inner1? extends ?Outer{??
- ??Inner1(){??
- ??? super ();??
- ??}??
- ?}??
- ? class ?Inner2? extends ?Inner1{??
- ??Inner2(){??
- ???Outer. this . super ();??
- ??}??
- ??Inner2(Outer?outer){??
- ???outer. super ();??
- ??}??
- ?}??
- }??
- class ?WithInner?{??
- ? class ?Inner?{}??
- }??
- class ?InheritInner? extends ?WithInner.Inner?{??
- ? //?!?InheritInner()?{}?//?不能編譯 ??
- ? /* ?
- ??*?這里的super指InheritInner類的父類WithInner.Inner的默認(rèn)構(gòu)造函數(shù),而不是 ?
- ??*?WithInner的父類構(gòu)造函數(shù),這種特殊的語法只在繼承一個(gè)非靜態(tài)內(nèi)部類時(shí)才用到, ?
- ??*?表示繼承非靜態(tài)內(nèi)部類時(shí),外圍對(duì)象一定要存在,并且只能在?第一行調(diào)用,而且一 ?
- ??*?定要調(diào)用一下。為什么不能直接使用?super()或不直接寫出呢?最主要原因就是每個(gè) ?
- ??*?非靜態(tài)的內(nèi)部類都會(huì)與一個(gè)外圍類實(shí)例對(duì)應(yīng),這個(gè)外圍類實(shí)例是運(yùn)行時(shí)傳到內(nèi) ?
- ??*?部類里去的,所以在內(nèi)部類里可以直接使用那個(gè)對(duì)象(比如Outer.this),但這里 ?
- ??*?是在外部內(nèi)外?,使用時(shí)還是需要存在外圍類實(shí)例對(duì)象,所以這里就顯示的通過構(gòu)造 ?
- ??*?器傳遞進(jìn)來,并且在外圍對(duì)象上顯示的調(diào)用一下內(nèi)部類的構(gòu)造器,這樣就確保了在 ?
- ??*?繼承至一個(gè)類部類的情況下?,外圍對(duì)象一類會(huì)存在的約束。 ?
- ??*/ ??
- ?InheritInner(WithInner?wi)?{??
- ??wi. super ();??
- ?}??
- ??
- ? public ? static ? void ?main(String[]?args)?{??
- ??WithInner?wi?=? new ?WithInner();??
- ??InheritInner?ii?=? new ?InheritInner(wi);??
- ?}??
- }??
?
58.?Hash集合序列化問題
- class ?Super? implements ?Serializable{??
- ? //?HashSet要放置在父類中會(huì)百分百機(jī)率出現(xiàn) ??
- ? //?放置到子類中就不一定會(huì)出現(xiàn)問題了 ??
- ? final ?Set?set?=? new ?HashSet();???
- }??
- class ?Sub? extends ?Super?{??
- ? private ? int ?id;??
- ? public ?Sub( int ?id)?{??
- ?? this .id?=?id;??
- ??set.add( this );??
- ?}??
- ? public ? int ?hashCode()?{??
- ?? return ?id;??
- ?}??
- ? public ? boolean ?equals(Object?o)?{??
- ?? return ?(o? instanceof ?Sub)?&&?(id?==?((Sub)?o).id);??
- ?}??
- }??
- ??
- public ? class ?SerialKiller?{??
- ? public ? static ? void ?main(String[]?args)? throws ?Exception?{??
- ??Sub?sb?=? new ?Sub( 888 );??
- ??System.out.println(sb.set.contains(sb)); //?true ??
- ????
- ??ByteArrayOutputStream?bos?=? new ?ByteArrayOutputStream();??
- ?? new ?ObjectOutputStream(bos).writeObject(sb);??
- ????
- ??ByteArrayInputStream?bin?=? new ?ByteArrayInputStream(bos.toByteArray());??
- ??sb?=?(Sub)? new ?ObjectInputStream(bin).readObject();??
- ????
- ??System.out.println(sb.set.contains(sb)); //?false ??
- ?}??
- }??
Hash一類集合都實(shí)現(xiàn)了序列化的writeObject()與readObject()方法。這里錯(cuò)誤原因是由HashSet的readObject方法引起的。在某些情況下,這個(gè)方法會(huì)間接地調(diào)用某個(gè)未初始化對(duì)象的被覆寫的方法。為了組裝正在反序列化的HashSet,HashSet.readObject調(diào)用了HashMap.put方法,而put方法會(huì)去調(diào)用鍵的hashCode方法。由于整個(gè)對(duì)象圖正在被反序列
化,并沒有什么可以保證每個(gè)鍵在它的hashCode方法被調(diào)用時(shí)已經(jīng)被完全初始化了,因?yàn)镠ashSet是在父類中定義的,而在序列化HashSet時(shí)子類還沒有開始初始化(這里應(yīng)該是序列化)子類,所以這就造成了在父類中調(diào)用還沒有初始完成(此時(shí)id為0)的被子類覆寫的hashCode方法,導(dǎo)致該對(duì)象重新放入hash表格的位置與反序列化前不一樣了。hashCode返回了錯(cuò)誤的值,相應(yīng)的鍵值對(duì)條目將會(huì)放入錯(cuò)誤的單元格中,當(dāng)id被初始化為888時(shí),一切都太遲了。
?
這個(gè)程序的說明,包含了HashMap的readObject方法的序列化系統(tǒng)總體上違背了不能從類的構(gòu)造器或偽構(gòu)造器(如序列化的readObject)中調(diào)用可覆寫方法的規(guī)則。
?
如果一個(gè)HashSet、Hashtable或HashMap被序列化,那么請(qǐng)確認(rèn)它們的內(nèi)容沒有直接或間接地引用它們自身,即正在被序列化的對(duì)象。
?
另外,在readObject或readResolve方法中,請(qǐng)避免直接或間接地在正在進(jìn)行反序列化的對(duì)象上調(diào)用任何方法,因?yàn)檎诜葱蛄谢膶?duì)象處于不穩(wěn)定狀態(tài)。
59.?迷惑的內(nèi)部類
- public ? class ?Twisted?{??
- ? private ? final ?String?name;??
- ?Twisted(String?name)?{??
- ?? this .name?=?name;??
- ?}??
- ? //?私有的不能被繼承,但能被內(nèi)部類直接訪問 ??
- ? private ?String?name()?{??
- ?? return ?name;??
- ?}??
- ? private ? void ?reproduce()?{??
- ?? new ?Twisted( "reproduce" )?{??
- ??? void ?printName()?{??
- ???? //?name()為外部類的,因?yàn)闆]有被繼承過來 ??
- ????System.out.println(name()); //?main ??
- ???}??
- ??}.printName();??
- ?}??
- ??
- ? public ? static ? void ?main(String[]?args)?{??
- ?? new ?Twisted( "main" ).reproduce();??
- ?}??
- }??
在頂層的類型中,即本例中的Twisted類,所有的本地的、內(nèi)部的、嵌套的長匿名的類都可以毫無限制地訪問彼此的成員。
?
另一個(gè)原因是私有的不能被繼承。
60.?編譯期常量表達(dá)式
第一個(gè)PrintWords代表客戶端,第二個(gè)Words代表一個(gè)類庫:
- class ?PrintWords?{??
- ? public ? static ? void ?main(String[]?args)?{??
- ??System.out //引用常量變量 ??
- ????.println(Words.FIRST?+? "?" ???
- ??????+?Words.SECOND?+? "?" ???
- ??????+?Words.THIRD);??
- ?}??
- }??
- ??
- class ?Words?{??
- ? //?常量變量 ??
- ? public ? static ? final ?String?FIRST?=? "the" ;??
- ? //?非常量變量 ??
- ? public ? static ? final ?String?SECOND?=? null ;??
- ? //?常量變量 ??
- ? public ? static ? final ?String?THIRD?=? "set" ;??
- }??
現(xiàn)在假設(shè)你像下面這樣改變了那個(gè)庫類并且重新編譯了這個(gè)類,但并不重新編譯客戶端的程序PrintWords:
- class ?Words?{??
- ? public ? static ? final ?String?FIRST?=? "physics" ;??
- ? public ? static ? final ?String?SECOND?=? "chemistry" ;??
- ? public ? static ? final ?String?THIRD?=? "biology" ;??
- }??
此時(shí),端的程序會(huì)打印出什么呢?結(jié)果是 the chemistry set,不是the null set,也不是physics chemistry biology,為什么?原因就是 null 不是一個(gè)編譯期常量表達(dá)式,而其他兩個(gè)都是。
?
對(duì)于常量變量(如上面Words類中的FIRST、THIRD)的引用(如在PrintWords類中對(duì)Words.FIRST、Words.THIRD的引用)會(huì)在編譯期被轉(zhuǎn)換為它們所表示的常量的值(即PrintWords類中的Words.FIRST、Words.THIRD引用會(huì)替換成"the"與"set")。
?
一個(gè)常量變量(如上面Words類中的FIRST、THIRD)的定義是,一個(gè)在編譯期被常量表達(dá)式(即編譯期常量表達(dá)式)初
始化的final的原生類型或String類型的變量。
?
那什么是“編譯期常量表達(dá)式”?精確定義在[JLS 15.28]中可以找到,這樣要說的是null不是一個(gè)編譯期常量表達(dá)式。
?
由于常量變量會(huì)編譯進(jìn)客戶端,API的設(shè)計(jì)者在設(shè)計(jì)一個(gè)常量域之前應(yīng)該仔細(xì)考慮一下是否應(yīng)該定義成常量變量。
?
如果你使用了一個(gè)非常量的表達(dá)式去初始化一個(gè)域,甚至是一個(gè)final或,那么這個(gè)域就不是一個(gè)常量。下面你可以通過將一個(gè)常量表達(dá)式傳給一個(gè)方法使用得它變成一個(gè)非常量:
- class ?Words?{??
- ? //?以下都成非常量變量 ??
- ? public ? static ? final ?String?FIRST?=?ident( "the" );??
- ? public ? static ? final ?String?SECOND?=?ident( null );??
- ? public ? static ? final ?String?THIRD?=?ident( "set" );??
- ? private ? static ?String?ident(String?s)?{??
- ?? return ?s;??
- ?}??
- }??
總之,常量變量將會(huì)被編譯進(jìn)那些引用它們的類中。一個(gè)常量變量就是任何常量表達(dá)式初始化的原生類型或字符串變量。且null不是一個(gè)常量表達(dá)式。
61.?打亂數(shù)組
- class ?Shuffle?{??
- ? private ? static ?Random?rd?=? new ?Random();??
- ? public ? static ? void ?shuffle(Object[]?a)?{??
- ?? for ?( int ?i?=? 0 ;?i?<?a.length;?i++)?{??
- ???swap(a,?i,?rd.nextInt(a.length));??
- ??}??
- ?}??
- ? public ? static ? void ?swap(Object[]?a,? int ?i,? int ?j)?{??
- ??Object?tmp?=?a[i];??
- ??a[i]?=?a[j];??
- ??a[j]?=?tmp;??
- ?}??
- ? public ? static ? void ?main(String[]?args)?{??
- ??Map?map?=? new ?TreeMap();??
- ?? for ?( int ?i?=? 0 ;?i?<? 9 ;?i++)?{??
- ???map.put(i,? 0 );??
- ??}??
- ????
- ?? //?測試數(shù)組上的每個(gè)位置放置的元素是否等概率 ??
- ?? for ?( int ?i?=? 0 ;?i?<? 10000 ;?i++)?{??
- ???Integer[]?intArr?=? new ?Integer[]?{? 0 ,? 1 ,? 2 ,? 3 ,? 4 ,? 5 ,? 6 ,? 7 ,? 8 ?};??
- ???shuffle(intArr);??
- ??? for ?( int ?j?=? 0 ;?j?<? 9 ;?j++)?{??
- ????map.put(j,(Integer)map.get(j)+intArr[j]);??
- ???}??
- ??}??
- ??System.out.println(map);??
- ?? for ?( int ?i?=? 0 ;?i?<? 9 ;?i++)?{??
- ???map.put(i,(Integer)?map.get(i)/10000f);??
- ??}??
- ??System.out.println(map);??
- ?}??
- }??
上面的算法不是很等概率的讓某個(gè)元素打亂到其位置,程序運(yùn)行了多次,大致的結(jié)果為:
{0=36031, 1=38094, 2=39347, 3=40264, 4=41374, 5=41648, 6=41780, 7=41188, 8=40274}
{0=3.6031, 1=3.8094, 2=3.9347, 3=4.0264, 4=4.1374, 5=4.1648, 6=4.178, 7=4.1188, 8=4.0274}
?
如果某個(gè)位置上等概率出現(xiàn)這9個(gè)值的話,則平均值會(huì)趨近于4,但測試的結(jié)果表明:開始的時(shí)候比較低,然后增長超過了平均值,最后又降下來了。
?
如果改用下面算法:
- public ? static ? void ?shuffle(Object[]?a)?{??
- ? for ?( int ?i?=? 0 ;?i?<?a.length;?i++)?{??
- ??swap(a,?i,?i?+?rd.nextInt(a.length?-?i));??
- ?}??
- }??
多次測試的結(jié)果大致如下:
{0=40207, 1=40398, 2=40179, 3=39766, 4=39735, 5=39710, 6=40074, 7=39871, 8=40060}
{0=4.0207, 1=4.0398, 2=4.0179, 3=3.9766, 4=3.9735, 5=3.971, 6=4.0074, 7=3.9871, 8=4.006}
所以修改后的算法是合理的。
?
另一種打亂集合的方式是通過Api中的Collections工具類:
- public ? static ? void ?shuffle(Object[]?a)?{??
- ?Collections.shuffle(Arrays.asList(a));??
- }??
其實(shí)算法與上面的基本相似,當(dāng)然我們使用API中提供的會(huì)更好,會(huì)在效率上獲得最大的受益。
更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主
微信掃碼或搜索:z360901061

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