更新時(shí)間:2020-01-17 15:47:13 來(lái)源:動(dòng)力節(jié)點(diǎn) 瀏覽2394次
為什么等待和通知是在 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)編寫代碼,并通過(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)視器的思想。在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è)原因是圍繞鉆石形繼承問(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 foo()
/ \
/ \
foo() B C foo()
\ /
\ /
D foo()
即使我們刪除鉆石的頂部 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++。
如果你的Serializable類包含一個(gè)不可序列化的成員,會(huì)發(fā)生什么?你是如何解決的?
任何序列化該類的嘗試都會(huì)因NotSerializableException而失敗,但這可以通過(guò)在 Java中 為 static 設(shè)置瞬態(tài)(trancient)變量來(lái)輕松解決。
Java 序列化相關(guān)的常見(jiàn)問(wèn)題
Java 序列化是一個(gè)重要概念, 但它很少用作持久性解決方案, 開(kāi)發(fā)人員大多忽略了 Java 序列化 API。根據(jù)我的經(jīng)驗(yàn), Java 序列化在任何 Java核心內(nèi)容面試中都是一個(gè)相當(dāng)重要的話題, 在幾乎所有的網(wǎng)面試中, 我都遇到過(guò)一兩個(gè) Java 序列化問(wèn)題, 我看過(guò)一次面試, 在問(wèn)幾個(gè)關(guān)于序列化的問(wèn)題之后候選人開(kāi)始感到不自在, 因?yàn)槿狈@方面的經(jīng)驗(yàn)。
他們不知道如何在 Java 中序列化對(duì)象, 或者他們不熟悉任何 Java 示例來(lái)解釋序列化, 忘記了諸如序列化在 Java 中如何工作, 什么是標(biāo)記接口, 標(biāo)記接口的目的是什么, 瞬態(tài)變量和可變變量之間的差異, 可序列化接口具有多少種方法, 在 Java 中,Serializable 和 Externalizable 有什么區(qū)別, 或者在引入注解之后, 為什么不用 @Serializable 注解或替換 Serializalbe 接口。
在本文中,我們將從初學(xué)者和高級(jí)別進(jìn)行提問(wèn), 這對(duì)新手和具有多年 Java 開(kāi)發(fā)經(jīng)驗(yàn)的高級(jí)開(kāi)發(fā)人員同樣有益。
為什么Java中 wait 方法需要在 synchronized 的方法中調(diào)用?
另一個(gè)棘手的核心 Java 問(wèn)題,wait 和 notify。它們是在有 synchronized 標(biāo)記的方法或 synchronized 塊中調(diào)用的,因?yàn)?wait 和 modify 需要監(jiān)視對(duì)其上調(diào)用 wait 或 notify-get 的 Object。
大多數(shù)Java開(kāi)發(fā)人員都知道對(duì)象類的 wait(),notify() 和 notifyAll()方法必須在Java中的 synchronized 方法或 synchronized 塊中調(diào)用, 但是我們想過(guò)多少次, 為什么在 Java 中 wait, notify 和 notifyAll 來(lái)自 synchronized 塊或方法?
最近這個(gè)問(wèn)題在Java面試中被問(wèn)到我的一位朋友,他思索了一下,并回答說(shuō): 如果我們不從同步上下文中調(diào)用 wait() 或 notify() 方法,我們將在 Java 中收到 IllegalMonitorStateException。
他的回答從實(shí)際效果上年是正確的,但面試官對(duì)這樣的答案不會(huì)完全滿意,并希望向他解釋這個(gè)問(wèn)題。面試結(jié)束后 他和我討論了同樣的問(wèn)題,我認(rèn)為他應(yīng)該告訴面試官關(guān)于 Java 中 wait()和 notify()之間的競(jìng)態(tài)條件,如果我們不在同步方法或塊中調(diào)用它們就可能存在。
讓我們看看競(jìng)態(tài)條件如何在Java程序中發(fā)生。它也是流行的線程面試問(wèn)題之一,并經(jīng)常在電話和面對(duì)面的Java開(kāi)發(fā)人員面試中出現(xiàn)。
為什么要等待來(lái)自 Java中的 synchronized 方法的 wait方法為什么必須從 Java 中的 synchronized 塊或方法調(diào)用 ?我們主要使用 wait(),notify() 或 notifyAll() 方法用于 Java 中的線程間通信。一個(gè)線程在檢查條件后正在等待,例如,在經(jīng)典的生產(chǎn)者 - 消費(fèi)者問(wèn)題中,如果緩沖區(qū)已滿,則生產(chǎn)者線程等待,并且消費(fèi)者線程通過(guò)使用元素在緩沖區(qū)中創(chuàng)建空間后通知生產(chǎn)者線程。調(diào)用notify()或notifyAll()方法向單個(gè)或多個(gè)線程發(fā)出一個(gè)條件已更改的通知,并且一旦通知線程離開(kāi) synchronized 塊,正在等待的所有線程開(kāi)始獲取正在等待的對(duì)象鎖定,幸運(yùn)的線程在重新獲取鎖之后從 wait() 方法返回并繼續(xù)進(jìn)行。
讓我們將整個(gè)操作分成幾步,以查看Java中wait()和notify()方法之間的競(jìng)爭(zhēng)條件的可能性,我們將使用Produce Consumer 線程示例更好地理解方案:
Producer 線程測(cè)試條件(緩沖區(qū)是是否完整)并確認(rèn)必須等待(找到緩沖區(qū)已滿)。
Consumer 線程在使用緩沖區(qū)中的元素后設(shè)置條件。
Consumer 線程調(diào)用 notify() 方法; 這是不會(huì)被聽(tīng)到的,因?yàn)?Producer 線程還沒(méi)有等待。
Producer 線程調(diào)用 wait() 方法并進(jìn)入等待狀態(tài)。
因此,由于競(jìng)態(tài)條件,我們可能會(huì)丟失通知,如果我們使用緩沖區(qū)或只使用一個(gè)元素,生產(chǎn)線程將永遠(yuǎn)等待,你的程序?qū)炱稹!霸趈ava同步中等待 notify 和 notifyall 現(xiàn)在讓我們考慮如何解決這個(gè)潛在的競(jìng)態(tài)條件?
這個(gè)競(jìng)態(tài)條件通過(guò)使用 Java 提供的 synchronized 關(guān)鍵字和鎖定來(lái)解決。為了調(diào)用 wait(),notify() 或 notifyAll(), 在Java中,我們必須獲得對(duì)我們調(diào)用方法的對(duì)象的鎖定。由于 Java 中的 wait() 方法在等待之前釋放鎖定并在從 wait() 返回之前重新獲取鎖定方法,我們必須使用這個(gè)鎖來(lái)確保檢查條件(緩沖區(qū)是否已滿)和設(shè)置條件(從緩沖區(qū)獲取元素)是原子的,這可以通過(guò)在 Java 中使用 synchronized 方法或塊來(lái)實(shí)現(xiàn)。
我不確定這是否是面試官實(shí)際期待的,但這個(gè)我認(rèn)為至少有意義,請(qǐng)糾正我如果我錯(cuò)了,請(qǐng)告訴我們是否還有其他令人信服的理由調(diào)用 wait(),notify() 或 Java 中的 notifyAll() 方法。
總結(jié)一下,我們用 Java 中的 synchronized 方法或 synchronized 塊調(diào)用 Java 中的 wait(),notify() 或 notifyAll() 方法來(lái)避免:
1) Java 會(huì)拋出 IllegalMonitorStateException,如果我們不調(diào)用來(lái)自同步上下文的wait(),notify()或者notifyAll()方法。
2) Javac 中 wait 和 notify 方法之間的任何潛在競(jìng)爭(zhēng)條件。
以上就是動(dòng)力節(jié)點(diǎn)Java培訓(xùn)機(jī)構(gòu)小編介紹的“比較難的Java程序員面試題集錦”的內(nèi)容,希望對(duì)大家有幫助,如有疑問(wèn),請(qǐng)?jiān)诰€咨詢,有專業(yè)老師隨時(shí)為你服務(wù)。
相關(guā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