Java面試題(2)

Java面試題(1)見:https://blog.csdn.net/Number_oneEngineer/article/details/83412521

相關概念

面向對象的三個特徵

封裝,繼承,多態,這個應該是人人皆知,有時候也會加上抽象。

多態的好處

允許不同類對象對同一消息做出響應,即同一消息可以根據發送對象的不同而採用多種不同的行爲方式(發送消息就是函數調用)。主要有以下優點:

  1. 可替換性:多態對已存在代碼具有可替換性
  2. 可擴充性:增加新的子類不影響已經存在的類結構
  3. 接口性:多態是超類通過方法簽名,向子類提供一個公共接口,由子類來完善或者重寫它來實現的。
  4. 靈活性
  5. 簡化性

代碼中如何實現多態

實現多態主要有以下三種方式:

  1. 接口實現
  2. 繼承父類重寫方法
  3. 同一類中進行方法重載

虛擬機是如何實現多態的

動態綁定技術(dynamic binding),執行期間判斷所引用對象的實際類型,根據實際類型調用對應的方法。

接口的意義

接口的意義用三個詞就可以概括:規範,擴展,回調。

抽象類的意義

抽象類的意義可以用三句話來概括:

  1. 爲其他子類提供一個公共的類型
  2. 封裝子類中重複定義的內容
  3. 定義抽象方法,子類雖然有不同的實現,但是定義時一致的

接口和抽象類的區別

在這裏插入圖片描述

父類的靜態方法能否被子類重寫

不能。重寫只適用於實例方法,不能用於靜態方法,而子類當中含有和父類相同簽名的靜態方法,我們一般稱之爲隱藏。

什麼是不可變對象?

不可變對象指對象一旦被創建,狀態就不能再改變。任何修改都會創建一個新的對象,如 String、Integer及其它包裝類。

靜態變量和實例變量的區別?

靜態變量存儲在方法區,屬於類所有。實例變量存儲在堆當中,其引用存在當前線程棧。

能否創建一個包含可變對象的不可變對象?

當然可以創建一個包含可變對象的不可變對象的,你只需要謹慎一點,不要共享可變對象的引用就可以了,如果需要變化時,就返回原對象的一個拷貝。最常見的例子就是對象中包含一個日期對象的引用。

java 創建對象的幾種方式

  1. 採用new
  2. 通過反射
  3. 採用clone
  4. 通過序列化機制

前2者都需要顯式地調用構造方法。造成耦合性最高的恰好是第一種,因此你發現無論什麼框架,只要涉及到解耦必先減少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直接優化爲”ab”,會將其放置在常量池當中,s5則是被創建在堆區,相當於s5=new String(“ab”);

你對String對象的intern()熟悉麼?

intern()方法會首先從常量池中查找是否存在該常量值,如果常量池中不存在則現在常量池中創建,如果已經存在則直接返回。
比如

String s1=”aa”; 
String s2=s1.intern(); 
System.out.print(s1==s2);//返回true

Object中有哪些公共方法?

  1. equals()
  2. clone()
  3. getClass()
  4. notify(),notifyAll(),wait()
  5. toString

java當中的四種引用

  • 強引用,軟引用,弱引用,虛引用。不同的引用類型主要體現在GC上:
    強引用:如果一個對象具有強引用,它就不會被垃圾回收器回收。即使當前內存空間不足,JVM也不會回收它,而是拋出 OutOfMemoryError 錯誤,使程序異常終止。如果想中斷強引用和某個對象之間的關聯,可以顯式地將引用賦值爲null,這樣一來的話,JVM在合適的時間就會回收該對象。
  • 軟引用:在使用軟引用時,如果內存的空間足夠,軟引用就能繼續被使用,而不會被垃圾回收器回收,只有在內存不足時,軟引用纔會被垃圾回收器回收。
  • 弱引用:具有弱引用的對象擁有的生命週期更短暫。因爲當 JVM 進行垃圾回收,一旦發現弱引用對象,無論當前內存空間是否充足,都會將弱引用回收。不過由於垃圾回收器是一個優先級較低的線程,所以並不一定能迅速發現弱引用對象。
  • 虛引用:顧名思義,就是形同虛設,如果一個對象僅持有虛引用,那麼它相當於沒有引用,在任何時候都可能被垃圾回收器回收。

更多瞭解參見深入對象引用:
http://blog.csdn.net/dd864140130/article/details/49885811

WeakReference與SoftReference的區別?

這點在四種引用類型中已經做了解釋,這裏簡單說明一下即可:
雖然 WeakReference 與 SoftReference 都有利於提高 GC 和 內存的效率,但是 WeakReference ,一旦失去最後一個強引用,就會被 GC 回收,而軟引用雖然不能阻止被回收,但是可以延遲到 JVM 內存不足的時候。

爲什麼要有不同的引用類型

不像C語言,我們可以控制內存的申請和釋放,在Java中有時候我們需要適當的控制對象被回收的時機,因此就誕生了不同的引用類型,可以說不同的引用類型實則是對GC回收時機不可控的妥協。有以下幾個使用場景可以充分的說明:

  • 1.利用軟引用和弱引用解決OOM問題:用一個HashMap來保存圖片的路徑和相應圖片對象關聯的軟引用之間的映射關係,在內存不足時,JVM會自動回收這些緩存圖片對象所佔用的空間,從而有效地避免了OOM的問題.
  • 2.通過軟引用實現Java對象的高速緩存:比如我們創建了一Person的類,如果每次需要查詢一個人的信息,哪怕是幾秒中之前剛剛查詢過的,都要重新構建一個實例,這將引起大量Person對象的消耗,並且由於這些對象的生命週期相對較短,會引起多次GC影響性能。此時,通過軟引用和 HashMap 的結合可以構建高速緩存,提供性能。

