談?wù)劷涌诤统橄箢愑惺裁磪^(qū)別?
接口是對行為的抽象,它是抽象方法的集合,利用接口可以達(dá)到API定義和實(shí)現(xiàn)分離的目的。接口,不能實(shí)例化;不能包含任何非常量成員,同時,沒有非靜態(tài)方法實(shí)現(xiàn),也就是說要么是抽象方法,要么是靜態(tài)方法。Java標(biāo)準(zhǔn)類庫中,定義了非常多的接口,比如java.util.List。
抽象類是不能實(shí)例化的類,用abstract關(guān)鍵字修飾class,其目的主要是代碼重用。除了不能實(shí)例化,形式上和一般的Java類并沒有太大區(qū)別,可以有一個或者多個抽象方法,也可
以沒有抽象方法。抽象類大多用于抽取相關(guān)Java類的共用方法實(shí)現(xiàn)或者是共同成員變量,然后通過繼承的方式達(dá)到代碼復(fù)用的目的。Java標(biāo)準(zhǔn)庫中,比如collection框架,很多通用
部分就被抽取成為抽象類,例如java.util.AbstractList。
進(jìn)行面向?qū)ο缶幊?,掌握基本的設(shè)計原則是必須的,我今天介紹最通用的部分,也就是所謂的S.O.L.I.D原則。
單一職責(zé)類或者對象最好是只有單一職責(zé),在程序設(shè)計中如果發(fā)現(xiàn)某個類承擔(dān)著多種義務(wù),可以考慮進(jìn)行拆分。
開關(guān)原則設(shè)計要對擴(kuò)展開放,對修改關(guān)閉。換句話說,程序設(shè)計應(yīng)保證平滑的擴(kuò)展性,盡量避免因?yàn)樾略鐾惞δ芏薷囊延袑?shí)現(xiàn),這樣可以少產(chǎn)出些回歸(regression)問題。
里氏替換這是面向?qū)ο蟮幕疽刂?,進(jìn)行繼承關(guān)系抽象時,凡是可以用父類或者基類的地方,都可以用子類替換。
接口分離我們在進(jìn)行類和接口設(shè)計時,如果在一個接口里定義了太多方法,其子類很可能面臨兩難,就是只有部分方法對它是有意義的,這就破壞了程序的內(nèi)聚性。對于這種情況,可以通過拆分成功能單一的多個接口,將行為進(jìn)行解耦。在未來維護(hù)中,如果某個接口設(shè)計有變,不會對使用其他接口的子類構(gòu)成影響
依賴反轉(zhuǎn)實(shí)體應(yīng)該依賴于抽象而不是實(shí)現(xiàn)。也就是說高層次模塊,不應(yīng)該依賴于低層次模塊,而是應(yīng)該基于抽象。實(shí)踐這一原則是保證產(chǎn)品代碼之間適當(dāng)耦合度的法寶
談?wù)勀阒赖脑O(shè)計模式?請手動實(shí)現(xiàn)單例模式,Spring等框架中使用了哪些模式?
設(shè)計模式可以分為創(chuàng)建型模式、結(jié)構(gòu)型模式和行為型模式。
創(chuàng)建型模式,是對對象創(chuàng)建過程的各種問題和解決方案的總結(jié),包括各種工廠模式(Factory、AbstractFactory)、單例模式(Singleton)、構(gòu)建器模式(Builder)、原型模式(ProtoType)。
結(jié)構(gòu)型模式,是針對軟件設(shè)計結(jié)構(gòu)的總結(jié),關(guān)注于類、對象繼承、組合方式的實(shí)踐經(jīng)驗(yàn)。常見的結(jié)構(gòu)型模式,包括橋接模式(Bridge)、適配器模式(Adapter)、裝飾者模式(Decorator)、代理模式(Proxy)、組合模式(Composite)、外觀模式(Facade)、享元模式(Flyweight)等。
行為型模式,是從類或?qū)ο笾g交互、職責(zé)劃分等角度總結(jié)的模式。比較常見的行為型模式有策略模式(Strategy)、解釋器模式(Interpreter)、命令模式(Command)、觀察者模式(Observer)、迭代器模式(Iterator)、模板方法模式(TemplateMethod)、訪問者模式(Visitor)。更多相關(guān)內(nèi)容你可以參考:https://en.wikipedia.org/wiki/Design_Patterns
InputStream是一個抽象類,標(biāo)準(zhǔn)類庫中提供了FileInputStream、ByteArrayInputStream等各種不同的子類,分別從不同角度對InputStream進(jìn)行了功能擴(kuò)展,這是典型的裝飾器模式應(yīng)用案例。識別裝飾器模式,可以通過識別類設(shè)計特征來進(jìn)行判斷,也就是其類構(gòu)造函數(shù)以相同的抽象類或者接口為輸入?yún)?shù)
創(chuàng)建型模式尤其是工廠模式,在我們的代碼中隨處可見,我舉個相對不同的API設(shè)計實(shí)踐。比如,JDK最新版本中HTTP/2ClientAPI,下面這個創(chuàng)建HttpRequest的過程,就是典型的構(gòu)建器模式(Builder),通常會被實(shí)現(xiàn)成fuent風(fēng)格的API,也有人叫它方法鏈。使用構(gòu)建器模式,可以比較優(yōu)雅地解決構(gòu)建復(fù)雜對象的麻煩,這里的“復(fù)雜”是指類似需要輸入的參數(shù)組合較多,如果用構(gòu)造函數(shù),我們往往需要為每一種可能的輸入?yún)?shù)組合實(shí)現(xiàn)相應(yīng)的構(gòu)造函數(shù),一系列復(fù)雜的構(gòu)造函數(shù)會讓代碼閱讀性和可維護(hù)性變得很差。
Spring等如何在API設(shè)計中使用設(shè)計模式
BeanFactory和ApplicationContext應(yīng)用了工廠模式
在Bean的創(chuàng)建中,Spring也為不同scope定義的對象,提供了單例和原型等模式實(shí)現(xiàn)。
AOP領(lǐng)域則是使用了代理模式、裝飾器模式、適配器模式等。
各種事件監(jiān)聽器,是觀察者模式的典型應(yīng)用。
類似JdbcTemplate等則是應(yīng)用了模板模式。
synchronized和ReentrantLock有什么區(qū)別?
synchronized是Java內(nèi)建的同步機(jī)制,它提供了互斥的語義和可見性,當(dāng)一個線程已經(jīng)獲取當(dāng)前鎖時,其他試圖獲取的線程只能等待或者阻塞在那里。
ReentrantLock,通常翻譯為再入鎖,是Java5提供的鎖實(shí)現(xiàn),它的語義和synchronized基本相同。再入鎖通過代碼直接調(diào)用lock()方法獲取,代碼書寫也更加靈活。與此同時,ReentrantLock提供了很多實(shí)用的方法,能夠?qū)崿F(xiàn)很多synchronized無法做到的細(xì)節(jié)控制,比如可以控制fairness,也就是公平性,或者利用定義條件等。但是,編碼中也需要注意,必須要明確調(diào)用unlock()方法釋放,不然就會一直持有該鎖。
synchronized底層如何實(shí)現(xiàn)?什么是鎖的升級、降級?
synchronized代碼塊是由一對兒monitorenter/monitorexit指令實(shí)現(xiàn)的,Monitor對象是同步的基本實(shí)現(xiàn)單元。在Java6之前,Monitor的實(shí)現(xiàn)完全是依靠操作系統(tǒng)內(nèi)部的互斥鎖,因?yàn)樾枰M(jìn)行用戶態(tài)到內(nèi)核態(tài)的切換,所以同步操作是一個無差別的重量級操作?,F(xiàn)代的(Oracle)JDK中,JVM對此進(jìn)行了大刀闊斧地改進(jìn),提供了三種不同的Monitor實(shí)現(xiàn),也就是常說的三種不同的鎖:偏向鎖(BiasedLocking)、輕量級鎖和重量級鎖,大大改進(jìn)了其性能。
所謂鎖的升級、降級,就是JVM優(yōu)化synchronized運(yùn)行的機(jī)制,當(dāng)JVM檢測到不同的競爭狀況時,會自動切換到適合的鎖實(shí)現(xiàn),這種切換就是鎖的升級、降級。
當(dāng)沒有競爭出現(xiàn)時,默認(rèn)會使用偏斜鎖。JVM會利用CAS操作,在對象頭上的MarkWord部分設(shè)置線程ID,以表示這個對象偏向于當(dāng)前線程,所以并不涉及真正的互斥鎖。這樣做的假設(shè)是基于在很多應(yīng)用場景中,大部分對象生命周期中最多會被一個線程鎖定,使用偏斜鎖可以降低無競爭開銷。
如果有另外的線程試圖鎖定某個已經(jīng)被偏斜過的對象,JVM就需要撤銷(revoke)偏斜鎖,并切換到輕量級鎖實(shí)現(xiàn)。輕量級鎖依賴CAS操作MarkWord來試圖獲取鎖,如果重試成功,就使用普通的輕量級鎖;否則,進(jìn)一步升級為重量級鎖。
你知道“自旋鎖”是做什么的嗎?它的使用場景是什么?
自旋鎖:競爭鎖的失敗的線程,并不會真實(shí)的在操作系統(tǒng)層面掛起等待,而是JVM會讓線程做幾個空循環(huán)(基于預(yù)測在不久的將來就能獲得),在經(jīng)過若干次循環(huán)后,如果可以獲得鎖,那么進(jìn)入臨界區(qū),如果還不能獲得鎖,才會真實(shí)的將線程在操作系統(tǒng)層面進(jìn)行掛起。
適用場景:自旋鎖可以減少線程的阻塞,這對于鎖競爭不激烈,且占用鎖時間非常短的代碼塊來說,有較大的性能提升,因?yàn)樽孕南臅∮诰€程阻塞掛起操作的消耗。如果鎖的競爭激烈,或者持有鎖的線程需要長時間占用鎖執(zhí)行同步塊,就不適合使用自旋鎖了,因?yàn)樽孕i在獲取鎖前一直都是占用cpu做無用功,線程自旋的消耗大于線程阻塞掛起操作的消耗,造成cpu的浪費(fèi)
在單核CPU上,自旋鎖是無用,因?yàn)楫?dāng)自旋鎖嘗試獲取鎖不成功會一直嘗試,這會一直占用CPU,其他線程不可能運(yùn)行,
同時由于其他線程無法運(yùn)行,所以當(dāng)前線程無法釋放鎖。
一個線程兩次調(diào)用start()方法會出現(xiàn)什么情況?談?wù)劸€程的生命周期和狀態(tài)轉(zhuǎn)移。
Java的線程是不允許啟動兩次的,第二次調(diào)用必然會拋出IllegalThreadStateException,這是一種運(yùn)行時異常,多次調(diào)用start被認(rèn)為是編程錯誤。
新建(NEW),表示線程被創(chuàng)建出來還沒真正啟動的狀態(tài),可以認(rèn)為它是個Java內(nèi)部狀態(tài)。
就緒(RUNNABLE),表示該線程已經(jīng)在JVM中執(zhí)行,當(dāng)然由于執(zhí)行需要計算資源,它可能是正在運(yùn)行,也可能還在等待系統(tǒng)分配給它CPU片段,在就緒隊(duì)列里面排隊(duì)。
阻塞(BLOCKED),這個狀態(tài)和我們前面兩講介紹的同步非常相關(guān),阻塞表示線程在等待Monitorlock。比如,線程試圖通過synchronized去獲取某個鎖,但是其他線程已經(jīng)獨(dú)占了,那么當(dāng)前線程就會處于阻塞狀態(tài)。
等待(WAITING),表示正在等待其他線程采取某些操作。一個常見的場景是類似生產(chǎn)者消費(fèi)者模式,發(fā)現(xiàn)任務(wù)條件尚未滿足,就讓當(dāng)前消費(fèi)者線程等待(wait),另外的生產(chǎn)者線程去準(zhǔn)備任務(wù)數(shù)據(jù),然后通過類似notify等動作,通知消費(fèi)線程可以繼續(xù)工作了。Thread.join()也會令線程進(jìn)入等待狀態(tài)。
計時等待(TIMED_WAIT),其進(jìn)入條件和等待狀態(tài)類似,但是調(diào)用的是存在超時條件的方法,比如wait或join等方法的指定超時版本
以上就是天津卓眾教育Java培訓(xùn)機(jī)構(gòu)小編介紹的“熟知的Java常見筆試題及答案”的內(nèi)容,希望對大家有幫助,如有疑問,請?jiān)诰€咨詢,有專業(yè)老師隨時為你服務(wù)。