今天要談的主題是關于求職,求職是在每個技術人員的生涯中都要經歷多次智末。對于我們大部分人而言枫笛,在進入自己心儀的公司之前少不了準備工作,有一份全面細致面試題將幫助我們減少許多麻煩沛厨。在跳槽季來臨之前,特地做這個系列的文章宙地,一方面幫助自己鞏固下基礎,另一方面也希望幫助想要換工作的朋友逆皮。
相關概念
面向對象的三個特征
封裝宅粥,繼承,多態(tài)电谣,這個應該是人人皆知秽梅,有時候也會加上抽象。
多態(tài)的好處
允許不同類對象對同一消息做出響應剿牺,即同一消息可以根據發(fā)送對象的不同而采用多種不同的行為方式(發(fā)送消息就是函數調用)企垦。主要有以下優(yōu)點:
可替換性:多態(tài)對已存在代碼具有可替換性
可擴充性:增加新的子類不影響已經存在的類結構
接口性:多態(tài)是超類通過方法簽名,向子類提供一個公共接口,由子類來完善或者重寫它來實現的。
靈活性
簡化性
代碼中如何實現多態(tài)
實現多態(tài)主要有以下三種方式:
1. 接口實現?
2. 繼承父類重寫方法?
3. 同一類中進行方法重載
虛擬機是如何實現多態(tài)的
動態(tài)綁定技術(dynamic binding)晒来,執(zhí)行期間判斷所引用對象的實際類型钞诡,根據實際類型調用對應的方法。
接口的意義
接口的意義用三個詞就可以概括:規(guī)范,擴展荧降,回調接箫。
抽象類的意義
抽象類的意義可以用三句話來概括:
為其他子類提供一個公共的類型
封裝子類中重復定義的內容
定義抽象方法,子類雖然有不同的實現,但是定義時一致的
接口和抽象類的區(qū)別
父類的靜態(tài)方法能否被子類重寫
不能朵诫。重寫只適用于實例方法,不能用于靜態(tài)方法辛友,而子類當中含有和父類相同簽名的靜態(tài)方法,我們一般稱之為隱藏剪返。
什么是不可變對象
不可變對象指對象一旦被創(chuàng)建废累,狀態(tài)就不能再改變。任何修改都會創(chuàng)建一個新的對象随夸,如 String九默、Integer及其它包裝類。
靜態(tài)變量和實例變量的區(qū)別?
靜態(tài)變量存儲在方法區(qū)宾毒,屬于類所有驼修。實例變量存儲在堆當中,其引用存在當前線程棧诈铛。
能否創(chuàng)建一個包含可變對象的不可變對象?
當然可以創(chuàng)建一個包含可變對象的不可變對象的乙各,你只需要謹慎一點纯续,不要共享可變對象的引用就可以了梆暖,如果需要變化時,就返回原對象的一個拷貝窜司。最常見的例子就是對象中包含一個日期對象的引用焕毫。
java 創(chuàng)建對象的幾種方式
采用new
通過反射
采用clone
通過序列化機制
前2者都需要顯式地調用構造方法蹲坷。造成耦合性最高的恰好是第一種,因此你發(fā)現無論什么框架邑飒,只要涉及到解耦必先減少new的使用循签。
switch中能否使用string做參數
在idk 1.7之前,switch只能支持byte, short, char, int或者其對應的封裝類以及Enum類型疙咸。從idk 1.7之后switch開始支持String县匠。
switch能否作用在byte, long上?
可以用在byte上,但是不能用在long上撒轮。
String s1=”ab”, String s2=”a”+”b”, String s3=”a”, String s4=”b”, s5=s3+s4請問s5==s2返回什么乞旦?
返回false。在編譯過程中题山,編譯器會將s2直接優(yōu)化為”ab”兰粉,會將其放置在常量池當中,s5則是被創(chuàng)建在堆區(qū)顶瞳,相當于s5=new String(“ab”);
你對String對象的intern()熟悉么?
intern()方法會首先從常量池中查找是否存在該常量值亲桦,如果常量池中不存在則現在常量池中創(chuàng)建崖蜜,如果已經存在則直接返回。
比如?
String s1=”aa”;?
String s2=s1.intern();?
System.out.print(s1==s2);//返回true
Object中有哪些公共方法?
equals()
clone()
getClass()
notify(),notifyAll(),wait()
toString
java當中的四種引用
強引用客峭,軟引用,弱引用抡柿,虛引用舔琅。不同的引用類型主要體現在GC上:
強引用:如果一個對象具有強引用,它就不會被垃圾回收器回收洲劣。即使當前內存空間不足备蚓,JVM也不會回收它,而是拋出 OutOfMemoryError 錯誤囱稽,使程序異常終止郊尝。如果想中斷強引用和某個對象之間的關聯,可以顯式地將引用賦值為null战惊,這樣一來的話流昏,JVM在合適的時間就會回收該對象。
軟引用:在使用軟引用時吞获,如果內存的空間足夠况凉,軟引用就能繼續(xù)被使用,而不會被垃圾回收器回收各拷,只有在內存不足時刁绒,軟引用才會被垃圾回收器回收。
弱引用:具有弱引用的對象擁有的生命周期更短暫烤黍。因為當 JVM 進行垃圾回收知市,一旦發(fā)現弱引用對象,無論當前內存空間是否充足速蕊,都會將弱引用回收嫂丙。不過由于垃圾回收器是一個優(yōu)先級較低的線程,所以并不一定能迅速發(fā)現弱引用對象互例。
虛引用:顧名思義奢入,就是形同虛設,如果一個對象僅持有虛引用媳叨,那么它相當于沒有引用腥光,在任何時候都可能被垃圾回收器回收。
WeakReference與SoftReference的區(qū)別?
這點在四種引用類型中已經做了解釋,這里簡單說明一下即可:?
雖然 WeakReference 與 SoftReference 都有利于提高 GC 和 內存的效率糊秆,但是 WeakReference 武福,一旦失去最后一個強引用,就會被 GC 回收痘番,而軟引用雖然不能阻止被回收捉片,但是可以延遲到 JVM 內存不足的時候平痰。
為什么要有不同的引用類型
不像C語言,我們可以控制內存的申請和釋放伍纫,在Java中有時候我們需要適當的控制對象被回收的時機宗雇,因此就誕生了不同的引用類型,可以說不同的引用類型實則是對GC回收時機不可控的妥協莹规。有以下幾個使用場景可以充分的說明:
利用軟引用和弱引用解決OOM問題:用一個HashMap來保存圖片的路徑和相應圖片對象關聯的軟引用之間的映射關系赔蒲,在內存不足時,JVM會自動回收這些緩存圖片對象所占用的空間良漱,從而有效地避免了OOM的問題.
通過軟引用實現Java對象的高速緩存:比如我們創(chuàng)建了一Person的類舞虱,如果每次需要查詢一個人的信息,哪怕是幾秒中之前剛剛查詢過的,都要重新構建一個實例母市,這將引起大量Person對象的消耗矾兜,并且由于這些對象的生命周期相對較短,會引起多次GC影響性能患久。此時椅寺,通過軟引用和 HashMap 的結合可以構建高速緩存,提供性能墙杯。
java中==和eqauls()的區(qū)別,equals()和`hashcode的區(qū)別
==是運算符配并,用于比較兩個變量是否相等,而equals是Object類的方法高镐,用于比較兩個對象是否相等溉旋。默認Object類的equals方法是比較兩個對象的地址,此時和==的結果一樣嫉髓。換句話說:基本類型比較用==观腊,比較的是他們的值。默認下算行,對象用==比較時梧油,比較的是內存地址,如果需要比較對象內容州邢,需要重寫equal方法儡陨。
equals()和hashcode()的聯系
hashCode()是Object類的一個方法,返回一個哈希值量淌。如果兩個對象根據equal()方法比較相等骗村,那么調用這兩個對象中任意一個對象的hashCode()方法必須產生相同的哈希值。
如果兩個對象根據eqaul()方法比較不相等呀枢,那么產生的哈希值不一定相等(碰撞的情況下還是會相等的胚股。)
a.hashCode()有什么用?與a.equals(b)有什么關系
hashCode() 方法是相應對象整型的 hash 值。它常用于基于 hash 的集合類裙秋,如 Hashtable琅拌、HashMap缨伊、LinkedHashMap等等。它與 equals() 方法關系特別緊密进宝。根據 Java 規(guī)范刻坊,使用 equal() 方法來判斷兩個相等的對象,必須具有相同的 hashcode党晋。
將對象放入到集合中時紧唱,首先判斷要放入對象的hashcode是否已經在集合中存在,不存在則直接放入集合隶校。如果hashcode相等,然后通過equal()方法判斷要放入對象與集合中的任意對象是否相等:如果equal()判斷不相等蛹锰,直接將該元素放入集合中深胳,否則不放入。
有沒有可能兩個不相等的對象有相同的hashcode
有可能铜犬,兩個不相等的對象可能會有相同的 hashcode 值舞终,這就是為什么在 hashmap 中會有沖突。如果兩個對象相等癣猾,必須有相同的hashcode 值敛劝,反之不成立。
可以在hashcode中使用隨機數字嗎?
不行纷宇,因為同一對象的 hashcode 值必須是相同的
a==b與a.equals(b)有什么區(qū)別
如果a 和b 都是對象夸盟,則 a==b 是比較兩個對象的引用,只有當 a 和 b 指向的是堆中的同一個對象才會返回 true像捶,而 a.equals(b) 是進行邏輯比較上陕,所以通常需要重寫該方法來提供邏輯一致性的比較。例如拓春,String 類重寫 equals() 方法释簿,所以可以用于兩個不同對象,但是包含的字母相同的比較硼莽。
3*0.1==0.3返回值是什么
false庶溶,因為有些浮點數不能完全精確的表示出來。
a=a+b與a+=b有什么區(qū)別嗎?
+=操作符會進行隱式自動類型轉換懂鸵,此處a+=b隱式的將加操作的結果類型強制轉換為持有結果的類型偏螺,而a=a+b則不會自動進行類型轉換。如:
byte a = 127;?
byte b = 127;?
b = a + b; // error : cannot convert from int to byte?
b += a; // ok?
(譯者注:這個地方應該表述的有誤矾瑰,其實無論 a+b 的值為多少砖茸,編譯器都會報錯,因為 a+b 操作會將 a殴穴、b 提升為 int 類型凉夯,所以將 int 類型賦值給 byte 就會編譯出錯)
short s1= 1; s1 = s1 + 1; 該段代碼是否有錯,有的話怎么改货葬?
有錯誤,short類型在進行運算時會自動提升為int類型劲够,也就是說s1+1的運算結果是int類型震桶。
short s1= 1; s1 += 1; 該段代碼是否有錯,有的話怎么改征绎?
+=操作符會自動對右邊的表達式結果強轉匹配左邊的數據類型蹲姐,所以沒錯。
& 和 &&的區(qū)別
首先記住&是位操作人柿,而&&是邏輯運算符柴墩。另外需要記住邏輯運算符具有短路特性,而&不具備短路特性凫岖。
以上代碼將會拋出空指針異常江咳。
一個java文件內部可以有類?(非內部類)
只能有一個public公共類哥放,但是可以有多個default修飾的類歼指。
如何正確的退出多層嵌套循環(huán)?
使用標號和break;
通過在外層循環(huán)中添加標識符
內部類的作用
內部類可以有多個實例甥雕,每個實例都有自己的狀態(tài)信息踩身,并且與其他外圍對象的信息相互獨立.在單個外圍類當中,可以讓多個內部類以不同的方式實現同一接口社露,或者繼承同一個類.創(chuàng)建內部類對象的時刻不依賴于外部類對象的創(chuàng)建挟阻。內部類并沒有令人疑惑的”is-a”管系,它就像是一個獨立的實體呵哨。
內部類提供了更好的封裝赁濒,除了該外圍類,其他類都不能訪問孟害。
final, finalize和finally的不同之處
final 是一個修飾符拒炎,可以修飾變量、方法和類挨务。如果 final 修飾變量击你,意味著該變量的值在初始化后不能被改變。finalize 方法是在對象被回收之前調用的方法谎柄,給對象自己最后一個復活的機會丁侄,但是什么時候調用 finalize 沒有保證。finally 是一個關鍵字朝巫,與 try 和 catch 一起用于異常的處理鸿摇。finally 塊一定會被執(zhí)行,無論在 try 塊中是否有發(fā)生異常劈猿。
clone()是哪個類的方法?
java.lang.Cloneable 是一個標示性接口拙吉,不包含任何方法潮孽,clone 方法在 object 類中定義。并且需要知道 clone() 方法是一個本地方法筷黔,這意味著它是由 c 或 c++ 或 其他本地語言實現的往史。
深拷貝和淺拷貝的區(qū)別是什么?
淺拷貝:被復制對象的所有變量都含有與原來的對象相同的值,而所有的對其他對象的引用仍然指向原來的對象佛舱。換言之椎例,淺拷貝僅僅復制所考慮的對象,而不復制它所引用的對象请祖。
深拷貝:被復制對象的所有變量都含有與原來的對象相同的值订歪,而那些引用其他對象的變量將指向被復制過的新對象,而不再是原有的那些被引用的對象肆捕。換言之陌粹,深拷貝把要復制的對象所引用的對象都復制了一遍。
static都有哪些用法?
幾乎所有的人都知道static關鍵字這兩個基本的用法:靜態(tài)變量和靜態(tài)方法福压。也就是被static所修飾的變量/方法都屬于類的靜態(tài)資源,類實例所共享或舞。
除了靜態(tài)變量和靜態(tài)方法之外荆姆,static也用于靜態(tài)塊,多用于初始化操作:
此外static也多用于修飾內部類映凳,此時稱之為靜態(tài)內部類胆筒。
最后一種用法就是靜態(tài)導包,即import static.import static是在JDK 1.5之后引入的新特性诈豌,可以用來指定導入某個類中的靜態(tài)資源仆救,并且不需要使用類名。資源名矫渔,可以直接使用資源名彤蔽,比如:
final有哪些用法
final也是很多面試喜歡問的地方,能回答下以下三點就不錯了:
1.被final修飾的類不可以被繼承?
2.被final修飾的方法不可以被重寫?
3.被final修飾的變量不可以被改變庙洼。如果修飾引用顿痪,那么表示引用不可變,引用指向的內容可變油够。
4.被final修飾的方法蚁袭,JVM會嘗試將其內聯,以提高運行效率?
5.被final修飾的常量石咬,在編譯階段會存入常量池中揩悄。
回答出編譯器對final域要遵守的兩個重排序規(guī)則更好:
1.在構造函數內對一個final域的寫入,與隨后把這個被構造對象的引用賦值給一個引用變量,這兩個操作之間不能重排序鬼悠。
2.初次讀一個包含final域的對象的引用删性,與隨后初次讀這個final域,這兩個操作之間不能重排序亏娜。
數據類型相關
java中int char,long各占多少字節(jié)?
64位的JVM當中,int的長度是多少?
Java 中,int 類型變量的長度是一個固定值镇匀,與平臺無關照藻,都是 32 位。意思就是說汗侵,在 32 位 和 64 位 的Java 虛擬機中幸缕,int 類型的長度是相同的。
int和Integer的區(qū)別
Integer是int的包裝類型晰韵,在拆箱和裝箱中发乔,二者自動轉換。int是基本類型雪猪,直接存數值栏尚,而integer是對象,用一個引用指向這個對象只恨。
int 和Integer誰占用的內存更多?
Integer 對象會占用更多的內存译仗。Integer是一個對象,需要存儲對象的元數據官觅。但是 int 是一個原始類型的數據纵菌,所以占用的空間更少。
String, StringBuffer和StringBuilder區(qū)別
String是字符串常量休涤,final修飾:StringBuffer字符串變量(線程安全)咱圆;
StringBuilder 字符串變量(線程不安全)。
String和StringBuffer
String和StringBuffer主要區(qū)別是性能:String是不可變對象功氨,每次對String類型進行操作都等同于產生了一個新的String對象序苏,然后指向新的String對象。所以盡量不在對String進行大量的拼接操作捷凄,否則會產生很多臨時對象忱详,導致GC開始工作,影響系統性能跺涤。
StringBuffer是對對象本身操作踱阿,而不是產生新的對象,因此在有大量拼接的情況下钦铁,我們建議使用StringBuffer软舌。
但是需要注意現在JVM會對String拼接做一定的優(yōu)化:
String s=“This is only ”+”simple”+”test”會被虛擬機直接優(yōu)化成String s=“This is only simple test”,此時就不存在拼接過程牛曹。
StringBuffer和StringBuilder
StringBuffer是線程安全的可變字符串佛点,其內部實現是可變數組。StringBuilder是jdk 1.5新增的,其功能和StringBuffer類似超营,但是非線程安全鸳玩。因此,在沒有多線程問題的前提下演闭,使用StringBuilder會取得更好的性能不跟。
什么是編譯器常量?使用它有什么風險米碰?
公共靜態(tài)不可變(public static final )變量也就是我們所說的編譯期常量窝革,這里的 public 可選的。實際上這些變量在編譯時會被替換掉吕座,因為編譯器知道這些變量的值虐译,并且知道這些變量在運行時不能改變。這種方式存在的一個問題是你使用了一個內部的或第三方庫中的公有編譯時常量吴趴,但是這個值后面被其他人改變了漆诽,但是你的客戶端仍然在使用老的值,甚至你已經部署了一個新的jar锣枝。為了避免這種情況厢拭,當你在更新依賴 JAR 文件時,確保重新編譯你的程序撇叁。
java當中使用什么類型表示價格比較好?
如果不是特別關心內存和性能的話蚪腐,使用BigDecimal,否則使用預定義精度的 double 類型税朴。
如何將byte轉為String
可以使用 String 接收 byte[] 參數的構造器來進行轉換,需要注意的點是要使用的正確的編碼家制,否則會使用平臺默認編碼正林,這個編碼可能跟原來的編碼相同,也可能不同颤殴。
可以將int強轉為byte類型么?會產生什么問題?
我們可以做強制轉換觅廓,但是Java中int是32位的而byte是8 位的,所以,如果強制轉化int類型的高24位將會被丟棄涵但,byte 類型的范圍是從-128到128
關于垃圾回收
你知道哪些垃圾回收算法?
垃圾回收從理論上非常容易理解,具體的方法有以下幾種:?
1. 標記-清除?
2. 標記-復制?
3. 標記-整理?
4. 分代回收
如何判斷一個對象是否應該被回收
這就是所謂的對象存活性判斷杈绸,常用的方法有兩種:1.引用計數法; 2.對象可達性分析。由于引用計數法存在互相引用導致無法進行GC的問題矮瘟,所以目前JVM虛擬機多使用對象可達性分析算法瞳脓。
簡單的解釋一下垃圾回收
Java 垃圾回收機制最基本的做法是分代回收。內存中的區(qū)域被劃分成不同的世代澈侠,對象根據其存活的時間被保存在對應世代的區(qū)域中劫侧。一般的實現是劃分成3個世代:年輕、年老和永久。內存的分配是發(fā)生在年輕世代中的烧栋。當一個對象存活時間足夠長的時候写妥,它就會被復制到年老世代中。對于不同的世代可以使用不同的垃圾回收算法审姓。進行世代劃分的出發(fā)點是對應用中對象存活時間進行研究之后得出的統計規(guī)律珍特。一般來說,一個應用中的大部分對象的存活時間都很短魔吐。比如局部變量的存活時間就只在方法的執(zhí)行過程中扎筒。基于這一點画畅,對于年輕世代的垃圾回收算法就可以很有針對性砸琅。
調用System.gc()會發(fā)生什么?
通知GC開始工作,但是GC真正開始的時間不確定轴踱。
進程,線程相關
說說進程症脂,線程,協程之間的區(qū)別
簡而言之淫僻,進程是程序運行和資源分配的基本單位诱篷,一個程序至少有一個進程,一個進程至少有一個線程雳灵。進程在執(zhí)行過程中擁有獨立的內存單元帕翻,而多個線程共享內存資源,減少切換次數哄陶,從而效率更高货裹。線程是進程的一個實體,是cpu調度和分派的基本單位躲撰,是比程序更小的能獨立運行的基本單位针贬。同一進程中的多個線程之間可以并發(fā)執(zhí)行。
你了解守護線程嗎拢蛋?它和非守護線程有什么區(qū)別
程序運行完畢桦他,jvm會等待非守護線程完成后關閉,但是jvm不會等待守護線程谆棱。守護線程最典型的例子就是GC線程快压。
什么是多線程上下文切換
多線程的上下文切換是指CPU控制權由一個已經正在運行的線程切換到另外一個就緒并等待獲取CPU執(zhí)行權的線程的過程。
創(chuàng)建兩種線程的方式?他們有什么區(qū)別?
通過實現java.lang.Runnable或者通過擴展java.lang.Thread類垃瞧。相比擴展Thread蔫劣,實現Runnable接口可能更優(yōu).原因有二:
Java不支持多繼承。因此擴展Thread類就代表這個子類不能擴展其他類个从。而實現Runnable接口的類還可能擴展另一個類拦宣。
類可能只要求可執(zhí)行即可,因此繼承整個Thread類的開銷過大。
Thread類中的start()和run()方法有什么區(qū)別?
start()方法被用來啟動新創(chuàng)建的線程鸵隧,而且start()內部調用了run()方法绸罗,這和直接調用run()方法的效果不一樣。當你調用run()方法的時候豆瘫,只會是在原來的線程中調用珊蟀,沒有新的線程啟動,start()方法才會啟動新線程外驱。
怎么檢測一個線程是否持有對象監(jiān)視器
Thread類提供了一個holdsLock(Object obj)方法育灸,當且僅當對象obj的監(jiān)視器被某條線程持有的時候才會返回true,注意這是一個static方法昵宇,這意味著”某條線程”指的是當前線程磅崭。
Runnable和Callable的區(qū)別
Runnable接口中的run()方法的返回值是void,它做的事情只是純粹地去執(zhí)行run()方法中的代碼而已瓦哎;Callable接口中的call()方法是有返回值的砸喻,是一個泛型,和Future蒋譬、FutureTask配合可以用來獲取異步執(zhí)行的結果割岛。?
這其實是很有用的一個特性,因為多線程相比單線程更難犯助、更復雜的一個重要原因就是因為多線程充滿著未知性癣漆,某條線程是否執(zhí)行了?某條線程執(zhí)行了多久剂买?某條線程執(zhí)行的時候我們期望的數據是否已經賦值完畢惠爽?無法得知,我們能做的只是等待這條多線程的任務執(zhí)行完畢而已瞬哼。而Callable+Future/FutureTask卻可以方便獲取多線程運行的結果婚肆,可以在等待時間太長沒獲取到需要的數據的情況下取消該線程的任務。
什么導致線程阻塞
阻塞指的是暫停一個線程的執(zhí)行以等待某個條件發(fā)生(如某資源就緒)倒槐,學過操作系統的同學對它一定已經很熟悉了。Java 提供了大量方法來支持阻塞附井,下面讓我們逐一分析讨越。
wait(),notify()和suspend(),resume()之間的區(qū)別
初看起來它們與 suspend() 和 resume() 方法對沒有什么分別,但是事實上它們是截然不同的永毅。區(qū)別的核心在于把跨,前面敘述的所有方法,阻塞時都不會釋放占用的鎖(如果占用了的話)沼死,而這一對方法則相反着逐。上述的核心區(qū)別導致了一系列的細節(jié)上的區(qū)別。
首先,前面敘述的所有方法都隸屬于 Thread 類耸别,但是這一對卻直接隸屬于 Object 類健芭,也就是說,所有對象都擁有這一對方法秀姐。初看起來這十分不可思議慈迈,但是實際上卻是很自然的,因為這一對方法阻塞時要釋放占用的鎖省有,而鎖是任何對象都具有的痒留,調用任意對象的 wait() 方法導致線程阻塞,并且該對象上的鎖被釋放蠢沿。而調用 任意對象的notify()方法則導致從調用該對象的 wait() 方法而阻塞的線程中隨機選擇的一個解除阻塞(但要等到獲得鎖后才真正可執(zhí)行)伸头。
其次,前面敘述的所有方法都可在任何位置調用舷蟀,但是這一對方法卻必須在 synchronized 方法或塊中調用恤磷,理由也很簡單,只有在synchronized 方法或塊中當前線程才占有鎖雪侥,才有鎖可以釋放碗殷。同樣的道理,調用這一對方法的對象上的鎖必須為當前線程所擁有速缨,這樣才有鎖可以釋放锌妻。因此,這一對方法調用必須放置在這樣的 synchronized 方法或塊中旬牲,該方法或塊的上鎖對象就是調用這一對方法的對象仿粹。若不滿足這一條件,則程序雖然仍能編譯原茅,但在運行時會出現IllegalMonitorStateException 異常吭历。
wait() 和 notify() 方法的上述特性決定了它們經常和synchronized關鍵字一起使用,將它們和操作系統進程間通信機制作一個比較就會發(fā)現它們的相似性:synchronized方法或塊提供了類似于操作系統原語的功能擂橘,它們的執(zhí)行不會受到多線程機制的干擾晌区,而這一對方法則相當于 block 和wakeup 原語(這一對方法均聲明為 synchronized)。它們的結合使得我們可以實現操作系統上一系列精妙的進程間通信的算法(如信號量算法)通贞,并用于解決各種復雜的線程間通信問題朗若。
關于 wait() 和 notify() 方法最后再說明兩點:?
第一:調用 notify() 方法導致解除阻塞的線程是從因調用該對象的 wait() 方法而阻塞的線程中隨機選取的,我們無法預料哪一個線程將會被選擇昌罩,所以編程時要特別小心哭懈,避免因這種不確定性而產生問題。
第二:除了 notify()茎用,還有一個方法 notifyAll() 也可起到類似作用遣总,唯一的區(qū)別在于睬罗,調用 notifyAll() 方法將把因調用該對象的 wait() 方法而阻塞的所有線程一次性全部解除阻塞。當然旭斥,只有獲得鎖的那一個線程才能進入可執(zhí)行狀態(tài)容达。
談到阻塞,就不能不談一談死鎖琉预,略一分析就能發(fā)現董饰,suspend() 方法和不指定超時期限的 wait() 方法的調用都可能產生死鎖。遺憾的是圆米,Java 并不在語言級別上支持死鎖的避免卒暂,我們在編程中必須小心地避免死鎖。
以上我們對 Java 中實現線程阻塞的各種方法作了一番分析娄帖,我們重點分析了 wait() 和 notify() 方法也祠,因為它們的功能最強大,使用也最靈活近速,但是這也導致了它們的效率較低诈嘿,較容易出錯。實際使用中我們應該靈活使用各種方法削葱,以便更好地達到我們的目的奖亚。
產生死鎖的條件
1.互斥條件:一個資源每次只能被一個進程使用。?
2.請求與保持條件:一個進程因請求資源而阻塞時析砸,對已獲得的資源保持不放昔字。?
3.不剝奪條件:進程已獲得的資源,在末使用完之前首繁,不能強行剝奪作郭。?
4.循環(huán)等待條件:若干進程之間形成一種頭尾相接的循環(huán)等待資源關系。
為什么wait()方法和notify()/notifyAll()方法要在同步塊中被調用
這是JDK強制的弦疮,wait()方法和notify()/notifyAll()方法在調用前都必須先獲得對象的鎖
wait()方法和notify()/notifyAll()方法在放棄對象監(jiān)視器時有什么區(qū)別
wait()方法和notify()/notifyAll()方法在放棄對象監(jiān)視器的時候的區(qū)別在于:wait()方法立即釋放對象監(jiān)視器夹攒,notify()/notifyAll()方法則會等待線程剩余代碼執(zhí)行完畢才會放棄對象監(jiān)視器。
wait()與sleep()的區(qū)別
關于這兩者已經在上面進行詳細的說明,這里就做個概括好了:
sleep()來自Thread類胁塞,和wait()來自Object類咏尝。調用sleep()方法的過程中,線程不會釋放對象鎖啸罢。而 調用 wait 方法線程會釋放對象鎖
sleep()睡眠后不出讓系統資源编检,wait讓其他線程可以占用CPU
sleep(milliseconds)需要指定一個睡眠時間,時間一到會自動喚醒.而wait()需要配合notify()或者notifyAll()使用
為什么wait, nofity和nofityAll這些方法不放在Thread類當中
一個很明顯的原因是JAVA提供的鎖是對象級的而不是線程級的伺糠,每個對象都有鎖蒙谓,通過線程獲得斥季。如果線程需要等待某些鎖那么調用對象中的wait()方法就有意義了训桶。如果wait()方法定義在Thread類中累驮,線程正在等待的是哪個鎖就不明顯了。簡單的說舵揭,由于wait谤专,notify和notifyAll都是鎖級別的操作,所以把他們定義在Object類中因為鎖屬于對象午绳。
怎么喚醒一個阻塞的線程
如果線程是因為調用了wait()置侍、sleep()或者join()方法而導致的阻塞,可以中斷線程拦焚,并且通過拋出InterruptedException來喚醒它蜡坊;如果線程遇到了IO阻塞,無能為力赎败,因為IO是操作系統實現的秕衙,Java代碼并沒有辦法直接接觸到操作系統。
什么是多線程的上下文切換
多線程的上下文切換是指CPU控制權由一個已經正在運行的線程切換到另外一個就緒并等待獲取CPU執(zhí)行權的線程的過程僵刮。
synchronized和ReentrantLock的區(qū)別
synchronized是和if据忘、else、for搞糕、while一樣的關鍵字勇吊,ReentrantLock是類,這是二者的本質區(qū)別窍仰。既然ReentrantLock是類汉规,那么它就提供了比synchronized更多更靈活的特性,可以被繼承辈赋、可以有方法鲫忍、可以有各種各樣的類變量,ReentrantLock比synchronized的擴展性體現在幾點上:?
(1)ReentrantLock可以對獲取鎖的等待時間進行設置钥屈,這樣就避免了死鎖?
(2)ReentrantLock可以獲取各種鎖的信息?
(3)ReentrantLock可以靈活地實現多路通知?
另外悟民,二者的鎖機制其實也是不一樣的:ReentrantLock底層調用的是Unsafe的park方法加鎖,synchronized操作的應該是對象頭中mark word篷就。
FutureTask是什么
這個其實前面有提到過射亏,FutureTask表示一個異步運算的任務。FutureTask里面可以傳入一個Callable的具體實現類竭业,可以對這個異步運算的任務的結果進行等待獲取智润、判斷是否已經完成、取消任務等操作未辆。當然窟绷,由于FutureTask也是Runnable接口的實現類,所以FutureTask也可以放入線程池中咐柜。
一個線程如果出現了運行時異常怎么辦?
如果這個異常沒有被捕獲的話兼蜈,這個線程就停止執(zhí)行了攘残。另外重要的一點是:如果這個線程持有某個某個對象的監(jiān)視器,那么這個對象監(jiān)視器會被立即釋放为狸。
Java當中有哪幾種鎖
自旋鎖: 自旋鎖在JDK1.6之后就默認開啟了歼郭。基于之前的觀察辐棒,共享數據的鎖定狀態(tài)只會持續(xù)很短的時間病曾,為了這一小段時間而去掛起和恢復線程有點浪費,所以這里就做了一個處理漾根,讓后面請求鎖的那個線程在稍等一會泰涂,但是不放棄處理器的執(zhí)行時間,看看持有鎖的線程能否快速釋放辐怕。為了讓線程等待负敏,所以需要讓線程執(zhí)行一個忙循環(huán)也就是自旋操作。在jdk6之后秘蛇,引入了自適應的自旋鎖其做,也就是等待的時間不再固定了,而是由上一次在同一個鎖上的自旋時間及鎖的擁有者狀態(tài)來決定赁还。
偏向鎖: 在JDK1.之后引入的一項鎖優(yōu)化妖泄,目的是消除數據在無競爭情況下的同步原語。進一步提升程序的運行性能艘策。 偏向鎖就是偏心的偏蹈胡,意思是這個鎖會偏向第一個獲得他的線程,如果接下來的執(zhí)行過程中朋蔫,改鎖沒有被其他線程獲取罚渐,則持有偏向鎖的線程將永遠不需要再進行同步。偏向鎖可以提高帶有同步但無競爭的程序性能驯妄,也就是說他并不一定總是對程序運行有利荷并,如果程序中大多數的鎖都是被多個不同的線程訪問,那偏向模式就是多余的青扔,在具體問題具體分析的前提下源织,可以考慮是否使用偏向鎖。
輕量級鎖: 為了減少獲得鎖和釋放鎖所帶來的性能消耗微猖,引入了“偏向鎖”和“輕量級鎖”谈息,所以在Java SE1.6里鎖一共有四種狀態(tài),無鎖狀態(tài)凛剥,偏向鎖狀態(tài)侠仇,輕量級鎖狀態(tài)和重量級鎖狀態(tài),它會隨著競爭情況逐漸升級犁珠。鎖可以升級但不能降級逻炊,意味著偏向鎖升級成輕量級鎖后不能降級成偏向鎖踢代。
如何在兩個線程間共享數據
通過在線程之間共享對象就可以了,然后通過wait/notify/notifyAll嗅骄、await/signal/signalAll進行喚起和等待,比方說阻塞隊列BlockingQueue就是為線程之間共享數據而設計的饼疙。
如何正確的使用wait()溺森?使用if還是while?
wait() 方法應該在循環(huán)調用窑眯,因為當線程獲取到 CPU 開始執(zhí)行的時候屏积,其他條件可能還沒有滿足,所以在處理前磅甩,循環(huán)檢測條件是否滿足會更好炊林。下面是一段標準的使用 wait 和 notify 方法的代碼:
什么是線程局部變量ThreadLocal
線程局部變量是局限于線程內部的變量,屬于線程自身所有卷要,不在多個線程間共享渣聚。Java提供ThreadLocal類來支持線程局部變量,是一種實現線程安全的方式僧叉。但是在管理環(huán)境下(如 web 服務器)使用線程局部變量的時候要特別小心奕枝,在這種情況下,工作線程的生命周期比任何應用變量的生命周期都要長瓶堕。任何線程局部變量一旦在工作完成后沒有釋放隘道,Java 應用就存在內存泄露的風險。
ThreadLoal的作用是什么?
簡單說ThreadLocal就是一種以空間換時間的做法在每個Thread里面維護了一個ThreadLocal.ThreadLocalMap把數據進行隔離郎笆,數據不共享谭梗,自然就沒有線程安全方面的問題了。
生產者消費者模型的作用是什么?
(1)通過平衡生產者的生產能力和消費者的消費能力來提升整個系統的運行效率宛蚓,這是生產者消費者模型最重要的作用激捏。
(2)解耦,這是生產者消費者模型附帶的作用凄吏,解耦意味著生產者和消費者之間的聯系少缩幸,聯系越少越可以獨自發(fā)展而不需要收到相互的制約。
寫一個生產者-消費者隊列
可以通過阻塞隊列實現竞思,也可以通過wait-notify來實現表谊。
使用阻塞隊列來實現
//消費者
public class Producer implements Runnable{
private final BlockingQueue queue;
public Producer(BlockingQueue q){
this.queue=q;
}
@Override
public void run() {
try {
while (true){
Thread.sleep(1000);//模擬耗時
queue.put(produce());
}
}catch (InterruptedException e){
}
}
private int produce() {
int n=new Random().nextInt(10000);
System.out.println("Thread:" + Thread.currentThread().getId() + " produce:" + n);
return n;
}
}
//消費者
public class Consumer implements Runnable {
private final BlockingQueue queue;
public Consumer(BlockingQueue q){
this.queue=q;
}
@Override
public void run() {
while (true){
try {
Thread.sleep(2000);//模擬耗時
consume(queue.take());
}catch (InterruptedException e){
}
}
}
private void consume(Integer n) {
System.out.println("Thread:" + Thread.currentThread().getId() + " consume:" + n);
}
}
//測試
public class Main {
public static void main(String[] args) {
BlockingQueue queue=new ArrayBlockingQueue(100);
Producer p=new Producer(queue);
Consumer c1=new Consumer(queue);
Consumer c2=new Consumer(queue);
new Thread(p).start();
new Thread(c1).start();
new Thread(c2).start();
}
}
使用wait-notify來實現
該種方式應該最經典,這里就不做說明了盖喷。
如果你提交任務時爆办,線程池隊列已滿,這時會發(fā)生什么
如果你使用的LinkedBlockingQueue课梳,也就是無界隊列的話距辆,沒關系余佃,繼續(xù)添加任務到阻塞隊列中等待執(zhí)行,因為LinkedBlockingQueue可以近乎認為是一個無窮大的隊列跨算,可以無限存放任務爆土;如果你使用的是有界隊列比方說ArrayBlockingQueue的話,任務首先會被添加到ArrayBlockingQueue中诸蚕,ArrayBlockingQueue滿了步势,則會使用拒絕策略RejectedExecutionHandler處理滿了的任務,默認是AbortPolicy背犯。
為什么要使用線程池
避免頻繁地創(chuàng)建和銷毀線程坏瘩,達到線程對象的重用。另外漠魏,使用線程池還可以根據項目靈活地控制并發(fā)的數目倔矾。
java中用到的線程調度算法是什么
搶占式。一個線程用完CPU之后柱锹,操作系統會根據線程優(yōu)先級哪自、線程饑餓情況等數據算出一個總的優(yōu)先級并分配下一個時間片給某個線程執(zhí)行。
Thread.sleep(0)的作用是什么
由于Java采用搶占式的線程調度算法禁熏,因此可能會出現某條線程常常獲取到CPU控制權的情況提陶,為了讓某些優(yōu)先級比較低的線程也能獲取到CPU控制權,可以使用Thread.sleep(0)手動觸發(fā)一次操作系統分配時間片的操作匹层,這也是平衡CPU控制權的一種操作隙笆。
什么是CAS
CAS,全稱為Compare and Swap升筏,即比較-替換撑柔。假設有三個操作數:內存值V、舊的預期值A您访、要修改的值B铅忿,當且僅當預期值A和內存值V相同時,才會將內存值修改為B并返回true灵汪,否則什么都不做并返回false檀训。當然CAS一定要volatile變量配合,這樣才能保證每次拿到的變量是主內存中最新的那個值享言,否則舊的預期值A對某條線程來說峻凫,永遠是一個不會變的值A,只要某次CAS操作失敗览露,永遠都不可能成功荧琼。
什么是樂觀鎖和悲觀鎖
樂觀鎖:樂觀鎖認為競爭不總是會發(fā)生,因此它不需要持有鎖,將比較-替換這兩個動作作為一個原子操作嘗試去修改內存中的變量命锄,如果失敗則表示發(fā)生沖突堰乔,那么就應該有相應的重試邏輯。
悲觀鎖:悲觀鎖認為競爭總是會發(fā)生脐恩,因此每次對某資源進行操作時镐侯,都會持有一個獨占的鎖,就像synchronized驶冒,不管三七二十一苟翻,直接上了鎖就操作資源了。
ConcurrentHashMap的并發(fā)度是什么?
ConcurrentHashMap的并發(fā)度就是segment的大小只怎,默認為16,這意味著最多同時可以有16條線程操作ConcurrentHashMap怜俐,這也是ConcurrentHashMap對Hashtable的最大優(yōu)勢身堡,任何情況下,Hashtable能同時有兩條線程獲取Hashtable中的數據嗎拍鲤?
ConcurrentHashMap的工作原理
ConcurrentHashMap在jdk 1.6和jdk 1.8實現原理是不同的贴谎。
jdk 1.6:
ConcurrentHashMap是線程安全的,但是與Hashtablea相比季稳,實現線程安全的方式不同擅这。Hashtable是通過對hash表結構進行鎖定,是阻塞式的景鼠,當一個線程占有這個鎖時仲翎,其他線程必須阻塞等待其釋放鎖。ConcurrentHashMap是采用分離鎖的方式铛漓,它并沒有對整個hash表進行鎖定溯香,而是局部鎖定,也就是說當一個線程占有這個局部鎖時浓恶,不影響其他線程對hash表其他地方的訪問玫坛。?
具體實現:ConcurrentHashMap內部有一個Segment.
jdk 1.8
在jdk 8中,ConcurrentHashMap不再使用Segment分離鎖包晰,而是采用一種樂觀鎖CAS算法來實現同步問題湿镀,但其底層還是“數組+鏈表->紅黑樹”的實現。
CyclicBarrier和CountDownLatch區(qū)別
這兩個類非常類似伐憾,都在java.util.concurrent下勉痴,都可以用來表示代碼運行到某個點上,二者的區(qū)別在于:
CyclicBarrier的某個線程運行到某個點上之后树肃,該線程即停止運行蚀腿,直到所有的線程都到達了這個點,所有線程才重新運行;CountDownLatch則不是莉钙,某線程運行到某個點上之后廓脆,只是給某個數值-1而已,該線程繼續(xù)運行磁玉。
CyclicBarrier只能喚起一個任務停忿,CountDownLatch可以喚起多個任務
CyclicBarrier可重用,CountDownLatch不可重用蚊伞,計數值為0該CountDownLatch就不可再用了席赂。
java中的++操作符線程安全么?
不是線程安全的操作。它涉及到多個指令时迫,如讀取變量值颅停,增加,然后存儲回內存掠拳,這個過程可能會出現多個線程交差癞揉。
你有哪些多線程開發(fā)良好的實踐?
給線程命名
最小化同步范圍
優(yōu)先使用volatile
盡可能使用更高層次的并發(fā)工具而非wait和notify()來實現線程通信,如BlockingQueue,Semeaphore
優(yōu)先使用并發(fā)容器而非同步容器.
考慮使用線程池
關于volatile關鍵字
可以創(chuàng)建Volatile數組嗎?
Java 中可以創(chuàng)建 volatile類型數組,不過只是一個指向數組的引用溺欧,而不是整個數組喊熟。如果改變引用指向的數組哀九,將會受到volatile 的保護罐脊,但是如果多個線程同時改變數組的元素嚣镜,volatile標示符就不能起到之前的保護作用了顾翼。
volatile能使得一個非原子操作變成原子操作嗎?
一個典型的例子是在類中有一個 long 類型的成員變量年扩。如果你知道該成員變量會被多個線程訪問盐欺,如計數器兵志、價格等汤功,你最好是將其設置為 volatile柏靶。為什么扇商?因為 Java 中讀取 long 類型變量不是原子的,需要分成兩步宿礁,如果一個線程正在修改該 long 變量的值案铺,另一個線程可能只能看到該值的一半(前 32 位)。但是對一個 volatile 型的 long 或 double 變量的讀寫是原子梆靖。
一種實踐是用 volatile 修飾 long 和 double 變量控汉,使其能按原子類型來讀寫。double 和 long 都是64位寬返吻,因此對這兩種類型的讀是分為兩部分的姑子,第一次讀取第一個 32 位,然后再讀剩下的 32 位测僵,這個過程不是原子的街佑,但 Java 中 volatile 型的 long 或 double 變量的讀寫是原子的谢翎。volatile 修復符的另一個作用是提供內存屏障(memory barrier),例如在分布式框架中的應用沐旨。簡單的說森逮,就是當你寫一個 volatile 變量之前,Java 內存模型會插入一個寫屏障(write barrier)磁携,讀一個 volatile 變量之前褒侧,會插入一個讀屏障(read barrier)。意思就是說谊迄,在你寫一個 volatile 域時闷供,能保證任何線程都能看到你寫的值,同時统诺,在寫之前歪脏,也能保證任何數值的更新對所有線程是可見的,因為內存屏障會將其他所有寫的值更新到緩存粮呢。
volatile類型變量提供什么保證?
volatile 主要有兩方面的作用:1.避免指令重排2.可見性保證.例如婿失,JVM 或者 JIT為了獲得更好的性能會對語句重排序,但是 volatile 類型變量即使在沒有同步塊的情況下賦值也不會與其他語句重排序鬼贱。 volatile 提供 happens-before 的保證移怯,確保一個線程的修改能對其他線程是可見的香璃。某些情況下这难,volatile 還能提供原子性,如讀 64 位數據類型葡秒,像 long 和 double 都不是原子的(低32位和高32位)姻乓,但 volatile 類型的 double 和 long 就是原子的。
關于集合
Java中的集合及其繼承關系
關于集合的體系是每個人都應該爛熟于心的,尤其是對我們經常使用的List,Map的原理更該如此.這里我們看這張圖即可:
poll()方法和remove()方法區(qū)別眯牧?
poll() 和 remove() 都是從隊列中取出一個元素蹋岩,但是 poll() 在獲取元素失敗的時候會返回空,但是 remove() 失敗的時候會拋出異常学少。
LinkedHashMap和PriorityQueue的區(qū)別
PriorityQueue 是一個優(yōu)先級隊列,保證最高或者最低優(yōu)先級的的元素總是在隊列頭部剪个,但是 LinkedHashMap 維持的順序是元素插入的順序。當遍歷一個 PriorityQueue 時版确,沒有任何順序保證扣囊,但是 LinkedHashMap 課保證遍歷順序是元素插入的順序。
WeakHashMap與HashMap的區(qū)別是什么?
WeakHashMap 的工作與正常的 HashMap 類似绒疗,但是使用弱引用作為 key侵歇,意思就是當 key 對象沒有任何引用時,key/value 將會被回收吓蘑。
ArrayList和LinkedList的區(qū)別?
最明顯的區(qū)別是 ArrrayList底層的數據結構是數組惕虑,支持隨機訪問,而 LinkedList 的底層數據結構是雙向循環(huán)鏈表,不支持隨機訪問溃蔫。使用下標訪問一個元素健提,ArrayList 的時間復雜度是 O(1),而 LinkedList 是 O(n)酒唉。
ArrayList和Array有什么區(qū)別?
Array可以容納基本類型和對象矩桂,而ArrayList只能容納對象。
Array是指定大小的痪伦,而ArrayList大小是固定的
ArrayList和HashMap默認大小?
在 Java 7 中侄榴,ArrayList 的默認大小是 10 個元素,HashMap 的默認大小是16個元素(必須是2的冪)网沾。這就是 Java 7 中 ArrayList 和 HashMap 類的代碼片段癞蚕。
Comparator和Comparable的區(qū)別?
Comparable 接口用于定義對象的自然順序,而 comparator 通常用于定義用戶定制的順序辉哥。Comparable 總是只有一個桦山,但是可以有多個 comparator 來定義對象的順序。
如何實現集合排序?
你可以使用有序集合醋旦,如 TreeSet 或 TreeMap恒水,你也可以使用有順序的的集合,如 list饲齐,然后通過 Collections.sort() 來排序钉凌。
如何打印數組內容
你可以使用 Arrays.toString() 和 Arrays.deepToString() 方法來打印數組。由于數組沒有實現 toString() 方法捂人,所以如果將數組傳遞給 System.out.println() 方法御雕,將無法打印出數組的內容,但是 Arrays.toString() 可以打印每個元素滥搭。
LinkedList的是單向鏈表還是雙向?
雙向循環(huán)列表酸纲,具體實現自行查閱源碼。
TreeMap是實現原理
采用紅黑樹實現瑟匆,具體實現自行查閱源碼闽坡。
遍歷ArrayList時如何正確移除一個元素
該問題的關鍵在于面試者使用的是 ArrayList 的 remove() 還是 Iterator 的 remove()方法。這有一段示例代碼愁溜,是使用正確的方式來實現在遍歷的過程中移除元素疾嗅,而不會出現 ConcurrentModificationException 異常的示例代碼。
什么是ArrayMap?它和HashMap有什么區(qū)別?
ArrayMap是Android SDK中提供的祝谚,非Android開發(fā)者可以略過宪迟。
ArrayMap是用兩個數組來模擬map,更少的內存占用空間,更高的效率交惯。
HashMap的實現原理
1. HashMap概述: HashMap是基于哈希表的Map接口的非同步實現次泽。此實現提供所有可選的映射操作穿仪,并允許使用null值和null鍵。此類不保證映射的順序意荤,特別是它不保證該順序恒久不變啊片。?
2. HashMap的數據結構: 在java編程語言中,最基本的結構就是兩種玖像,一個是數組紫谷,另外一個是模擬指針(引用),所有的數據結構都可以用這兩個基本結構來構造的捐寥,HashMap也不例外笤昨。HashMap實際上是一個“鏈表散列”的數據結構,即數組和鏈表的結合體握恳。
當我們往Hashmap中put元素時,首先根據key的hashcode重新計算hash值,根絕hash值得到這個元素在數組中的位置(下標),如果該數組在該位置上已經存放了其他元素,那么在這個位置上的元素將以鏈表的形式存放,新加入的放在鏈頭,最先加入的放入鏈尾.如果數組中該位置沒有元素,就直接將該元素放到數組的該位置上.
需要注意Jdk 1.8中對HashMap的實現做了優(yōu)化,當鏈表中的節(jié)點數據超過八個之后,該鏈表會轉為紅黑樹來提高查詢效率,從原來的O(n)到O(logn)
你了解Fail-Fast機制嗎瞒窒?
Fail-Fast即我們常說的快速失敗,
Fail-fast和Fail-safe有什么區(qū)別
Iterator的fail-fast屬性與當前的集合共同起作用乡洼,因此它不會受到集合中任何改動的影響崇裁。Java.util包中的所有集合類都被設計為fail->fast的,而java.util.concurrent中的集合類都為fail-safe的束昵。當檢測到正在遍歷的集合的結構被改變時拔稳,Fail-fast迭代器拋出ConcurrentModificationException,而fail-safe迭代器從不拋出ConcurrentModificationException锹雏。
關于日期
SimpleDateFormat是線程安全的嗎?
非常不幸巴比,DateFormat 的所有實現,包括 SimpleDateFormat 都不是線程安全的逼侦,因此你不應該在多線程序中使用匿辩,除非是在對外線程安全的環(huán)境中使用腰耙,如 將 SimpleDateFormat 限制在 ThreadLocal 中榛丢。如果你不這么做,在解析或者格式化日期的時候挺庞,可能會獲取到一個不正確的結果晰赞。因此,從日期选侨、時間處理的所有實踐來說掖鱼,我強力推薦 joda-time 庫。
如何格式化日期?
Java 中援制,可以使用 SimpleDateFormat 類或者 joda-time 庫來格式日期戏挡。DateFormat 類允許你使用多種流行的格式來格式化日期。參見答案中的示例代碼晨仑,代碼中演示了將日期格式化成不同的格式褐墅,如 dd-MM-yyyy 或 ddMMyyyy拆檬。
關于異常
簡單描述java異常體系
相比沒有人不了解異常體系,關于異常體系的更多信息可以見
什么是異常鏈
詳情直接參見上面的白話異常機制,不做解釋了妥凳。
throw和throws的區(qū)別
throw用于主動拋出java.lang.Throwable 類的一個實例化對象竟贯,意思是說你可以通過關鍵字 throw 拋出一個 Error 或者 一個Exception,如:throw new IllegalArgumentException(“size must be multiple of 2″),?
而throws 的作用是作為方法聲明和簽名的一部分逝钥,方法被拋出相應的異常以便調用者能處理屑那。Java 中,任何未處理的受檢查異常強制在 throws 子句中聲明艘款。
關于序列化
Java 中持际,Serializable 與 Externalizable 的區(qū)別
Serializable 接口是一個序列化 Java 類的接口,以便于它們可以在網絡上傳輸或者可以將它們的狀態(tài)保存在磁盤上哗咆,是 JVM 內嵌的默認序列化方式选酗,成本高、脆弱而且不安全岳枷。Externalizable 允許你控制整個序列化過程芒填,指定特定的二進制格式,增加安全機制空繁。
關于JVM
JVM特性
平臺無關性.?
Java語言的一個非常重要的特點就是與平臺的無關性殿衰。而使用Java虛擬機是實現這一特點的關鍵。一般的高級語言如果要在不同的平臺上運行盛泡,至少需要編譯成不同的目標代碼闷祥。而引入Java語言虛擬機后,Java語言在不同平臺上運行時不需要重新編譯傲诵。Java語言使用模式Java虛擬機屏蔽了與具體平臺相關的信息凯砍,使得Java語言編譯程序只需生成在Java虛擬機上運行的目標代碼(字節(jié)碼),就可以在多種平臺上不加修改地運行拴竹。Java虛擬機在執(zhí)行字節(jié)碼時悟衩,把字節(jié)碼解釋成具體平臺上的機器指令執(zhí)行。
簡單解釋一下類加載器
有關類加載器一般會問你四種類加載器的應用場景以及雙親委派模型,
簡述堆和棧的區(qū)別
VM 中堆和棧屬于不同的內存區(qū)域栓拜,使用目的也不同座泳。棧常用于保存方法幀和局部變量,而對象總是在堆上分配幕与。棧通常都比堆小挑势,也不會在多個線程之間共享,而堆被整個 JVM 的所有線程共享啦鸣。
簡述JVM內存分配
基本數據類型比變量和對象的引用都是在棧分配的潮饱。
堆內存用來存放由new創(chuàng)建的對象和數組。
類變量(static修飾的變量)诫给,程序在一加載的時候就在堆中為類變量分配內存香拉,堆中的內存地址存放在棧中饲漾。
實例變量:當你使用java關鍵字new的時候,系統在堆中開辟并不一定是連續(xù)的空間分配給變量缕溉,是根據零散的堆內存地址考传,通過哈希算法換算為一長串數字以表征這個變量在堆中的”物理位置”,實例變量的生命周期–當實例變量的引用丟失后,將被GC(垃圾回收器)列入可回收“名單”中证鸥,但并不是馬上就釋放堆中內存僚楞。
局部變量: 由聲明在某方法,或某代碼段里(比如for循環(huán))枉层,執(zhí)行到它的時候在棧中開辟內存泉褐,當局部變量一但脫離作用域,內存立即釋放鸟蜡。
其他
java當中采用的是大端還是小端?
XML解析的幾種方式和特點
DOM, SAX, PULL三種解析方式:
DOM:消耗內存:先把xml文檔都讀到內存中膜赃,然后再用DOM API來訪問樹形結構,并獲取數據揉忘。這個寫起來很簡單跳座,但是很消耗內存。要是數據過大泣矛,手機不夠牛逼疲眷,可能手機直接死機
SAX:解析效率高,占用內存少您朽,基于事件驅動的:更加簡單地說就是對文檔進行順序掃描狂丝,當掃描到文檔(document)開始與結束、元素(element)開始與結束哗总、文檔(document)結束等地方時通知事件處理函數几颜,由事件處理函數做相應動作,然后繼續(xù)同樣的掃描讯屈,直至文檔結束蛋哭。
PULL:與 SAX 類似,也是基于事件驅動耻煤,我們可以調用它的next()方法具壮,來獲取下一個解析事件(就是開始文檔准颓,結束文檔哈蝇,開始標簽,結束標簽)攘已,當處于某個元素時可以調用XmlPullParser的getAttributte()方法來獲取屬性的值炮赦,也可調用它的nextText()獲取本節(jié)點的值。
JDK 1.7特性
然 JDK 1.7 不像 JDK 5 和 8 一樣的大版本样勃,但是吠勘,還是有很多新的特性性芬,如 try-with-resource 語句,這樣你在使用流或者資源的時候剧防,就不需要手動關閉植锉,Java 會自動關閉。Fork-Join 池某種程度上實現 Java 版的 Map-reduce峭拘。允許 Switch 中有 String 變量和文本俊庇。菱形操作符(<>)用于類型推斷,不再需要在變量聲明的右邊申明泛型鸡挠,因此可以寫出可讀寫更強辉饱、更簡潔的代碼。
JDK 1.8特性
java 8 在 Java 歷史上是一個開創(chuàng)新的版本拣展,下面 JDK 8 中 5 個主要的特性:?
Lambda 表達式彭沼,允許像對象一樣傳遞匿名函數?
Stream API,充分利用現代多核 CPU备埃,可以寫出很簡潔的代碼?
Date 與 Time API姓惑,最終,有一個穩(wěn)定按脚、簡單的日期和時間庫可供你使用?
擴展方法挺益,現在,接口中可以有靜態(tài)乘寒、默認方法望众。?
重復注解,現在你可以將相同的注解在同一類型上使用多次伞辛。
Maven和ANT有什么區(qū)別?
雖然兩者都是構建工具烂翰,都用于創(chuàng)建 Java 應用,但是 Maven 做的事情更多蚤氏,在基于“約定優(yōu)于配置”的概念下甘耿,提供標準的Java 項目結構,同時能為應用自動管理依賴(應用中所依賴的 JAR 文件竿滨。
JDBC最佳實踐
優(yōu)先使用批量操作來插入和更新數據
使用PreparedStatement來避免SQL漏洞
使用數據連接池
通過列名來獲取結果集
IO操作最佳實踐
使用有緩沖的IO類,不要單獨讀取字節(jié)或字符
使用NIO和NIO 2或者AIO,而非BIO
在finally中關閉流
使用內存映射文件獲取更快的IO
有需要面試資料以及架構進階資料的朋友們可以關注小編佳恬,私信“資料”獲取。