Java高階面試題及答案

Java知音發表於2018-05-02

List和Set比較,各自的子類比較

對比一:Arraylist與LinkedList的比較

1、ArrayList是實現了基於動態陣列的資料結構,因為地址連續,一旦資料儲存好了,查詢操作效率會比較高(在記憶體裡是連著放的)。

2、因為地址連續, ArrayList要移動資料,所以插入和刪除操作效率比較低。

3、LinkedList基於連結串列的資料結構,地址是任意的,所以在開闢記憶體空間的時候不需要等一個連續的地址,對於新增和刪除操作add和remove,LinedList比較佔優勢。

4、因為LinkedList要移動指標,所以查詢操作效能比較低。

適用場景分析:

當需要對資料進行對此訪問的情況下選用ArrayList,當需要對資料進行多次增加刪除修改時採用LinkedList。

對比二:ArrayList與Vector的比較

1、Vector的方法都是同步的,是執行緒安全的,而ArrayList的方法不是,由於執行緒的同步必然要影響效能。因此,ArrayList的效能比Vector好。
2、當Vector或ArrayList中的元素超過它的初始大小時,Vector會將它的容量翻倍,而ArrayList只增加50%的大小,這樣。ArrayList就有利於節約記憶體空間。

3、大多數情況不使用Vector,因為效能不好,但是它支援執行緒的同步,即某一時刻只有一個執行緒能夠寫Vector,避免多執行緒同時寫而引起的不一致性。

4、Vector可以設定增長因子,而ArrayList不可以。

適用場景分析:

1、Vector是執行緒同步的,所以它也是執行緒安全的,而ArrayList是執行緒非同步的,是不安全的。如果不考慮到執行緒的安全因素,一般用ArrayList效率比較高。

2、如果集合中的元素的數目大於目前集合陣列的長度時,在集合中使用資料量比較大的資料,用Vector有一定的優勢。

對比三:HashSet與TreeSet的比較

1.TreeSet 是二叉樹實現的,Treeset中的資料是自動排好序的,不允許放入null值 。

2.HashSet 是雜湊表實現的,HashSet中的資料是無序的,可以放入null,但只能放入一個null,兩者中的值都不能重複,就如資料庫中唯一約束 。

3.HashSet要求放入的物件必須實現HashCode()方法,放入的物件,是以hashcode碼作為標識的,而具有相同內容的String物件,hashcode是一樣,所以放入的內容不能重複。但是同一個類的物件可以放入不同的例項。

適用場景分析:

HashSet是基於Hash演算法實現的,其效能通常都優於TreeSet。我們通常都應該使用HashSet,在我們需要排序的功能時,我們才使用TreeSet。


HashMap和ConcurrentHashMap的區別

1、HashMap不是執行緒安全的,而ConcurrentHashMap是執行緒安全的。

2、ConcurrentHashMap採用鎖分段技術,將整個Hash桶進行了分段segment,也就是將這個大的陣列分成了幾個小的片段segment,而且每個小的片段segment上面都有鎖存在,那麼在插入元素的時候就需要先找到應該插入到哪一個片段segment,然後再在這個片段上面進行插入,而且這裡還需要獲取segment鎖。

3、ConcurrentHashMap讓鎖的粒度更精細一些,併發效能更好。


JVM的記憶體結構

根據 JVM 規範,JVM 記憶體共分為虛擬機器棧、堆、方法區、程式計數器、本地方法棧五個部分。

1、Java虛擬機器棧:

執行緒私有;每個方法在執行的時候會建立一個棧幀,儲存了區域性變數表,運算元棧,動態連線,方法返回地址等;每個方法從呼叫到執行完畢,對應一個棧幀在虛擬機器棧中的入棧和出棧。

2、堆:

執行緒共享;被所有執行緒共享的一塊記憶體區域,在虛擬機器啟動時建立,用於存放物件例項。

3、方法區:

執行緒共享;被所有執行緒共享的一塊記憶體區域;用於儲存已被虛擬機器載入的類資訊,常量,靜態變數等。

4、程式計數器:

執行緒私有;是當前執行緒所執行的位元組碼的行號指示器,每條執行緒都要有一個獨立的程式計數器,這類記憶體也稱為“執行緒私有”的記憶體。

5、本地方法棧:

執行緒私有;主要為虛擬機器使用到的Native方法服務。