java中==和eqauls()的區別,equals()和`hashcode的區別

= =是運算符,用於比較兩個變量是否相等,而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 規範,使用 equal() 方法來判斷兩個相等的對象,必須具有相同的 hashcode。
將對象放入到集合中時,首先判斷要放入對象的hashcode是否已經在集合中存在,不存在則直接放入集合。如果hashcode相等,然後通過equal()方法判斷要放入對象與集合中的任意對象是否相等:如果equal()判斷不相等,直接將該元素放入集合中,否則不放入。

有沒有可能兩個不相等的對象有相同的hashcode

有可能,兩個不相等的對象可能會有相同的 hashcode 值,這就是爲什麼在 hashmap 中會有衝突。如果兩個對象相等,必須有相同的hashcode 值,反之不成立。

可以在hashcode中使用隨機數字嗎?

不行,因爲同一對象的 hashcode 值必須是相同的

a= =b與a.equals(b)有什麼區別

如果a 和b 都是對象,則 a= =b 是比較兩個對象的引用,只有當 a 和 b 指向的是堆中的同一個對象纔會返回 true,而 a.equals(b) 是進行邏輯比較,所以通常需要重寫該方法來提供邏輯一致性的比較。例如,String 類重寫 equals() 方法,所以可以用於兩個不同對象,但是包含的字母相同的比較。

3*0.1==0.3返回值是什麼

false,因爲有些浮點數不能完全精確的表示出來。

a=a+b與a+=b有什麼區別嗎?

+=操作符會進行隱式自動類型轉換,此處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類型。
#3 short s1= 1; s1 += 1; 該段代碼是否有錯,有的話怎麼改?
+=操作符會自動對右邊的表達式結果強轉匹配左邊的數據類型,所以沒錯。

& 和 &&的區別

首先記住&是位操作,而&&是邏輯運算符。另外需要記住邏輯運算符具有短路特性,而&不具備短路特性。

public class Test{
   static String name;
 
   public static void main(String[] args){
       if(name!=null&userName.equals("")){
           System.out.println("ok");
       }else{
           System.out.println("erro");
       }
   }
}

以上代碼將會拋出空指針異常。

一個java文件內部可以有類?(非內部類)

只能有一個public公共類,但是可以有多個default修飾的類。

如何正確的退出多層嵌套循環?

使用標號和break;
通過在外層循環中添加標識符

內部類的作用

內部類可以有多個實例,每個實例都有自己的狀態信息,並且與其他外圍對象的信息相互獨立.在單個外圍類當中,可以讓多個內部類以不同的方式實現同一接口,或者繼承同一個類.創建內部類對象的時刻不依賴於外部類對象的創建。內部類並沒有令人疑惑的”is-a”管系,它就像是一個獨立的實體。
內部類提供了更好的封裝,除了該外圍類,其他類都不能訪問。

final, finalize和finally的不同之處

final 是一個修飾符,可以修飾變量、方法和類。如果 final 修飾變量,意味着該變量的值在初始化後不能被改變。finalize 方法是在對象被回收之前調用的方法,給對象自己最後一個復活的機會,但是什麼時候調用 finalize 沒有保證。finally 是一個關鍵字,與 try 和 catch 一起用於異常的處理。finally 塊一定會被執行,無論在 try 塊中是否有發生異常。

clone()是哪個類的方法?

java.lang.Cloneable 是一個標示性接口,不包含任何方法,clone 方法在 object 類中定義。並且需要知道 clone() 方法是一個本地方法,這意味着它是由 c 或 c++ 或 其他本地語言實現的。

深拷貝和淺拷貝的區別是什麼?

  • 淺拷貝:被複制對象的所有變量都含有與原來的對象相同的值,而所有的對其他對象的引用仍然指向原來的對象。換言之,淺拷貝僅僅複製所考慮的對象,而不復制它所引用的對象。
  • 深拷貝:被複制對象的所有變量都含有與原來的對象相同的值,而那些引用其他對象的變量將指向被複制過的新對象,而不再是原有的那些被引用的對象。換言之,深拷貝把要複製的對象所引用的對象都複製了一遍。

static都有哪些用法?

幾乎所有的人都知道static關鍵字這兩個基本的用法:靜態變量和靜態方法。也就是被static所修飾的變量/方法都屬於類的靜態資源,類實例所共享。
除了靜態變量和靜態方法之外,static也用於靜態塊,多用於初始化操作:

public calss PreCache{
   static{
       //執行相關操作
   }
}

此外static也多用於修飾內部類,此時稱之爲靜態內部類。
最後一種用法就是靜態導包,即import static.import static是在JDK 1.5之後引入的新特性,可以用來指定導入某個類中的靜態資源,並且不需要使用類名。資源名,可以直接使用資源名,比如:

import static java.lang.Math.*;
 
public class Test{
 
   public static void main(String[] args){
       //System.out.println(Math.sin(20));傳統做法
       System.out.println(sin(20));
   }
}

final有哪些用法

final也是很多面試喜歡問的地方,能回答下以下三點就不錯了:
1.被final修飾的類不可以被繼承
2.被final修飾的方法不可以被重寫
3.被final修飾的變量不可以被改變。如果修飾引用,那麼表示引用不可變,引用指向的內容可變。
4.被final修飾的方法,JVM會嘗試將其內聯,以提高運行效率
5.被final修飾的常量,在編譯階段會存入常量池中。

回答出編譯器對final域要遵守的兩個重排序規則更好:

1.在構造函數內對一個final域的寫入,與隨後把這個被構造對象的引用賦值給一個引用變量,這兩個操作之間不能重排序。
2.初次讀一個包含final域的對象的引用,與隨後初次讀這個final域,這兩個操作之間不能重排序。

數據類型相關

在這裏插入圖片描述

java中int char,long各佔多少字節?

在這裏插入圖片描述

64位的JVM當中,int的長度是多少?

Java 中,int 類型變量的長度是一個固定值,與平臺無關,都是 32 位。意思就是說,在 32 位 和 64 位 的Java 虛擬機中,int 類型的長度是相同的。

int和Integer的區別

Integer是int的包裝類型,在拆箱和裝箱中,二者自動轉換。int是基本類型,直接存數值,而integer是對象,用一個引用指向這個對象。

int 和Integer誰佔用的內存更多?

Integer 對象會佔用更多的內存。Integer是一個對象,需要存儲對象的元數據。但是 int 是一個原始類型的數據,所以佔用的空間更少。

String, StringBuffer和StringBuilder區別

String是字符串常量,final修飾:StringBuffer字符串變量(線程安全);
StringBuilder 字符串變量(線程不安全)。

String和StringBuffer

String和StringBuffer主要區別是性能:String是不可變對象,每次對String類型進行操作都等同於產生了一個新的String對象,然後指向新的String對象。所以儘量不在對String進行大量的拼接操作,否則會產生很多臨時對象,導致GC開始工作,影響系統性能。
StringBuffer是對對象本身操作,而不是產生新的對象,因此在有大量拼接的情況下,我們建議使用StringBuffer。
但是需要注意現在JVM會對String拼接做一定的優化:
String s=“This is only ”+”simple”+”test”會被虛擬機直接優化成String s=“This is only simple test”,此時就不存在拼接過程。

StringBuffer和StringBuilder

StringBuffer是線程安全的可變字符串,其內部實現是可變數組。StringBuilder是jdk 1.5新增的,其功能和StringBuffer類似,但是非線程安全。因此,在沒有多線程問題的前提下,使用StringBuilder會取得更好的性能。

什麼是編譯器常量?使用它有什麼風險?

公共靜態不可變(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. 分代回收
    更詳細的內容參見深入理解垃圾回收算法:
    http://blog.csdn.net/dd864140130/article/details/50084471

1.標記——清除算法(Mark-Sweep)

標記——清除算法是第一種使用和比較完善的垃圾回收算法,算法分爲兩個過程:1、標記正所有需要回收的對象2、標記完成後清除被標記的對象。其標記的過程就是判斷對象有效性,執行可達性分析的過程。其執行流程圖大體如何下所示:

同樣,我們也藉助現實的場景進行描述。圖書管裏有好多人在看書,圖書管理員想要收集起沒有被看的書的時候,他決定讓所有正在看書的人站起來,然後詢問每個人:那一本書是不看的。詢問完所有的人之後,同學們做下繼續看書。這時候,圖書管理員開始尋找所有做過標記的書,把它們收集起來。

缺點:

1、每次進行垃圾回收時,會暫停當前用戶程序的運行(類似讓所有的同學站起來)
2、垃圾回收器需要間隔性的檢查,並且標記和清除的過程相對較慢。
3、在標記清除之後可能會產生大量內存碎片,導致一旦需要爲大對象分配空間時,由於找不到足夠大的內存空間,而不得以引發另外一次GC過程。

2.標記——複製算法(Mark——Copy)

標記——複製存儲算法通過採用雙區域交替使用這種方式解決了標記——清除算法中效率低下的問題。它將可可用內存劃分爲兩個等量的區域(使用區和空閒區),每次只使用一塊。當正在使用的區域需要進行垃圾回收時,存活的對象將被複制到另外一塊區域。原先被使用的區域被重置,轉爲空閒區。其執行流程大體如下所示:

圖書管理員爲了更好的發現不看的書,將圖書室一分爲二(A區和B區),同一時刻只有一塊區域允許看書。開始時只允許在A區看書。當管理員想要回收A區不被看的書的時候,大喊一嗓子“正在看書的同學拿着你書到B區”。等所有人都到了B區後,圖書管理員只要把A區的書收集起來,就完成了任務。下一次收集的時候,則是要求同學帶着自己看的書從B區轉移到A區。如此循環往復即可。

缺點:

1、原有可用空間被縮小爲1/2,空間利用率降低了。
2、過程中也會暫停當前應用的運行。

3.標記——整理算法(Mark——Compat)

標記-複製算法在對象存活率較高的情況下就要進行較多的複製操作,更重要的是該算法浪費一般的內存空間,爲了解決該問題,出現了標記——整理算法:其標記的過程和“標記-清除”算法一樣,而整理的過程則是讓所有存活的對象都想另外一端移動,然後直接清理掉端邊界以外的內存。其執行流程大體如下所示:

缺點:

1、暫停當前應用的運行,非實時性的回收。

4.分代收集算法

分代收集算法理論來源於統計學。IBM公司的專門研究發現,對象的生存週期總體可分爲三種:新生代、老年代和永久代。因此可以根據各個年代的特點採用適當的垃圾回收算法。比如新生代的對象在每次垃圾時都會有大量的對象死去,只有很少一部分存活,那就可以選擇標記-複製算法。另外I,在新生代中每次死亡對象約佔98%,那麼在標記-複製算法中就不需要按照1:1的比例來劃分內存區域,而是將新生代細分爲了一塊較大的Eden和兩塊較小的Survivor區域,HotSpot中默認這兩塊區域的大小比例爲8:2。每次新生代可用區域爲Eden加上其中一塊Survivor區域,共90%的內存空間,這樣就只有10%的內存空間處在被閒置狀態。在進行垃圾回收時,存活的對象被轉移到原本處在“空閒的”Eden區域。如果某次垃圾回收後,存活對象所佔空間遠大於這10%的內存空間時,也就是Survivor空間不夠用時,需要額外的空間來擔保,通常是將這些對象轉移到老年代。對於老年代來說,大部分對象都處在存活狀態。同時,如果一個大對象要在該區域進行分配,而內存空間又不足,那麼在沒有外部內存空間擔保的情況下,就必須選用標記-清除或者標記-整理算法來進行垃圾回收了。

總而言之,分代收集只是根據對象生存週期的不同來選擇不同的算法,其本身並沒有任何新思想。

5.增量收集算法

以上所述的算法,都存在一個缺點:在進行垃圾回首時需要暫停當前應用的執行,也就是這時候的垃圾回收線程不能和應用線程同時運行。如果我們想做到“在不打斷同學們看書的情況下,圖書管理員就可以收集沒有被看的書”,這也是增量收集算法的目標,即在不中斷應用線程的狀態下垃圾回收線程也能進行垃圾回收。但是這裏需要面對的問題是:垃圾回收線程在標記階段標記好了,還沒來的及清除時,當前應用線程進行內存操作,以至於清除階段無法正確開展,類似的情況是:圖書管理員剛標記了《JAVA核心技術》這本書已經沒有人看了,等標記完後,卻發現這本書已經有人在看了。

如何判斷一個對象是否應該被回收

這就是所謂的對象存活性判斷,常用的方法有兩種:1.引用計數法; 2.對象可達性分析。由於引用計數法存在互相引用導致無法進行GC的問題,所以目前JVM虛擬機多使用對象可達性分析算法。

簡單的解釋一下垃圾回收

Java 垃圾回收機制最基本的做法是分代回收。內存中的區域被劃分成不同的世代,對象根據其存活的時間被保存在對應世代的區域中。一般的實現是劃分成3個世代:年輕、年老和永久。內存的分配是發生在年輕世代中的。當一個對象存活時間足夠長的時候,它就會被複制到年老世代中。對於不同的世代可以使用不同的垃圾回收算法。進行世代劃分的出發點是對應用中對象存活時間進行研究之後得出的統計規律。一般來說,一個應用中的大部分對象的存活時間都很短。比如局部變量的存活時間就只在方法的執行過程中。基於這一點,對於年輕世代的垃圾回收算法就可以很有針對性。

調用System.gc()會發生什麼?

通知GC開始工作,但是GC真正開始的時間不確定。


進程,線程相關

說說進程,線程,協程之間的區別

簡而言之,進程是程序運行和資源分配的基本單位,一個程序至少有一個進程,一個進程至少有一個線程。進程在執行過程中擁有獨立的內存單元,而多個線程共享內存資源,減少切換次數,從而效率更高。線程是進程的一個實體,是cpu調度和分派的基本單位,是比程序更小的能獨立運行的基本單位。同一進程中的多個線程之間可以併發執行。

你瞭解守護線程嗎?它和非守護線程有什麼區別

程序運行完畢,jvm會等待非守護線程完成後關閉,但是jvm不會等待守護線程。守護線程最典型的例子就是GC線程。

什麼是多線程上下文切換

多線程的上下文切換是指CPU控制權由一個已經正在運行的線程切換到另外一個就緒並等待獲取CPU執行權的線程的過程。

創建兩種線程的方式?他們有什麼區別?

通過實現java.lang.Runnable或者通過擴展java.lang.Thread類。相比擴展Thread,實現Runnable接口可能更優.原因有二:

  1. Java不支持多繼承。因此擴展Thread類就代表這個子類不能擴展其他類。而實現Runnable接口的類還可能擴展另一個類。
  2. 類可能只要求可執行即可,因此繼承整個Thread類的開銷過大。

Thread類中的start()和run()方法有什麼區別?

start()方法被用來啓動新創建的線程,而且start()內部調用了run()方法,這和直接調用run()方法的效果不一樣。當你調用run()方法的時候,只會是在原來的線程中調用,沒有新的線程啓動,start()方法纔會啓動新線程。

怎麼檢測一個線程是否持有對象監視器

Thread類提供了一個holdsLock(Object obj)方法,當且僅當對象obj的監視器被某條線程持有的時候纔會返回true,注意這是一個static方法,這意味着”某條線程”指的是當前線程。

Runnable和Callable的區別

Runnable接口中的run()方法的返回值是void,它做的事情只是純粹地去執行run()方法中的代碼而已;Callable接口中的call()方法是有返回值的,是一個泛型,和Future、FutureTask配合可以用來獲取異步執行的結果。
這其實是很有用的一個特性,因爲多線程相比單線程更難、更復雜的一個重要原因就是因爲多線程充滿着未知性,某條線程是否執行了?某條線程執行了多久?某條線程執行的時候我們期望的數據是否已經賦值完畢?無法得知,我們能做的只是等待這條多線程的任務執行完畢而已。而Callable+Future/FutureTask卻可以方便獲取多線程運行的結果,可以在等待時間太長沒獲取到需要的數據的情況下取消該線程的任務。

什麼導致線程阻塞

在這裏插入圖片描述

wait(),notify()和suspend(),resume()之間的區別

初看起來它們與 suspend() 和 resume() 方法對沒有什麼分別,但是事實上它們是截然不同的。區別的核心在於,前面敘述的所有方法,阻塞時都不會釋放佔用的鎖(如果佔用了的話),而這一對方法則相反。上述的核心區別導致了一系列的細節上的區別。
首先,前面敘述的所有方法都隸屬於 Thread 類,但是這一對卻直接隸屬於 Object 類,也就是說,所有對象都擁有這一對方法。初看起來這十分不可思議,但是實際上卻是很自然的,因爲這一對方法阻塞時要釋放佔用的鎖,而鎖是任何對象都具有的,調用任意對象的 wait() 方法導致線程阻塞,並且該對象上的鎖被釋放。而調用 任意對象的notify()方法則導致從調用該對象的 wait() 方法而阻塞的線程中隨機選擇的一個解除阻塞(但要等到獲得鎖後才真正可執行)。
其次,前面敘述的所有方法都可在任何位置調用,但是這一對方法卻必須在 synchronized 方法或塊中調用,理由也很簡單,只有在synchronized 方法或塊中當前線程才佔有鎖,纔有鎖可以釋放。同樣的道理,調用這一對方法的對象上的鎖必須爲當前線程所擁有,這樣纔有鎖可以釋放。因此,這一對方法調用必須放置在這樣的 synchronized 方法或塊中,該方法或塊的上鎖對象就是調用這一對方法的對象。若不滿足這一條件,則程序雖然仍能編譯,但在運行時會出現IllegalMonitorStateException 異常。
wait() 和 notify() 方法的上述特性決定了它們經常和synchronized關鍵字一起使用,將它們和操作系統進程間通信機制作一個比較就會發現它們的相似性:synchronized方法或塊提供了類似於操作系統原語的功能,它們的執行不會受到多線程機制的干擾,而這一對方法則相當於 block 和wakeup 原語(這一對方法均聲明爲 synchronized)。它們的結合使得我們可以實現操作系統上一系列精妙的進程間通信的算法(如信號量算法),並用於解決各種複雜的線程間通信問題。

關於 wait() 和 notify() 方法最後再說明兩點:

  • 第一:調用 notify() 方法導致解除阻塞的線程是從因調用該對象的 wait() 方法而阻塞的線程中隨機選取的,我們無法預料哪一個線程將會被選擇,所以編程時要特別小心,避免因這種不確定性而產生問題。
  • 第二:除了 notify(),還有一個方法 notifyAll() 也可起到類似作用,唯一的區別在於,調用 notifyAll() 方法將把因調用該對象的 wait() 方法而阻塞的所有線程一次性全部解除阻塞。當然,只有獲得鎖的那一個線程才能進入可執行狀態。
  • 談到阻塞,就不能不談一談死鎖,略一分析就能發現,suspend() 方法和不指定超時期限的 wait() 方法的調用都可能產生死鎖。遺憾的是,Java 並不在語言級別上支持死鎖的避免,我們在編程中必須小心地避免死鎖。
    以上我們對 Java 中實現線程阻塞的各種方法作了一番分析,我們重點分析了 wait() 和 notify() 方法,因爲它們的功能最強大,使用也最靈活,但是這也導致了它們的效率較低,較容易出錯。實際使用中我們應該靈活使用各種方法,以便更好地達到我們的目的。

產生死鎖的條件

  1. 互斥條件:一個資源每次只能被一個進程使用。
  2. 請求與保持條件:一個進程因請求資源而阻塞時,對已獲得的資源保持不放。
  3. 不剝奪條件:進程已獲得的資源,在末使用完之前,不能強行剝奪。
  4. 循環等待條件:若干進程之間形成一種頭尾相接的循環等待資源關係。

爲什麼wait()方法和notify()/notifyAll()方法要在同步塊中被調用

這是JDK強制的,wait()方法和notify()/notifyAll()方法在調用前都必須先獲得對象的鎖

wait()方法和notify()/notifyAll()方法在放棄對象監視器時有什麼區別

wait()方法和notify()/notifyAll()方法在放棄對象監視器的時候的區別在於:wait()方法立即釋放對象監視器,notify()/notifyAll()方法則會等待線程剩餘代碼執行完畢纔會放棄對象監視器。

wait()與sleep()的區別

關於這兩者已經在上面進行詳細的說明,這裏就做個概括好了:

  • 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執行權的線程的過程。

synchronized和ReentrantLock的區別

synchronized是和if、else、for、while一樣的關鍵字,ReentrantLock是類,這是二者的本質區別。既然ReentrantLock是類,那麼它就提供了比synchronized更多更靈活的特性,可以被繼承、可以有方法、可以有各種各樣的類變量,ReentrantLock比synchronized的擴展性體現在幾點上:
(1)ReentrantLock可以對獲取鎖的等待時間進行設置,這樣就避免了死鎖
(2)ReentrantLock可以獲取各種鎖的信息
(3)ReentrantLock可以靈活地實現多路通知
另外,二者的鎖機制其實也是不一樣的:ReentrantLock底層調用的是Unsafe的park方法加鎖,synchronized操作的應該是對象頭中mark word。

FutureTask是什麼

這個其實前面有提到過,FutureTask表示一個異步運算的任務。FutureTask裏面可以傳入一個Callable的具體實現類,可以對這個異步運算的任務的結果進行等待獲取、判斷是否已經完成、取消任務等操作。當然,由於FutureTask也是Runnable接口的實現類,所以FutureTask也可以放入線程池中。

一個線程如果出現了運行時異常怎麼辦?

如果這個異常沒有被捕獲的話,這個線程就停止執行了。另外重要的一點是:如果這個線程持有某個對象的監視器,那麼這個對象監視器會被立即釋放。

Java當中有哪幾種鎖

  1. 自旋鎖: 自旋鎖在JDK1.6之後就默認開啓了。基於之前的觀察,共享數據的鎖定狀態只會持續很短的時間,爲了這一小段時間而去掛起和恢復線程有點浪費,所以這裏就做了一個處理,讓後面請求鎖的那個線程在稍等一會,但是不放棄處理器的執行時間,看看持有鎖的線程能否快速釋放。爲了讓線程等待,所以需要讓線程執行一個忙循環也就是自旋操作。在jdk6之後,引入了自適應的自旋鎖,也就是等待的時間不再固定了,而是由上一次在同一個鎖上的自旋時間及鎖的擁有者狀態來決定。
  2. 偏向鎖: 在JDK1.之後引入的一項鎖優化,目的是消除數據在無競爭情況下的同步原語。進一步提升程序的運行性能。 偏向鎖就是偏心的偏,意思是這個鎖會偏向第一個獲得他的線程,如果接下來的執行過程中,改鎖沒有被其他線程獲取,則持有偏向鎖的線程將永遠不需要再進行同步。偏向鎖可以提高帶有同步但無競爭的程序性能,也就是說他並不一定總是對程序運行有利,如果程序中大多數的鎖都是被多個不同的線程訪問,那偏向模式就是多餘的,在具體問題具體分析的前提下,可以考慮是否使用偏向鎖。
  3. 輕量級鎖: 爲了減少獲得鎖和釋放鎖所帶來的性能消耗,引入了“偏向鎖”和“輕量級鎖”,所以在Java SE1.6裏鎖一共有四種狀態,無鎖狀態,偏向鎖狀態,輕量級鎖狀態和重量級鎖狀態,它會隨着競爭情況逐漸升級。鎖可以升級但不能降級,意味着偏向鎖升級成輕量級鎖後不能降級成偏向鎖。

如何在兩個線程間共享數據

通過在線程之間共享對象就可以了,然後通過wait/notify/notifyAll、await/signal/signalAll進行喚起和等待,比方說阻塞隊列BlockingQueue就是爲線程之間共享數據而設計的。

如何正確的使用wait()?使用if還是while?

wait() 方法應該在循環調用,因爲當線程獲取到 CPU 開始執行的時候,其他條件可能還沒有滿足,所以在處理前,循環檢測條件是否滿足會更好。下面是一段標準的使用 wait 和 notify 方法的代碼:

synchronized (obj) {
   while (condition does not hold)
     obj.wait(); // (Releases lock, and reacquires on wakeup)
     ... // Perform action appropriate to condition
}

什麼是線程局部變量ThreadLocal

線程局部變量是侷限於線程內部的變量,屬於線程自身所有,不在多個線程間共享。Java提供ThreadLocal類來支持線程局部變量,是一種實現線程安全的方式。但是在管理環境下(如 web 服務器)使用線程局部變量的時候要特別小心,在這種情況下,工作線程的生命週期比任何應用變量的生命週期都要長。任何線程局部變量一旦在工作完成後沒有釋放,Java 應用就存在內存泄露的風險。

ThreadLoal的作用是什麼?

簡單說ThreadLocal就是一種以空間換時間的做法在每個Thread裏面維護了一個ThreadLocal.ThreadLocalMap把數據進行隔離,數據不共享,自然就沒有線程安全方面的問題了。

生產者消費者模型的作用是什麼?

(1)通過平衡生產者的生產能力和消費者的消費能力來提升整個系統的運行效率,這是生產者消費者模型最重要的作用。
(2)解耦,這是生產者消費者模型附帶的作用,解耦意味着生產者和消費者之間的聯繫少,聯繫越少越可以獨自發展而不需要收到相互的制約。

寫一個生產者-消費者隊列

可以通過阻塞隊列實現,也可以通過wait-notify來實現。
使用阻塞隊列來實現

//消費者
public class Producer implements Runnable{
   private final BlockingQueue<Integer> 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<Integer> 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<Integer> queue=new ArrayBlockingQueue<Integer>(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來實現

該種方式應該最經典,這裏就不做說明了。

如果你提交任務時,線程池隊列已滿,這時會發生什麼

如果你使用的LinkedBlockingQueue,也就是無界隊列的話,沒關係,繼續添加任務到阻塞隊列中等待執行,因爲LinkedBlockingQueue可以近乎認爲是一個無窮大的隊列,可以無限存放任務;如果你使用的是有界隊列比方說ArrayBlockingQueue的話,任務首先會被添加到ArrayBlockingQueue中,ArrayBlockingQueue滿了,則會使用拒絕策略RejectedExecutionHandler處理滿了的任務,默認是AbortPolicy。

爲什麼要使用線程池

避免頻繁地創建和銷燬線程,達到線程對象的重用。另外,使用線程池還可以根據項目靈活地控制併發的數目。

java中用到的線程調度算法是什麼

搶佔式。一個線程用完CPU之後,操作系統會根據線程優先級、線程飢餓情況等數據算出一個總的優先級並分配下一個時間片給某個線程執行。

Thread.sleep(0)的作用是什麼

由於Java採用搶佔式的線程調度算法,因此可能會出現某條線程常常獲取到CPU控制權的情況,爲了讓某些優先級比較低的線程也能獲取到CPU控制權,可以使用Thread.sleep(0)手動觸發一次操作系統分配時間片的操作,這也是平衡CPU控制權的一種操作。

什麼是CAS

CAS,全稱爲Compare and Swap,即比較-替換。假設有三個操作數:內存值V、舊的預期值A、要修改的值B,當且僅當預期值A和內存值V相同時,纔會將內存值修改爲B並返回true,否則什麼都不做並返回false。當然CAS一定要volatile變量配合,這樣才能保證每次拿到的變量是主內存中最新的那個值,否則舊的預期值A對某條線程來說,永遠是一個不會變的值A,只要某次CAS操作失敗,永遠都不可能成功。

什麼是樂觀鎖和悲觀鎖

  1. 樂觀鎖:樂觀鎖認爲競爭不總是會發生,因此它不需要持有鎖,將比較-替換這兩個動作作爲一個原子操作嘗試去修改內存中的變量,如果失敗則表示發生衝突,那麼就應該有相應的重試邏輯。
  2. 悲觀鎖:悲觀鎖認爲競爭總是會發生,因此每次對某資源進行操作時,都會持有一個獨佔的鎖,就像synchronized,不管三七二十一,直接上了鎖就操作資源了。

ConcurrentHashMap的併發度是什麼?

ConcurrentHashMap的併發度就是segment的大小,默認爲16,這意味着最多同時可以有16條線程操作ConcurrentHashMap,這也是ConcurrentHashMap對Hashtable的最大優勢,任何情況下,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區別

這兩個類非常類似,都在java.util.concurrent下,都可以用來表示代碼運行到某個點上,二者的區別在於:

  • CyclicBarrier的某個線程運行到某個點上之後,該線程即停止運行,直到所有的線程都到達了這個點,所有線程才重新運行;CountDownLatch則不是,某線程運行到某個點上之後,只是給某個數值-1而已,該線程繼續運行。
  • CyclicBarrier只能喚起一個任務,CountDownLatch可以喚起多個任務
  • CyclicBarrier可重用,CountDownLatch不可重用,計數值爲0該CountDownLatch就不可再用了。

java中的++操作符線程安全麼?

不是線程安全的操作。它涉及到多個指令,如讀取變量值,增加,然後存儲回內存,這個過程可能會出現多個線程交差。

你有哪些多線程開發良好的實踐?

  1. 給線程命名
  2. 最小化同步範圍
  3. 優先使用volatile
  4. 儘可能使用更高層次的併發工具而非wait和notify()來實現線程通信,如BlockingQueue,Semeaphore
  5. 優先使用併發容器而非同步容器.
  6. 考慮使用線程池

關於volatile關鍵字

可以創建Volatile數組嗎?

Java 中可以創建 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的原理更該如此.這裏我們看這張圖即可:
更多內容可見集合類總結:
http://write.blog.csdn.net/postedit/40826423

poll()方法和remove()方法區別?

poll() 和 remove() 都是從隊列中取出一個元素,但是 poll() 在獲取元素失敗的時候會返回空,但是 remove() 失敗的時候會拋出異常。

LinkedHashMap和PriorityQueue的區別

PriorityQueue 是一個優先級隊列,保證最高或者最低優先級的的元素總是在隊列頭部,但是 LinkedHashMap 維持的順序是元素插入的順序。當遍歷一個 PriorityQueue 時,沒有任何順序保證,但是 LinkedHashMap 課保證遍歷順序是元素插入的順序。

WeakHashMap與HashMap的區別是什麼?

WeakHashMap 的工作與正常的 HashMap 類似,但是使用弱引用作爲 key,意思就是當 key 對象沒有任何引用時,key/value 將會被回收。

ArrayList和LinkedList的區別?

最明顯的區別是 ArrrayList底層的數據結構是數組,支持隨機訪問,而 LinkedList 的底層數據結構是雙向循環鏈表,不支持隨機訪問。使用下標訪問一個元素,ArrayList 的時間複雜度是 O(1),而 LinkedList 是 O(n)。

ArrayList和Array有什麼區別?

  1. Array可以容納基本類型和對象,而ArrayList只能容納對象。
  2. Array是指定大小的,而ArrayList大小是固定的

ArrayList和HashMap默認大小?

在 Java 7 中,ArrayList 的默認大小是 10 個元素,HashMap 的默認大小是16個元素(必須是2的冪)。這就是 Java 7 中 ArrayList 和 HashMap 類的代碼片段。

private static final int DEFAULT_CAPACITY = 10;
 
//from HashMap.java JDK 7
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

Comparator和Comparable的區別?

Comparable 接口用於定義對象的自然順序,而 comparator 通常用於定義用戶定製的順序。Comparable 總是隻有一個,但是可以有多個 comparator 來定義對象的順序。

如何實現集合排序?

你可以使用有序集合,如 TreeSet 或 TreeMap,你也可以使用有順序的的集合,如 list,然後通過 Collections.sort() 來排序。

如何打印數組內容

你可以使用 Arrays.toString() 和 Arrays.deepToString() 方法來打印數組。由於數組沒有實現 toString() 方法,所以如果將數組傳遞給 System.out.println() 方法,將無法打印出數組的內容,但是 Arrays.toString() 可以打印每個元素。

LinkedList的是單向鏈表還是雙向?

雙向循環列表,具體實現自行查閱源碼。

TreeMap是實現原理

採用紅黑樹實現,具體實現自行查閱源碼。

遍歷ArrayList時如何正確移除一個元素

該問題的關鍵在於面試者使用的是 ArrayList 的 remove() 還是 Iterator 的 remove()方法。這有一段示例代碼,是使用正確的方式來實現在遍歷的過程中移除元素,而不會出現 ConcurrentModificationException 異常的示例代碼。

什麼是ArrayMap?它和HashMap有什麼區別?

ArrayMap是Android SDK中提供的,非Android開發者可以略過。
ArrayMap是用兩個數組來模擬map,更少的內存佔用空間,更高的效率。
具體參考這篇文章:ArrayMap VS HashMap:http://lvable.com/?p=217]

HashMap的實現原理

  1. HashMap概述: HashMap是基於哈希表的Map接口的非同步實現。此實現提供所有可選的映射操作,並允許使用null值和null鍵。此類不保證映射的順序,特別是它不保證該順序恆久不變。
  2. HashMap的數據結構: 在java編程語言中,最基本的結構就是兩種,一個是數組,另外一個是模擬指針(引用),所有的數據結構都可以用這兩個基本結構來構造的,HashMap也不例外。HashMap實際上是一個“鏈表散列”的數據結構,即數組和鏈表的結合體。
    當我們往Hashmap中put元素時,首先根據key的hashcode重新計算hash值,根絕hash值得到這個元素在數組中的位置(下標),如果該數組在該位置上已經存放了其他元素,那麼在這個位置上的元素將以鏈表的形式存放,新加入的放在鏈頭,最先加入的放入鏈尾.如果數組中該位置沒有元素,就直接將該元素放到數組的該位置上.
    需要注意Jdk 1.8中對HashMap的實現做了優化,當鏈表中的節點數據超過八個之後,該鏈表會轉爲紅黑樹來提高查詢效率,從原來的O(n)到O(logn)

你瞭解Fail-Fast機制嗎?

Fail-Fast即我們常說的快速失敗,
更多內容參看fail-fast機制:http://blog.csdn.net/chenssy/article/details/38151189

Fail-fast和Fail-safe有什麼區別

Iterator的fail-fast屬性與當前的集合共同起作用,因此它不會受到集合中任何改動的影響。Java.util包中的所有集合類都被設計爲fail->fast的,而java.util.concurrent中的集合類都爲fail-safe的。當檢測到正在遍歷的集合的結構被改變時,Fail-fast迭代器拋出ConcurrentModificationException,而fail-safe迭代器從不拋出ConcurrentModificationException。


關於日期

SimpleDateFormat是線程安全的嗎?

非常不幸,DateFormat 的所有實現,包括 SimpleDateFormat 都不是線程安全的,因此你不應該在多線程序中使用,除非是在對外線程安全的環境中使用,如 將 SimpleDateFormat 限制在 ThreadLocal 中。如果你不這麼做,在解析或者格式化日期的時候,可能會獲取到一個不正確的結果。因此,從日期、時間處理的所有實踐來說,我強力推薦 joda-time 庫。

如何格式化日期?

Java 中,可以使用 SimpleDateFormat 類或者 joda-time 庫來格式日期。DateFormat 類允許你使用多種流行的格式來格式化日期。參見答案中的示例代碼,代碼中演示了將日期格式化成不同的格式,如 dd-MM-yyyy 或 ddMMyyyy。


關於異常

簡單描述java異常體系

相比沒有人不瞭解異常體系,關於異常體系的更多信息可以見
白話異常機制:http://blog.csdn.net/dd864140130/article/details/42504189

什麼是異常鏈

詳情直接參見上面的白話異常機制,不做解釋了。

throw和throws的區別

throw用於主動拋出java.lang.Throwable 類的一個實例化對象,意思是說你可以通過關鍵字 throw 拋出一個 Error 或者 一個Exception,如:throw new IllegalArgumentException(“size must be multiple of 2″),
而throws 的作用是作爲方法聲明和簽名的一部分,方法被拋出相應的異常以便調用者能處理。Java 中,任何未處理的受檢查異常強制在 throws 子句中聲明。


關於序列化

Java 中,Serializable 與 Externalizable 的區別

Serializable 接口是一個序列化 Java 類的接口,以便於它們可以在網絡上傳輸或者可以將它們的狀態保存在磁盤上,是 JVM 內嵌的默認序列化方式,成本高、脆弱而且不安全。Externalizable 允許你控制整個序列化過程,指定特定的二進制格式,增加安全機制。


關於JVM

JVM特性

平臺無關性.
Java語言的一個非常重要的特點就是與平臺的無關性。而使用Java虛擬機是實現這一特點的關鍵。一般的高級語言如果要在不同的平臺上運行,至少需要編譯成不同的目標代碼。而引入Java語言虛擬機後,Java語言在不同平臺上運行時不需要重新編譯。Java語言使用模式Java虛擬機屏蔽了與具體平臺相關的信息,使得Java語言編譯程序只需生成在Java虛擬機上運行的目標代碼(字節碼),就可以在多種平臺上不加修改地運行。Java虛擬機在執行字節碼時,把字節碼解釋成具體平臺上的機器指令執行。

簡單解釋一下類加載器

有關類加載器一般會問你四種類加載器的應用場景以及雙親委派模型,
更多的內容參看深入理解JVM加載器:
http://blog.csdn.net/dd864140130/article/details/49817357

簡述堆和棧的區別

VM 中堆和棧屬於不同的內存區域,使用目的也不同。棧常用於保存方法幀和局部變量,而對象總是在堆上分配。棧通常都比堆小,也不會在多個線程之間共享,而堆被整個 JVM 的所有線程共享。

簡述JVM內存分配

  1. 基本數據類型比變量和對象的引用都是在棧分配的。
  2. 堆內存用來存放由new創建的對象和數組。
  3. 類變量(static修飾的變量),程序在一加載的時候就在堆中爲類變量分配內存,堆中的內存地址存放在棧中。
  4. 實例變量:當你使用java關鍵字new的時候,系統在堆中開闢並不一定是連續的空間分配給變量,是根據零散的堆內存地址,通過哈希算法換算爲一長串數字以表徵這個變量在堆中的”物理位置”,實例變量的生命週期–當實例變量的引用丟失後,將被GC(垃圾回收器)列入可回收“名單”中,但並不是馬上就釋放堆中內存。
    5.局部變量: 由聲明在某方法,或某代碼段裏(比如for循環),執行到它的時候在棧中開闢內存,當局部變量一但脫離作用域,內存立即釋放。

其他

java當中採用的是大端還是小端?

XML解析的幾種方式和特點

DOM, SAX, PULL三種解析方式:

  • DOM:消耗內存:先把xml文檔都讀到內存中,然後再用DOM API來訪問樹形結構,並獲取數據。這個寫起來很簡單,但是很消耗內存。要是數據過大,手機不夠牛逼,可能手機直接死機
  • SAX:解析效率高,佔用內存少,基於事件驅動的:更加簡單地說就是對文檔進行順序掃描,當掃描到文檔(document)開始與結束、元素(element)開始與結束、文檔(document)結束等地方時通知事件處理函數,由事件處理函數做相應動作,然後繼續同樣的掃描,直至文檔結束。
  • PULL:與 SAX 類似,也是基於事件驅動,我們可以調用它的next()方法,來獲取下一個解析事件(就是開始文檔,結束文檔,開始標籤,結束標籤),當處於某個元素時可以調用XmlPullParser的getAttributte()方法來獲取屬性的值,也可調用它的nextText()獲取本節點的值。

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 歷史上是一個開創新的版本,下面 JDK 8 中 5 個主要的特性:
Lambda 表達式,允許像對象一樣傳遞匿名函數
Stream API,充分利用現代多核 CPU,可以寫出很簡潔的代碼
Date 與 Time API,最終,有一個穩定、簡單的日期和時間庫可供你使用
擴展方法,現在,接口中可以有靜態、默認方法。
重複註解,現在你可以將相同的註解在同一類型上使用多次。

Maven和ANT有什麼區別?

雖然兩者都是構建工具,都用於創建 Java 應用,但是 Maven 做的事情更多,在基於“約定優於配置”的概念下,提供標準的Java 項目結構,同時能爲應用自動管理依賴(應用中所依賴的 JAR 文件。

JDBC最佳實踐

  • 優先使用批量操作來插入和更新數據
  • 使用PreparedStatement來避免SQL漏洞
  • 使用數據連接池
  • 通過列名來獲取結果集

IO操作最佳實踐

  • 使用有緩衝的IO類,不要單獨讀取字節或字符
  • 使用NIO和NIO 2或者AIO,而非BIO
  • 在finally中關閉流
  • 使用內存映射文件獲取更快的IO
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章