更新時(shí)間:2019-08-05 16:36:55 來(lái)源:動(dòng)力節(jié)點(diǎn) 瀏覽5403次
這是我收集的幾個(gè)最棘手的Java面試問(wèn)題列表。這些問(wèn)題主要來(lái)自Java核心部分,不涉及JavaEE相關(guān)問(wèn)題。你可能知道這些棘手的Java問(wèn)題的答案,或者覺(jué)得這些不足以挑戰(zhàn)你的Java知識(shí),但這些問(wèn)題都是容易在各種Java面試中被問(wèn)到的,而且包括我的朋友和同事在內(nèi)的許多程序員都覺(jué)得很難回答。
為什么等待和通知是在Object類而不是Thread中聲明的?
一個(gè)棘手的Java問(wèn)題,如果Java編程語(yǔ)言不是你設(shè)計(jì)的,你怎么能回答這個(gè)問(wèn)題呢。Java編程的常識(shí)和深入了解有助于回答這種棘手的Java核心方面的面試問(wèn)題。
為什么wait,notify和notifyAll是在Object類中定義的而不是在Thread類中定義
這是有名的Java面試問(wèn)題,招2~4年經(jīng)驗(yàn)的到高級(jí)Java開(kāi)發(fā)人員面試都可能碰到。
這個(gè)問(wèn)題的好在它能反映了面試者對(duì)等待通知機(jī)制的了解,以及他對(duì)此主題的理解是否明確。就像為什么Java中不支持多繼承或者為什么String在Java中是final的問(wèn)題一樣,這個(gè)問(wèn)題也可能有多個(gè)答案。
為什么在Object類中定義wait和notify方法,每個(gè)人都能說(shuō)出一些理由。從我的面試經(jīng)驗(yàn)來(lái)看,wait和nofity仍然是大多數(shù)Java程序員最困惑的,特別是2到3年的開(kāi)發(fā)人員,如果他們要求使用wait和notify,他們會(huì)很困惑。因此,如果你去參加Java面試,請(qǐng)確保對(duì)wait和notify機(jī)制有充分的了解,并且可以輕松地使用wait來(lái)編寫(xiě)代碼,并通過(guò)生產(chǎn)者-消費(fèi)者問(wèn)題或?qū)崿F(xiàn)阻塞隊(duì)列等了解通知的機(jī)制。
為什么等待和通知需要從同步塊或方法中調(diào)用,以及Java中的wait,sleep和yield方法之間的差異,如果你還沒(méi)有讀過(guò),你會(huì)覺(jué)得有趣。為何wait,notify和notifyAll屬于Object類?為什么它們不應(yīng)該在Thread類中?以下是我認(rèn)為有意義的一些想法:
1)wait和notify不僅僅是普通方法或同步工具,更重要的是它們是Java中兩個(gè)線程之間的通信機(jī)制。對(duì)語(yǔ)言設(shè)計(jì)者而言,如果不能通過(guò)Java關(guān)鍵字(例如synchronized)實(shí)現(xiàn)通信此機(jī)制,同時(shí)又要確保這個(gè)機(jī)制對(duì)每個(gè)對(duì)象可用,那么Object類則是的正確聲明位置。記住同步和等待通知是兩個(gè)不同的領(lǐng)域,不要把它們看成是相同的或相關(guān)的。同步是提供互斥并確保Java類的線程安全,而wait和notify是兩個(gè)線程之間的通信機(jī)制。
2)每個(gè)對(duì)象都可上鎖,這是在Object類而不是Thread類中聲明wait和notify的另一個(gè)原因。
3)在Java中為了進(jìn)入代碼的臨界區(qū),線程需要鎖定并等待鎖定,他們不知道哪些線程持有鎖,而只是知道鎖被某個(gè)線程持有,并且他們應(yīng)該等待取得鎖,而不是去了解哪個(gè)線程在同步塊內(nèi),并請(qǐng)求它們釋放鎖定。
4)Java是基于Hoare的監(jiān)視器的思想(http://en.wikipedia.org/wiki/...。在Java中,所有對(duì)象都有一個(gè)監(jiān)視器。
線程在監(jiān)視器上等待,為執(zhí)行等待,我們需要2個(gè)參數(shù):
一個(gè)線程
一個(gè)監(jiān)視器(任何對(duì)象)
在Java設(shè)計(jì)中,線程不能被指定,它總是運(yùn)行當(dāng)前代碼的線程。但是,我們可以指定監(jiān)視器(這是我們稱之為等待的對(duì)象)。這是一個(gè)很好的設(shè)計(jì),因?yàn)槿绻覀兛梢宰屓魏纹渌€程在所需的監(jiān)視器上等待,這將導(dǎo)致“入侵”,導(dǎo)致在設(shè)計(jì)并發(fā)程序時(shí)會(huì)遇到困難。請(qǐng)記住,在Java中,所有在另一個(gè)線程的執(zhí)行中侵入的操作都被棄用了(例如stop方法)。
為什么Java中不支持多重繼承?
我發(fā)現(xiàn)這個(gè)Java核心問(wèn)題很難回答,因?yàn)槟愕拇鸢缚赡懿粫?huì)讓面試官滿意,在大多數(shù)情況下,面試官正在尋找答案中的關(guān)鍵點(diǎn),如果你提到這些關(guān)鍵點(diǎn),面試官會(huì)很高興。在Java中回答這種棘手問(wèn)題的關(guān)鍵是準(zhǔn)備好相關(guān)主題,以應(yīng)對(duì)后續(xù)的各種可能的問(wèn)題。
這是非常經(jīng)典的問(wèn)題,與為什么String在Java中是不可變的很類似;這兩個(gè)問(wèn)題之間的相似之處在于它們主要是由Java創(chuàng)作者的設(shè)計(jì)決策使然。
為什么Java不支持多重繼承,可以考慮以下兩點(diǎn):
1)第一個(gè)原因是圍繞鉆石:gem:形繼承問(wèn)題產(chǎn)生的歧義,考慮一個(gè)類A有foo()方法,然后B和C派生自A,并且有自己的foo()實(shí)現(xiàn),現(xiàn)在D類使用多個(gè)繼承派生自B和C,如果我們只引用foo(),編譯器將無(wú)法決定它應(yīng)該調(diào)用哪個(gè)foo()。這也稱為Diamond問(wèn)題,因?yàn)檫@個(gè)繼承方案的結(jié)構(gòu)類似于菱形,見(jiàn)下圖:
即使我們刪除鉆石的頂部A類并允許多重繼承,我們也將看到這個(gè)問(wèn)題含糊性的一面。如果你把這個(gè)理由告訴面試官,他會(huì)問(wèn)為什么C++可以支持多重繼承而Java不行。嗯,在這種情況下,我會(huì)試著向他解釋我下面給出的第二個(gè)原因,它不是因?yàn)榧夹g(shù)難度,而是更多的可維護(hù)和更清晰的設(shè)計(jì)是驅(qū)動(dòng)因素,雖然這只能由Java言語(yǔ)設(shè)計(jì)師確認(rèn),我們只是推測(cè)。維基百科鏈接有一些很好的解釋,說(shuō)明在使用多重繼承時(shí),由于鉆石問(wèn)題,不同的語(yǔ)言地址問(wèn)題是如何產(chǎn)生的。
2)對(duì)我來(lái)說(shuō)第二個(gè)也是更有說(shuō)服力的理由是,多重繼承確實(shí)使設(shè)計(jì)復(fù)雜化并在轉(zhuǎn)換、構(gòu)造函數(shù)鏈接等過(guò)程中產(chǎn)生問(wèn)題。假設(shè)你需要多重繼承的情況并不多,簡(jiǎn)單起見(jiàn),明智的決定是省略它。此外,Java可以通過(guò)使用接口支持單繼承來(lái)避免這種歧義。由于接口只有方法聲明而且沒(méi)有提供任何實(shí)現(xiàn),因此只有一個(gè)特定方法的實(shí)現(xiàn),因此不會(huì)有任何歧義。
為什么Java不支持運(yùn)算符重載?
另一個(gè)類似棘手的Java問(wèn)題。為什么C++支持運(yùn)算符重載而Java不支持?有人可能會(huì)說(shuō)+運(yùn)算符在Java中已被重載用于字符串連接,不要被這些論據(jù)所欺騙。
與C++不同,Java不支持運(yùn)算符重載。Java不能為程序員提供自由的標(biāo)準(zhǔn)算術(shù)運(yùn)算符重載,例如+,-,*和/等。如果你以前用過(guò)C++,那么Java與C++相比少了很多功能,例如Java不支持多重繼承,Java中沒(méi)有指針,Java中沒(méi)有引用傳遞。另一個(gè)類似的問(wèn)題是關(guān)于Java通過(guò)引用傳遞,這主要表現(xiàn)為Java是通過(guò)值還是引用傳參。雖然我不知道背后的真正原因,但我認(rèn)為以下說(shuō)法有些道理,為什么Java不支持運(yùn)算符重載。
1)簡(jiǎn)單性和清晰性。清晰性是Java設(shè)計(jì)者的目標(biāo)之一。設(shè)計(jì)者不是只想復(fù)制語(yǔ)言,而是希望擁有一種清晰,真正面向?qū)ο蟮恼Z(yǔ)言。添加運(yùn)算符重載比沒(méi)有它肯定會(huì)使設(shè)計(jì)更復(fù)雜,并且它可能導(dǎo)致更復(fù)雜的編譯器,或減慢JVM,因?yàn)樗枰鲱~外的工作來(lái)識(shí)別運(yùn)算符的實(shí)際含義,并減少優(yōu)化的機(jī)會(huì),以保證Java中運(yùn)算符的行為。
2)避免編程錯(cuò)誤。Java不允許用戶定義的運(yùn)算符重載,因?yàn)槿绻试S程序員進(jìn)行運(yùn)算符重載,將為同一運(yùn)算符賦予多種含義,這將使任何開(kāi)發(fā)人員的學(xué)習(xí)曲線變得陡峭,事情變得更加混亂。據(jù)觀察,當(dāng)語(yǔ)言支持運(yùn)算符重載時(shí),編程錯(cuò)誤會(huì)增加,從而增加了開(kāi)發(fā)和交付時(shí)間。由于Java和JVM已經(jīng)承擔(dān)了大多數(shù)開(kāi)發(fā)人員的責(zé)任,如在通過(guò)提供垃圾收集器進(jìn)行內(nèi)存管理時(shí),因?yàn)檫@個(gè)功能增加污染代碼的機(jī)會(huì),成為編程錯(cuò)誤之源,因此沒(méi)有多大意義。
3)JVM復(fù)雜性。從JVM的角度來(lái)看,支持運(yùn)算符重載使問(wèn)題變得更加困難。通過(guò)更直觀,更干凈的方式使用方法重載也能實(shí)現(xiàn)同樣的事情,因此不支持Java中的運(yùn)算符重載是有意義的。與相對(duì)簡(jiǎn)單的JVM相比,復(fù)雜的JVM可能導(dǎo)致JVM更慢,并為保證在Java中運(yùn)算符行為的確定性從而減少了優(yōu)化代碼的機(jī)會(huì)。
4)讓開(kāi)發(fā)工具處理更容易。這是在Java中不支持運(yùn)算符重載的另一個(gè)好處。省略運(yùn)算符重載使語(yǔ)言更容易處理,這反過(guò)來(lái)又更容易開(kāi)發(fā)處理語(yǔ)言的工具,例如IDE或重構(gòu)工具。Java中的重構(gòu)工具遠(yuǎn)勝于C++。
為什么String在Java中是不可變的?
我最喜歡的Java面試問(wèn)題,很棘手,但同時(shí)也非常有用。一些面試者也常問(wèn)這個(gè)問(wèn)題,為什么String在Java中是final的。
字符串在Java中是不可變的,因?yàn)镾tring對(duì)象緩存在String池中。由于緩存的字符串在多個(gè)客戶之間共享,因此始終存在風(fēng)險(xiǎn),其中一個(gè)客戶的操作會(huì)影響所有其他客戶。例如,如果一段代碼將String“Test”的值更改為“TEST”,則所有其他客戶也將看到該值。由于String對(duì)象的緩存性能是很重要的一方面,因此通過(guò)使String類不可變來(lái)避免這種風(fēng)險(xiǎn)。
同時(shí),String是final的,因此沒(méi)有人可以通過(guò)擴(kuò)展和覆蓋行為來(lái)破壞String類的不變性、緩存、散列值的計(jì)算等。String類不可變的另一個(gè)原因可能是由于HashMap。
由于把字符串作為HashMap鍵很受歡迎。對(duì)于鍵值來(lái)說(shuō),重要的是它們是不可變的,以便用它們檢索存儲(chǔ)在HashMap中的值對(duì)象。由于HashMap的工作原理是散列,因此需要具有相同的值才能正常運(yùn)行。如果在插入后修改了String的內(nèi)容,可變的String將在插入和檢索時(shí)生成兩個(gè)不同的哈希碼,可能會(huì)丟失Map中的值對(duì)象。
如果你是印度板球迷,你可能能夠與我的下一句話聯(lián)系起來(lái)。字符串是Java的VVSLaxman,即非常特殊的類。我還沒(méi)有看到一個(gè)沒(méi)有使用String編寫(xiě)的Java程序。這就是為什么對(duì)String的充分理解對(duì)于Java開(kāi)發(fā)人員來(lái)說(shuō)非常重要。
String作為數(shù)據(jù)類型,傳輸對(duì)象和中間人角色的重要性和流行性也使這個(gè)問(wèn)題在Java面試中很常見(jiàn)。
為什么String在Java中是不可變的是Java中最常被問(wèn)到的字符串訪問(wèn)問(wèn)題之一,它首先討論了什么是String,Java中的String如何與C和C++中的String不同,然后轉(zhuǎn)向在Java中什么是不可變對(duì)象,不可變對(duì)象有什么好處,為什么要使用它們以及應(yīng)該使用哪些場(chǎng)景。這個(gè)問(wèn)題有時(shí)也會(huì)問(wèn):“為什么String在Java中是final的”。在類似的說(shuō)明中,如果你正在準(zhǔn)備Java面試,我建議你看看Java編程面試公開(kāi)書(shū),這是高級(jí)和中級(jí)Java程序員的優(yōu)秀資源。它包含來(lái)自所有重要Java主題的問(wèn)題,包括多線程,集合,GC,JVM內(nèi)部以及Spring和Hibernate框架等。
正如我所說(shuō),這個(gè)問(wèn)題可能有很多可能的答案,而String類的唯一設(shè)計(jì)者可以放心地回答它。我在JoshuaBloch的EffectiveJava書(shū)中期待一些線索,但他也沒(méi)有提到它。我認(rèn)為以下幾點(diǎn)解釋了為什么String類在Java中是不可變的或final的:
1)想象字符串池沒(méi)有使字符串不可變,它根本不可能,因?yàn)樵谧址氐那闆r下,一個(gè)字符串對(duì)象/文字,例如“Test”已被許多參考變量引用,因此如果其中任何一個(gè)更改了值,其他參數(shù)將自動(dòng)受到影響,即假設(shè)
現(xiàn)在字符串B調(diào)用"Test".toUpperCase(),將同一個(gè)對(duì)象改為“TEST”,所以A也是“TEST”,這不是期望的結(jié)果。
下圖顯示了如何在堆內(nèi)存和字符串池中創(chuàng)建字符串。
2)字符串已被廣泛用作許多Java類的參數(shù),例如,為了打開(kāi)網(wǎng)絡(luò)連接,你可以將主機(jī)名和端口號(hào)作為字符串傳遞,你可以將數(shù)據(jù)庫(kù)URL作為字符串傳遞,以打開(kāi)數(shù)據(jù)庫(kù)連接,你可以通過(guò)將文件名作為參數(shù)傳遞給FileI/O類來(lái)打開(kāi)Java中的任何文件。如果String不是不可變的,這將導(dǎo)致嚴(yán)重的安全威脅,我的意思是有人可以訪問(wèn)他有權(quán)授權(quán)的任何文件,然后可以故意或意外地更改文件名并獲得對(duì)該文件的訪問(wèn)權(quán)限。由于不變性,你無(wú)需擔(dān)心這種威脅。這個(gè)原因也說(shuō)明了,為什么String在Java中是最終的,通過(guò)使java.lang.Stringfinal,Java設(shè)計(jì)者確保沒(méi)有人覆蓋String類的任何行為。
3)由于String是不可變的,它可以安全地共享許多線程,這對(duì)于多線程編程非常重要.并且避免了Java中的同步問(wèn)題,不變性也使得String實(shí)例在Java中是線程安全的,這意味著你不需要從外部同步String操作。關(guān)于String的另一個(gè)要點(diǎn)是由截取字符串SubString引起的內(nèi)存泄漏,這不是與線程相關(guān)的問(wèn)題,但也是需要注意的。
4)為什么String在Java中是不可變的另一個(gè)原因是允許String緩存其哈希碼,Java中的不可變String緩存其哈希碼,并且不會(huì)在每次調(diào)用String的hashcode方法時(shí)重新計(jì)算,這使得它在Java中的HashMap中使用的HashMap鍵非常快。簡(jiǎn)而言之,因?yàn)镾tring是不可變的,所以沒(méi)有人可以在創(chuàng)建后更改其內(nèi)容,這保證了String的hashCode在多次調(diào)用時(shí)是相同的。
5)String不可變的絕對(duì)最重要的原因是它被類加載機(jī)制使用,因此具有深刻和基本的安全考慮。如果String是可變的,加載“java.io.Writer”的請(qǐng)求可能已被更改為加載“mil.vogoon.DiskErasingWriter”.安全性和字符串池是使字符串不可變的主要原因。順便說(shuō)一句,上面的理由很好回答另一個(gè)Java面試問(wèn)題:“為什么String在Java中是最終的”。要想是不可變的,你必須是最終的,這樣你的子類不會(huì)破壞不變性。你怎么看?
為什么char數(shù)組比Java中的String更適合存儲(chǔ)密碼?
另一個(gè)基于String的棘手Java問(wèn)題,相信我只有很少的Java程序員可以正確回答這個(gè)問(wèn)題。這是一個(gè)真正艱難的核心Java面試問(wèn)題,并且需要對(duì)String的扎實(shí)知識(shí)才能回答這個(gè)問(wèn)題。
這是最近在Java面試中向我的一位朋友詢問(wèn)的問(wèn)題。他正在接受技術(shù)主管職位的面試,并且有超過(guò)6年的經(jīng)驗(yàn)。如果你還沒(méi)有遇到過(guò)這種情況,那么字符數(shù)組和字符串可以用來(lái)存儲(chǔ)文本數(shù)據(jù),但是選擇一個(gè)而不是另一個(gè)很難。但正如我的朋友所說(shuō),任何與String相關(guān)的問(wèn)題都必須對(duì)字符串的特殊屬性有一些線索,比如不變性,他用它來(lái)說(shuō)服訪提問(wèn)的人。在這里,我們將探討為什么你應(yīng)該使用char[]存儲(chǔ)密碼而不是String的一些原因。
字符串:1)由于字符串在Java中是不可變的,如果你將密碼存儲(chǔ)為純文本,它將在內(nèi)存中可用,直到垃圾收集器清除它.并且為了可重用性,會(huì)存在String在字符串池中,它很可能會(huì)保留在內(nèi)存中持續(xù)很長(zhǎng)時(shí)間,從而構(gòu)成安全威脅。
由于任何有權(quán)訪問(wèn)內(nèi)存轉(zhuǎn)儲(chǔ)的人都可以以明文形式找到密碼,這是另一個(gè)原因,你應(yīng)該始終使用加密密碼而不是純文本。由于字符串是不可變的,所以不能更改字符串的內(nèi)容,因?yàn)槿魏胃亩紩?huì)產(chǎn)生新的字符串,而如果你使用char[],你就可以將所有元素設(shè)置為空白或零。因此,在字符數(shù)組中存儲(chǔ)密碼可以明顯降低竊取密碼的安全風(fēng)險(xiǎn)。
2)Java本身建議使用JPasswordField的getPassword()方法,該方法返回一個(gè)char[]和不推薦使用的getTex()方法,該方法以明文形式返回密碼,由于安全原因。應(yīng)遵循Java團(tuán)隊(duì)的建議,堅(jiān)持標(biāo)準(zhǔn)而不是反對(duì)它。
3)使用String時(shí),總是存在在日志文件或控制臺(tái)中打印純文本的風(fēng)險(xiǎn),但如果使用Array,則不會(huì)打印數(shù)組的內(nèi)容而是打印其內(nèi)存位置。雖然不是一個(gè)真正的原因,但仍然有道理。
輸出
字符串密碼:Unknown
我還建議使用散列或加密的密碼而不是純文本,并在驗(yàn)證完成后立即從內(nèi)存中清除它。因此,在Java中,用字符數(shù)組用存儲(chǔ)密碼比字符串是更好的選擇。雖然僅使用char[]還不夠,還你需要擦除內(nèi)容才能更安全。
如何使用雙重檢查鎖定在Java中創(chuàng)建線程安全的單例?
艱難的核心Java面試問(wèn)題.這個(gè)Java問(wèn)題也常被問(wèn):什么是線程安全的單例,你怎么創(chuàng)建它。好吧,在Java5之前的版本,使用雙重檢查鎖定創(chuàng)建單例Singleton時(shí),如果多個(gè)線程試圖同時(shí)創(chuàng)建Singleton實(shí)例,則可能有多個(gè)Singleton實(shí)例被創(chuàng)建。從Java5開(kāi)始,使用Enum創(chuàng)建線程安全的Singleton很容易。但如果面試官堅(jiān)持雙重檢查鎖定,那么你必須為他們編寫(xiě)代碼。記得使用volatile變量。
為什么枚舉單例在Java中更好
枚舉單例是使用一個(gè)實(shí)例在Java中實(shí)現(xiàn)單例模式的新方法。雖然Java中的單例模式存在很長(zhǎng)時(shí)間,但枚舉單例是相對(duì)較新的概念,在引入Enum作為關(guān)鍵字和功能之后,從Java5開(kāi)始在實(shí)踐中。本文與之前關(guān)于Singleton的內(nèi)容有些相關(guān),其中討論了有關(guān)Singleton模式的面試中的常見(jiàn)問(wèn)題,以及10個(gè)Java枚舉示例,其中我們看到了如何通用枚舉可以。這篇文章是關(guān)于為什么我們應(yīng)該使用Eeame作為Java中的單例,它比傳統(tǒng)的單例方法相比有什么好處等等。
Java枚舉和單例模式
Java中的枚舉單例模式是使用枚舉在Java中實(shí)現(xiàn)單例模式。單例模式在Java中早有應(yīng)用,但使用枚舉類型創(chuàng)建單例模式時(shí)間卻不長(zhǎng).如果感興趣,你可以了解下構(gòu)建者設(shè)計(jì)模式和裝飾器設(shè)計(jì)模式。
1)枚舉單例易于書(shū)寫(xiě)
這是迄今為止最大的優(yōu)勢(shì),如果你在Java5之前一直在編寫(xiě)單例,你知道,即使雙檢查鎖定,你仍可以有多個(gè)實(shí)例。雖然這個(gè)問(wèn)題通過(guò)Java內(nèi)存模型的改進(jìn)已經(jīng)解決了,從Java5開(kāi)始的volatile類型變量提供了保證,但是對(duì)于許多初學(xué)者來(lái)說(shuō),編寫(xiě)起來(lái)仍然很棘手。與同步雙檢查鎖定相比,枚舉單例實(shí)在是太簡(jiǎn)單了。如果你不相信,那就比較一下下面的傳統(tǒng)雙檢查鎖定單例和枚舉單例的代碼:
在Java中使用枚舉的單例
這是我們通常聲明枚舉的單例的方式,它可能包含實(shí)例變量和實(shí)例方法,但為了簡(jiǎn)單起見(jiàn),我沒(méi)有使用任何實(shí)例方法,只是要注意,如果你使用的實(shí)例方法且該方法能改變對(duì)象的狀態(tài)的話,則需要確保該方法的線程安全。默認(rèn)情況下,創(chuàng)建枚舉實(shí)例是線程安全的,但Enum上的任何其他方法是否線程安全都是程序員的責(zé)任。
你可以通過(guò)EasySingleton.INSTANCE來(lái)處理它,這比在單例上調(diào)用getInstance()方法容易得多。
具有雙檢查鎖定的單例示例
下面的代碼是單例模式中雙重檢查鎖定的示例,此處的getInstance()方法檢查兩次,以查看INSTANCE是否為空,這就是為什么它被稱為雙檢查鎖定模式,請(qǐng)記住,雙檢查鎖定是代理之前Java5,但Java5內(nèi)存模型中易失變量的干擾,它應(yīng)該工作完美。
你可以調(diào)用DoubleCheckedLockingSingleton.getInstance()來(lái)獲取此單例類的訪問(wèn)權(quán)限。
現(xiàn)在,只需查看創(chuàng)建延遲加載的線程安全的Singleton所需的代碼量。使用枚舉單例模式,你可以在一行中具有該模式,因?yàn)閯?chuàng)建枚舉實(shí)例是線程安全的,并且由JVM進(jìn)行。
人們可能會(huì)爭(zhēng)辯說(shuō),有更好的方法來(lái)編寫(xiě)Singleton而不是雙檢查鎖定方法,但每種方法都有自己的優(yōu)點(diǎn)和缺點(diǎn),就像我最喜歡在類加載時(shí)創(chuàng)建的靜態(tài)字段Singleton,如下面所示,但請(qǐng)記住,這不是一個(gè)延遲加載單例:
單例模式用靜態(tài)工廠方法
這是我最喜歡的在Java中影響Singleton模式的方法之一,因?yàn)镾ingleton實(shí)例是靜態(tài)的,并且最后一個(gè)變量在類首次加載到內(nèi)存時(shí)初始化,因此實(shí)例的創(chuàng)建本質(zhì)上是線程安全的。
你可以調(diào)用Singleton.getSingleton()來(lái)獲取此類的訪問(wèn)權(quán)限。
2)枚舉單例自行處理序列化
傳統(tǒng)單例的另一個(gè)問(wèn)題是,一旦實(shí)現(xiàn)可序列化接口,它們就不再是Singleton,因?yàn)閞eadObject()方法總是返回一個(gè)新實(shí)例,就像Java中的構(gòu)造函數(shù)一樣。通過(guò)使用readResolve()方法,通過(guò)在以下示例中替換Singeton來(lái)避免這種情況:
如果Singleton類保持內(nèi)部狀態(tài),這將變得更加復(fù)雜,因?yàn)槟阈枰獦?biāo)記為transient(不被序列化),但使用枚舉單例,序列化由JVM進(jìn)行。
3)創(chuàng)建枚舉實(shí)例是線程安全的
如第1點(diǎn)所述,因?yàn)镋num實(shí)例的創(chuàng)建在默認(rèn)情況下是線程安全的,你無(wú)需擔(dān)心是否要做雙重檢查鎖定。
總之,在保證序列化和線程安全的情況下,使用兩行代碼枚舉單例模式是在Java5以后的世界中創(chuàng)建Singleton的最佳方式。你仍然可以使用其他流行的方法,如你覺(jué)得更好,歡迎討論。
編寫(xiě)Java程序時(shí),如何在Java中創(chuàng)建死鎖并修復(fù)它?
經(jīng)典但核心Java面試問(wèn)題之一。
如果你沒(méi)有參與過(guò)多線程并發(fā)Java應(yīng)用程序的編碼,你可能會(huì)失敗。
如何避免Java線程死鎖?
如何避免Java中的死鎖?是Java面試的熱門(mén)問(wèn)題之一,也是多線程的編程中的重口味之一,主要在招高級(jí)程序員時(shí)容易被問(wèn)到,且有很多后續(xù)問(wèn)題。盡管問(wèn)題看起來(lái)非常基本,但大多數(shù)Java開(kāi)發(fā)人員一旦你開(kāi)始深入,就會(huì)陷入困境。
面試問(wèn)題總是以“什么是死鎖:lock:?”開(kāi)始
當(dāng)兩個(gè)或多個(gè)線程在等待彼此釋放所需的資源(鎖定)并陷入無(wú)限等待即是死鎖。它僅在多任務(wù)或多線程的情況下發(fā)生。
如何檢測(cè)Java中的死鎖?
雖然這可以有很多答案,但我的版本是首先我會(huì)看看代碼,如果我看到一個(gè)嵌套的同步塊,或從一個(gè)同步的方法調(diào)用其他同步方法,或試圖在不同的對(duì)象上獲取鎖,如果開(kāi)發(fā)人員不是非常小心,就很容易造成死鎖。
另一種方法是在運(yùn)行應(yīng)用程序時(shí)實(shí)際鎖定時(shí)找到它,嘗試采取線程轉(zhuǎn)儲(chǔ),在Linux中,你可以通過(guò)kill-3命令執(zhí)行此操作,這將打印應(yīng)用程序日志文件中所有線程的狀態(tài),并且你可以看到哪個(gè)線程被鎖定在哪個(gè)線程對(duì)象上。
你可以使用fastthread.io網(wǎng)站等工具分析該線程轉(zhuǎn)儲(chǔ),這些工具允許你上載線程轉(zhuǎn)儲(chǔ)并對(duì)其進(jìn)行分析。
另一種方法是使用jConsole或VisualVM,它將顯示哪些線程被鎖定以及哪些對(duì)象被鎖定。
如果你有興趣了解故障排除工具和分析線程轉(zhuǎn)儲(chǔ)的過(guò)程,我建議你看看UriahLevy在多元視覺(jué)(PluraIsight)上《分析Java線程轉(zhuǎn)儲(chǔ)》課程。旨在詳細(xì)了解Java線程轉(zhuǎn)儲(chǔ),并熟悉其他流行的高級(jí)故障排除工具。
編寫(xiě)一個(gè)將導(dǎo)致死鎖的Java程序?
一旦你回答了前面的問(wèn)題,他們可能會(huì)要求你編寫(xiě)代碼,這將導(dǎo)致Java死鎖。
這是我的版本之一
如果method1()和method2()都由兩個(gè)或多個(gè)線程調(diào)用,則存在死鎖的可能性,因?yàn)槿绻€程1在執(zhí)行method1()時(shí)在Sting對(duì)象上獲取鎖,線程2在執(zhí)行method2()時(shí)在Integer對(duì)象上獲取鎖,等待彼此釋放Integer和String上的鎖以繼續(xù)進(jìn)行一步,但這永遠(yuǎn)不會(huì)發(fā)生。
此圖精確演示了我們的程序,其中一個(gè)線程在一個(gè)對(duì)象上持有鎖,并等待其他線程持有的其他對(duì)象鎖。
你可以看到,Thread1需要Thread2持有的Object2上的鎖,而Thread2希望獲得Thread1持有的Object1上的鎖。由于沒(méi)有線程愿意放棄,因此存在死鎖,Java程序被卡住。
其理念是,你應(yīng)該知道使用常見(jiàn)并發(fā)模式的正確方法,如果你不熟悉這些模式,那么JosePaumard《應(yīng)用于并發(fā)和多線程的常見(jiàn)Java模式》是學(xué)習(xí)的好起點(diǎn)。
相關(guān)閱讀
0基礎(chǔ) 0學(xué)費(fèi) 15天面授
有基礎(chǔ) 直達(dá)就業(yè)
業(yè)余時(shí)間 高薪轉(zhuǎn)行
工作1~3年,加薪神器
工作3~5年,晉升架構(gòu)
提交申請(qǐng)后,顧問(wèn)老師會(huì)電話與您溝通安排學(xué)習(xí)
初級(jí) 202925
初級(jí) 203221
初級(jí) 202629
初級(jí) 203743