強引用,軟引用和弱引用的區別

強引用:

只有這個引用被釋放之後,物件才會被釋放掉,只要引用存在,垃圾回收器永遠不會回收,這是最常見的New出來的物件。

軟引用:

記憶體溢位之前通過程式碼回收的引用。軟引用主要使用者實現類似快取的功能,在記憶體足夠的情況下直接通過軟引用取值,無需從繁忙的真實來源查詢資料,提升速度;當記憶體不足時,自動刪除這部分快取資料,從真正的來源查詢這些資料。

弱引用:

第二次垃圾回收時回收的引用,短時間內通過弱引用取對應的資料,可以取到,當執行過第二次垃圾回收時,將返回null。弱引用主要用於監控物件是否已經被垃圾回收器標記為即將回收的垃圾,可以通過弱引用的isEnQueued方法返回物件是否被垃圾回收器標記。


springmvc的核心是什麼,請求的流程是怎麼處理的,控制反轉怎麼實現的

核心:

控制反轉和麵向切面

請求處理流程:

1、首先使用者傳送請求到前端控制器,前端控制器根據請求資訊(如URL)來決定選擇哪一個頁面控制器進行處理並把請求委託給它,即以前的控制器的控制邏輯部分;

2、頁面控制器接收到請求後,進行功能處理,首先需要收集和繫結請求引數到一個物件,並進行驗證,然後將命令物件委託給業務物件進行處理;處理完畢後返回一個ModelAndView(模型資料和邏輯檢視名);

3、前端控制器收回控制權,然後根據返回的邏輯檢視名,選擇相應的檢視進行渲染,並把模型資料傳入以便檢視渲染;

4、前端控制器再次收回控制權,將響應返回給使用者。

控制反轉如何實現:

我們每次使用spring框架都要配置xml檔案,這個xml配置了bean的id和class。

spring中預設的bean為單例項模式,通過bean的class引用反射機制可以建立這個例項。

因此,spring框架通過反射替我們建立好了例項並且替我們維護他們。

A需要引用B類,spring框架就會通過xml把B例項的引用傳給了A的成員變數。


BIO、NIO和AIO的區別

Java BIO : 同步並阻塞,伺服器實現模式為一個連線一個執行緒,即客戶端有連線請求時伺服器端就需要啟動一個執行緒進行處理,如果這個連線不做任何事情會造成不必要的執行緒開銷,當然可以通過執行緒池機制改善。

Java NIO : 同步非阻塞,伺服器實現模式為一個請求一個執行緒,即客戶端傳送的連線請求都會註冊到多路複用器上,多路複用器輪詢到連線有I/O請求時才啟動一個執行緒進行處理。

Java AIO: 非同步非阻塞,伺服器實現模式為一個有效請求一個執行緒,客戶端的I/O請求都是由OS先完成了再通知伺服器應用去啟動執行緒進行處理。

NIO比BIO的改善之處是把一些無效的連線擋在了啟動執行緒之前,減少了這部分資源的浪費(因為我們都知道每建立一個執行緒,就要為這個執行緒分配一定的記憶體空間)

AIO比NIO的進一步改善之處是將一些暫時可能無效的請求擋在了啟動執行緒之前,比如在NIO的處理方式中,當一個請求來的話,開啟執行緒進行處理,但這個請求所需要的資源還沒有就緒,此時必須等待後端的應用資源,這時執行緒就被阻塞了。

適用場景分析:

BIO方式適用於連線數目比較小且固定的架構,這種方式對伺服器資源要求比較高,併發侷限於應用中,JDK1.4以前的唯一選擇,但程式直觀簡單易理解,如之前在Apache中使用。

NIO方式適用於連線數目多且連線比較短(輕操作)的架構,比如聊天伺服器,併發侷限於應用中,程式設計比較複雜,JDK1.4開始支援,如在 Nginx,Netty中使用。

AIO方式使用於連線數目多且連線比較長(重操作)的架構,比如相簿伺服器,充分呼叫OS參與併發操作,程式設計比較複雜,JDK7開始支援,在成長中,Netty曾經使用過,後來放棄。


為什麼要用執行緒池

那先要明白什麼是執行緒池

執行緒池是指在初始化一個多執行緒應用程式過程中建立一個執行緒集合,然後在需要執行新的任務時重用這些執行緒而不是新建一個執行緒。

使用執行緒池的好處

1、執行緒池改進了一個應用程式的響應時間。由於執行緒池中的執行緒已經準備好且等待被分配任務,應用程式可以直接拿來使用而不用新建一個執行緒。

2、執行緒池節省了CLR 為每個短生存週期任務建立一個完整的執行緒的開銷並可以在任務完成後回收資源。

3、執行緒池根據當前在系統中執行的程式來優化執行緒時間片。

4、執行緒池允許我們開啟多個任務而不用為每個執行緒設定屬性。

5、執行緒池允許我們為正在執行的任務的程式引數傳遞一個包含狀態資訊的物件引用。

6、執行緒池可以用來解決處理一個特定請求最大執行緒數量限制問題。


悲觀鎖和樂觀鎖的區別,怎麼實現

悲觀鎖:一段執行邏輯加上悲觀鎖,不同執行緒同時執行時,只能有一個執行緒執行,其他的執行緒在入口處等待,直到鎖被釋放。

樂觀鎖:一段執行邏輯加上樂觀鎖,不同執行緒同時執行時,可以同時進入執行,在最後更新資料的時候要檢查這些資料是否被其他執行緒修改了(版本和執行初是否相同),沒有修改則進行更新,否則放棄本次操作。

悲觀鎖的實現:

begin;/begin work;/start transaction; (三者選一就可以)
//1.查詢出商品資訊
select status from t_goods where id=1 for update;
//2.根據商品資訊生成訂單
insert into t_orders (id,goods_id) values (null,1);
//3.修改商品status為2
update t_goods set status=2;
//4.提交事務
commit;/commit work;
複製程式碼

樂觀鎖的實現:

1.查詢出商品資訊
select (status,status,version) from t_goods where id=#{id}
2.根據商品資訊生成訂單
3.修改商品status為2
update t_goods 
set status=2,version=version+1
where id=#{id} and version=#{version};
複製程式碼


什麼是執行緒死鎖?死鎖如何產生?如何避免執行緒死鎖?

死鎖的介紹:

執行緒死鎖是指由於兩個或者多個執行緒互相持有對方所需要的資源,導致這些執行緒處於等待狀態,無法前往執行。當執行緒進入物件的synchronized程式碼塊時,便佔有了資源,直到它退出該程式碼塊或者呼叫wait方法,才釋放資源,在此期間,其他執行緒將不能進入該程式碼塊。當執行緒互相持有對方所需要的資源時,會互相等待對方釋放資源,如果執行緒都不主動釋放所佔有的資源,將產生死鎖。

死鎖的產生的一些特定條件:

1、互斥條件:程式對於所分配到的資源具有排它性,即一個資源只能被一個程式佔用,直到被該程式釋放 。

2、請求和保持條件:一個程式因請求被佔用資源而發生阻塞時,對已獲得的資源保持不放。

3、不剝奪條件:任何一個資源在沒被該程式釋放之前,任何其他程式都無法對他剝奪佔用。

4、迴圈等待條件:當發生死鎖時,所等待的程式必定會形成一個環路(類似於死迴圈),造成永久阻塞。

如何避免:

1、加鎖順序:

當多個執行緒需要相同的一些鎖,但是按照不同的順序加鎖,死鎖就很容易發生。如果能確保所有的執行緒都是按照相同的順序獲得鎖,那麼死鎖就不會發生。當然這種方式需要你事先知道所有可能會用到的鎖,然而總有些時候是無法預知的。

2、加鎖時限:

加上一個超時時間,若一個執行緒沒有在給定的時限內成功獲得所有需要的鎖,則會進行回退並釋放所有已經獲得的鎖,然後等待一段隨機的時間再重試。但是如果有非常多的執行緒同一時間去競爭同一批資源,就算有超時和回退機制,還是可能會導致這些執行緒重複地嘗試但卻始終得不到鎖。

3、死鎖檢測:

死鎖檢測即每當一個執行緒獲得了鎖,會線上程和鎖相關的資料結構中(map、graph等等)將其記下。除此之外,每當有執行緒請求鎖,也需要記錄在這個資料結構中。死鎖檢測是一個更好的死鎖預防機制,它主要是針對那些不可能實現按序加鎖並且鎖超時也不可行的場景。


這一篇先總結這些,歡迎關注我的公眾號

Java高階面試題及答案


相關文章