Java開發工程師常見的面試總結走起.....

ly_20181001發表於2020-10-21

這個月您的技術棧提升了嗎?

時間過得挺快的,十月份還剩一週了,回顧過去,展望未來,技術旅程,重在積累實踐總結上。

面試

大家都知道進入一家不錯的企業都要經歷一個很重要的環節那就是面試,面試是檢驗候選人是否符合崗位要求,技術面試一般都是有淺往深問,這個時候最考驗候選人的技術能力了,思路大概是:

  1. 首先考察候選人的基礎知識掌握情況;
  2. 再通過深度考察是否有技術熱情和深度以及技術的廣度;
  3. 提出一些質疑和挑戰來考察候選人能否與有不同意見,這個最好能回答不同好的意見具有加分項;

面試準備

要想面試成功拿到offer,面試準備這個環節必不可少的,有一句俗語說的很對“好記性不如爛筆頭”,對於自己需要掌握的技術知識點要經常積累。
遇到比較難的問題不要逃避問題,要深入思考去解決,這樣解決一個個難題積累下來整理成文件,自然而然的就有了自己的個人體系,面試時候看一下就知道很多知識點。
如何將自己的技術水平很好的展現出來呢?
1.面試前看下面試單位的JD介紹,針對考察內容能否很快的在自己的腦海想到最優解答思路,如果沒有想到就應該把問題列出來,找資料,整理最優解答案以及擴充套件知識點。
2.整理自己簡歷,要把你簡歷裡的專案中最大的技術難點和亮點突出出來,並且有很好的解決方案,整理好思路,不至於面試過程中出現卡殼的現象。
3.在跟面試官進行技術溝通過程中要做到有理有據,不卑不亢,
4.在技術問題上堅持自己的客觀和原則,根據共同認可的事實進行邏輯判斷得出觀點。

基礎面試內容

抽象類是什麼?與介面有什麼區別?為什麼要使用抽象類?

  • 抽象類是不允許被例項化的類,一個類只能使用一次繼承關係,但是一個類可以實現多個介面
    抽象類和介面所反映出的設計理念不同:
    抽象類表示的是 “is - a”
    介面表示的是 “like - a”
    實現抽象類和介面的類必須實現其中的所有方法.抽象類可以有非抽象方法,介面中則不能有實現方法,但是在Java 8中允許介面中有靜態預設方法
    介面中定義的變數預設是public static final型,且必須給出初值,所以實現類中不能重新定義,也不能改變這個值
    抽象類中定義的變數預設是friendly型,這個變數的值可以在子類中重新定義,也可以重新賦值
    子類中實現父類中的抽象方法時.可見性可以大於等於父類中的
    介面實現類類中的介面方法的可見性只能與介面中的相同,即為public
    使用抽象類是為了重用,減少編碼量,降低耦合性

String,StringBuffer與StringBuilder的區別?

三者都可以可以儲存和操作字串。其中 String 是隻讀字串,也就意味著 String 引用的字串內容是不能被改變的。而 StringBuffer和StringBuilder 類表示的字串物件可以直接進行修改。StringBuilder 是 Java 5 中引入的,它和 StringBuffer 的方法完全相同,區別在於它是在單執行緒環境下使用的,因為它的所有方面都沒有被synchronized 修飾,因此它的效率也比 StringBuffer 要高。

  • 如果操作少量的資料用String
  • 單執行緒下操作大量的資料用StringBuilder
  • 多執行緒下操作大量的資料用StringBuffer

java中常用的集合類有哪些?

Map介面和Collection介面是所有集合框架的父介面:
Collection介面的子介面包括:Set介面和List介面
Map介面的實現類主要有:HashMap、TreeMap、Hashtable、ConcurrentHashMap以及Properties等
Set介面的實現類主要有:HashSet、TreeSet、LinkedHashSet等
List介面的實現類主要有:ArrayList、LinkedList、Stack以及Vector等

ArrayList,LinkList的區別?插入和查詢哪個更快?

  • 資料結構實現:ArrayList 是動態陣列的資料結構實現,而 LinkedList 是雙向連結串列的資料結構實現。
  • 隨機訪問效率:ArrayList 比 LinkedList 在隨機訪問的時候效率要高,因為 LinkedList 是線性的資料儲存方式,所以需要移動指標從前往後依次查詢。
  • 增加和刪除效率:在非首尾的增加和刪除操作,LinkedList 要比 ArrayList 效率要高,因為 ArrayList 增刪操作要影響陣列內的其他資料的下標。
  • 記憶體空間佔用:LinkedList 比 ArrayList 更佔記憶體,因為 LinkedList 的節點除了儲存資料,還儲存了兩個引用,一個指向前一個元素,一個指向後一個元素。
  • 執行緒安全:ArrayList 和 LinkedList 都是不同步的,也就是不保證執行緒安全;
  • 綜合來說,在需要頻繁讀取集合中的元素時,更推薦使用 ArrayList,而在插入和刪除操作較多時,更推薦使用 LinkedList。
    補充:資料結構基礎之雙向連結串列
    雙向連結串列也叫雙連結串列,是連結串列的一種,它的每個資料結點中都有兩個指標,分別指向直接後繼和直接前驅。所以,從雙向連結串列中的任意一個結點開始,都可以很方便地訪問它的前驅結點和後繼結點。

HashMap和HashTable的區別?

HashMap: JDK1.8之前HashMap由陣列+連結串列組成的,陣列是HashMap的主體,連結串列則是主要為了解決雜湊衝突而存在的(“拉鍊法”解決衝突).JDK1.8以後在解決雜湊衝突時有了較大的變化,當連結串列長度大於閾值(預設為8)時,將連結串列轉化為紅黑樹,以減少搜尋時間
LinkedHashMap:LinkedHashMap 繼承自 HashMap,所以它的底層仍然是基於拉鍊式雜湊結構即由陣列和連結串列或紅黑樹組成。另外,LinkedHashMap 在上面結構的基礎上,增加了一條雙向連結串列,使得上面的結構可以保持鍵值對的插入順序。同時通過對連結串列進行相應的操作,實現了訪問順序相關邏輯。
HashTable: 陣列+連結串列組成的,陣列是 HashMap 的主體,連結串列則是主要為了解決雜湊衝突而存在的
TreeMap: 紅黑樹(自平衡的排序二叉樹)

HashMap的原理,在Java 8中做了哪些改變?

  • Java8 對 HashMap 進行了一些修改,最大的不同就是利用了紅黑樹,所以其由 陣列+連結串列+紅黑樹組成。
  • 根據 Java7 HashMap 的介紹,我們知道,查詢的時候,根據 hash 值我們能夠快速定位到陣列的具體下標,但是之後的話,需要順著連結串列一個個比較下去才能找到我們需要的,時間複雜度取決於連結串列的長度,為 O(n)。為了降低這部分的開銷,在 Java8 中,當連結串列中的元素超過了 8 個以後,會將連結串列轉換為紅黑樹,在這些位置進行查詢的時候可以降低時間複雜度為 O(logN)
    在這裡插入圖片描述
    在這裡插入圖片描述
    HashMap 裡面是一個陣列,然後陣列中每個元素是一個單向連結串列。上圖中,每個綠色
    的實體是巢狀類 Entry 的例項,Entry 包含四個屬性:key, value, hash 值和用於單向連結串列的 next。
  • capacity:當前陣列容量,始終保持 2^n,可以擴容,擴容後陣列大小為當前的 2 倍。
  • loadFactor:負載因子,預設為 0.75。
  • threshold:擴容的閾值,等於 capacity * loadFactor

ConcurrentHashMap資料結構原理

  • ConcurrentHashMap 是一個 Segment 陣列,Segment 通過繼承 ReentrantLock 來進行加鎖,所以每次需要加鎖的操作鎖住的是一個 segment,這樣只要保證每 個 Segment 是執行緒安全的,也就實現了全域性的執行緒安全。

JAVA 執行緒實現和建立方式

  1. 繼承 Thread 類
    Thread 類本質上是實現了 Runnable 介面的一個例項,代表一個執行緒的例項。啟動執行緒的唯一方法就是通過 Thread 類的 start()例項方法。start()方法是一個 native 方法,它將啟動一個新線 程,並執行 run()方法。
public class MyThread extends Thread { 
  public void run() { 
   System.out.println("MyThread.run()");
  }
}
MyThread myThread = new MyThread(); 
myThread.start();   

2.實現 Runnable 介面。
如果自己的類已經 extends 另一個類,就無法直接 extends Thread,此時,可以實現一個Runnable 介面。

public class MyThread extends OtherClass implements Runnable {
  public void run() { 
   System.out.println("MyThread.run()");
  }
}

說說執行緒的生命週期

  • 當執行緒被建立並啟動以後,它既不是一啟動就進入了執行狀態,也不是一直處於執行狀態。線上程的生命週期中,它要經過新建(New)就緒(Runnable)執行(Running)阻塞 (Blocked)死亡(Dead) 5 種狀態。尤其是當執行緒啟動以後,它不可能一直"霸佔"著 CPU 獨自 執行,所以 CPU 需要在多條執行緒之間切換,於是執行緒狀態也會多次在執行、阻塞之間切換
  • 當程式使用 new 關鍵字建立了一個執行緒之後,該執行緒就處於新建狀態,此時僅由 JVM 為其分配記憶體,並初始化其成員變數的值
  • 當執行緒物件呼叫了 start()方法之後,該執行緒處於就緒狀態。Java 虛擬機器會為其建立方法呼叫棧和程式計數器,等待排程執行。
  • 如果處於就緒狀態的執行緒獲得了 CPU,開始執行 run()方法的執行緒執行體,則該執行緒處於執行狀態。
  • 阻塞狀態是指執行緒因為某種原因放棄了 cpu 使用權,也即讓出了 cpu timeslice,暫時停止執行。直到執行緒進入可執行(runnable)狀態,才有機會再次獲得 cpu timeslice 轉到執行(running)狀 態。
  • 阻塞的情況分三種:
    等待阻塞(o.wait->等待對列):執行(running)的執行緒執行 o.wait()方法,JVM 會把該執行緒放入等待佇列(waitting queue)中。
    同步阻塞(lock->鎖池) :執行(running)的執行緒在獲取物件的同步鎖時,若該同步鎖被別的執行緒佔用,則 JVM 會把該執行緒放入鎖池(lock pool)中。
    其他阻塞(sleep/join):執行(running)的執行緒執行 Thread.sleep(long ms)或 t.join()方法,或者發出了 I/O 請求時,
    JVM 會把該執行緒置為阻塞狀態。當 sleep()狀態超時、join()等待執行緒終止或者超時、或者 I/O 處理完畢時,執行緒重新轉入可執行(runnable)狀態。
  • 執行緒會以下面三種方式結束,結束後就是死亡狀態。
  • 正常結束
    run()或 call()方法執行完成,執行緒正常結束。
  • 異常結束
    執行緒丟擲一個未捕獲的 Exception 或 Error。
  • 呼叫 stop
    直接呼叫該執行緒的 stop()方法來結束該執行緒—該方法通常容易導致死鎖,不推薦使用。
    在這裡插入圖片描述

說說sleep 與 wait 區別有哪些

  1. 對於 sleep()方法,我們首先要知道該方法是屬於 Thread 類中的。而 wait()方法,則是屬於 Object 類中的。
  2. sleep()方法導致了程式暫停執行指定的時間,讓出 cpu 該其他執行緒,但是他的監控狀態依然 保持者,當指定的時間到了又會自動恢復執行狀態。
  3. 在呼叫 sleep()方法的過程中,執行緒不會釋放物件鎖。
  4. 而當呼叫 wait()方法的時候,執行緒會放棄物件鎖,進入等待此物件的等待鎖定池,只有針對此
    物件呼叫 notify()方法後本執行緒才進入物件鎖定池準備獲取物件鎖進入執行狀態。

java中的鎖有哪些?

樂觀鎖
樂觀鎖是一種樂觀思想,即認為讀多寫少,遇到併發寫的可能性低,每次去拿資料的時候都認為別人不會修改,所以不會上鎖,但是在更新的時候會判斷一下在此期間別人有沒有去更新這個數 據,採取在寫時先讀出當前版本號,然後加鎖操作(比較跟上一次的版本號,如果一樣則更新), 如果失敗則要重複讀-比較-寫的操作。
java 中的樂觀鎖基本都是通過 CAS 操作實現的,CAS 是一種更新的原子操作,比較當前值跟傳入值是否一樣,一樣則更新,否則失敗。

悲觀鎖
悲觀鎖是就是悲觀思想,即認為寫多,遇到併發寫的可能性高,每次去拿資料的時候都認為別人會修改,所以每次在讀寫資料的時候都會上鎖,這樣別人想讀寫這個資料就會 block 直到拿到鎖。 java 中的悲觀鎖就是 Synchronized,AQS 框架下的鎖則是先嚐試 cas 樂觀鎖去獲取鎖,獲取不到, 才會轉換為悲觀鎖,如 RetreenLock。

自旋鎖
如果持有鎖的執行緒能在很短時間內釋放鎖資源,那麼那些等待競爭鎖的執行緒就不需要做核心態和使用者態之間的切換進入阻塞掛起狀態,它們只需要等一等(自旋), 等持有鎖的執行緒釋放鎖後即可立即獲取鎖,這樣就避免使用者執行緒和核心的切換的消耗。
執行緒自旋是需要消耗 cup 的,說白了就是讓 cup 在做無用功,如果一直獲取不到鎖,那執行緒 也不能一直佔用 cup 自旋做無用功,所以需要設定一個自旋等待的最大時間。
如果持有鎖的執行緒執行的時間超過自旋等待的最大時間扔沒有釋放鎖,就會導致其它爭用鎖 的執行緒在最大等待時間內還是獲取不到鎖,這時爭用執行緒會停止自旋進入阻塞狀態。

Synchronized 同步鎖
synchronized 它可以把任意一個非 NULL 的物件當作鎖。他屬於獨佔式的悲觀鎖,同時屬於可重入鎖。
Synchronized 作用範圍

  1. 作用於方法時,鎖住的是物件的例項(this);
  2. 當作用於靜態方法時,鎖住的是 Class 例項,又因為 Class 的相關資料儲存在永久帶PermGen
    (jdk1.8 則是 metaspace),永久帶是全域性共享的,因此靜態方法鎖相當於類的一個全域性鎖, 會鎖所有呼叫該方法的執行緒;
  3. synchronized 作用於一個物件例項時,鎖住的是所有以該物件為鎖的程式碼塊。它有多個佇列, 當多個執行緒一起訪問某個物件監視器的時候,物件監視器會將這些執行緒儲存在不同的容器中。
  • Synchronized 核心元件:
  1. Wait Set:哪些呼叫 wait 方法被阻塞的執行緒被放置在這裡;
  2. Contention List:競爭佇列,所有請求鎖的執行緒首先被放在這個競爭佇列中;
  3. Entry List:Contention List 中那些有資格成為候選資源的執行緒被移動到 Entry List 中;
  4. OnDeck:任意時刻,最多隻有一個執行緒正在競爭鎖資源,該執行緒被成為 OnDeck;
  5. Owner:當前已經獲取到所資源的執行緒被稱為 Owner;
  6. !Owner:當前釋放鎖的執行緒。

ReentrantLock鎖
ReentantLock 繼承介面 Lock 並實現了介面中定義的方法,他是一種可重入鎖,除了能完成 synchronized 所能完成的所有工作外,還提供了諸如可響應中斷鎖、可輪詢鎖請求、定時鎖等 避免多執行緒死鎖的方法。ReentrantLock 在建構函式中提供了
是否公平鎖的初始化方式,預設為非公平鎖。

ReentrantLock 與 synchronized兩者區別?

兩者的共同點:

  1. 都是用來協調多執行緒對共享物件、變數的訪問
  2. 都是可重入鎖,同一執行緒可以多次獲得同一個鎖
  3. 都保證了可見性和互斥性
  • 兩者的不同點:
  1. ReentrantLock 顯示的獲得、釋放鎖,synchronized 隱式獲得釋放鎖
  2. ReentrantLock 可響應中斷、可輪迴,synchronized 是不可以響應中斷的,為處理鎖的 不可用性提供了更高的靈活性
  3. ReentrantLock 是 API 級別的,synchronized 是 JVM 級別的
  4. ReentrantLock 可以實現公平鎖
  5. ReentrantLock 通過 Condition 可以繫結多個條件
  6. 底層實現不一樣, synchronized 是同步阻塞,使用的是悲觀併發策略,lock 是同步非阻塞,採用的是樂觀併發策略
  7. Lock 是一個介面,而 synchronized 是 Java 中的關鍵字,synchronized 是內建的語言實現。
  8. synchronized 在發生異常時,會自動釋放執行緒佔有的鎖,因此不會導致死鎖現象發生;而 Lock 在發生異常時,如果沒有主動通過 unLock()去釋放鎖,則很可能造成死鎖現象, 因此使用 Lock 時需要在 finally 塊中釋放鎖。
  9. Lock 可以讓等待鎖的執行緒響應中斷,而 synchronized 卻不行,使用 synchronized 時, 等待的執行緒會一直等待下去,不能夠響應中斷。
  10. 通過 Lock 可以知道有沒有成功獲取鎖,而 synchronized 卻無法辦到。
  11. Lock 可以提高多個執行緒進行讀操作的效率,既就是實現讀寫鎖等。

什麼是 CAS?

CAS(Compare And Swap/Set)比較並交換,CAS 演算法的過程是這樣:它包含 3 個引數CAS(V,E,N)。V 表示要更新的變數(記憶體值),E 表示預期值(舊的),N 表示新值。當且僅當 V 值等於 E 值時,才會將 V 的值設為 N,如果 V 值和 E 值不同,則說明已經有其他執行緒做了更新,則當 前執行緒什麼都不做。最後,CAS 返回當前 V 的真實值。
CAS 操作是抱著樂觀的態度進行的(樂觀鎖),它總是認為自己可以成功完成操作。當多個執行緒同時 使用 CAS 操作一個變數時,只有一個會勝出,併成功更新,其餘均會失敗。失敗的執行緒不會被掛 起,僅是被告知失敗,並且允許再次嘗試,當然也允許失敗的執行緒放棄操作。基於這樣的原理,
CAS 操作即使沒有鎖,也可以發現其他執行緒對當前執行緒的干擾,並進行恰當的處理。

  • CAS 會導致“ABA 問題”?

CAS 演算法實現一個重要前提需要取出記憶體中某時刻的資料,而在下時刻比較並替換,那麼在這個時間差類會導致資料的變化。
比如說一個執行緒 one 從記憶體位置 V 中取出 A,這時候另一個執行緒 two 也從記憶體中取出 A,並且 two 進行了一些操作變成了 B,然後 two 又將 V 位置的資料變成 A,這時候執行緒 one 進行 CAS 操 作發現記憶體中仍然是 A,然後 one 操作成功。儘管執行緒 one 的 CAS 操作成功,但是不代表這個過 程就是沒有問題的。
部分樂觀鎖的實現是通過版本號(version)的方式來解決 ABA 問題,樂觀鎖每次在執行資料的修 改操作時,都會帶上一個版本號,一旦版本號和資料的版本號一致就可以執行修改操作並對版本 號執行+1 操作,否則就執行失敗。因為每次操作的版本號都會隨之增加,所以不會出現 ABA 問 題,因為版本號只會增加不會減少。

volatile 關鍵字的作用?

如何在兩個執行緒之間共享資料?

說一下Springbean 生命週期?

  • 例項化
  1. 例項化一個 Bean,也就是我們常說的 new。
  • IOC 依賴注入
  1. 按照 Spring 上下文對例項化的 Bean 進行配置,也就是 IOC 注入。
  • setBeanName 實現
  1. 如果這個 Bean 已經實現了 BeanNameAware 介面,會呼叫它實現的 setBeanName(String)方法,此處傳遞的就是 Spring 配置檔案中 Bean 的 id 值
  • BeanFactoryAware 實現
  1. 如果這個 Bean 已經實現了 BeanFactoryAware 介面,會呼叫它實現的 setBeanFactory,setBeanFactory(BeanFactory)傳遞的是 Spring 工廠自身(可以用這個方式來獲取其它 Bean, 只需在 Spring 配置檔案中配置一個普通的 Bean 就可以)。
  • ApplicationContextAware 實現
  1. 如果這個 Bean 已經實現了 ApplicationContextAware 介面,會呼叫setApplicationContext(ApplicationContext)方法,傳入 Spring 上下文(同樣這個方式也 可以實現步驟 4 的內容,但比 4 更好,因為 ApplicationContext 是 BeanFactory 的子接 口,有更多的實現方法)
  • postProcessBeforeInitialization 介面實現-初始化預處理
  1. 如果這個 Bean 關聯了 BeanPostProcessor 介面,將會呼叫postProcessBeforeInitialization(Object obj, String s)方法,BeanPostProcessor 經常被用 作是 Bean 內容的更改,並且由於這個是在 Bean 初始化結束時呼叫那個的方法,也可以被應 用於記憶體或快取技術。
  • init-method
  1. 如果 Bean 在 Spring 配置檔案中配置了 init-method 屬性會自動呼叫其配置的初始化方法。
  • postProcessAfterInitialization
  1. 如果這個 Bean 關聯了 BeanPostProcessor 介面,將會呼叫postProcessAfterInitialization(Object obj, String s)方法。

注:以上工作完成以後就可以應用這個 Bean 了,那這個 Bean 是一個 Singleton 的,所以一 般情況下我們呼叫同一個 id 的 Bean 會是在內容地址相同的例項,當然在 Spring 配置檔案中 也可以配置非 Singleton。

  • Destroy 過期自動清理階段
  1. 當 Bean 不再需要時,會經過清理階段,如果 Bean 實現了 DisposableBean 這個介面,會呼叫那個其實現的 destroy()方法;
  • destroy-method 自配置清理
  1. 最後,如果這個 Bean 的 Spring 配置中配置了 destroy-method 屬性,會自動呼叫其配置的銷燬方法。

說一下Spring AOP兩種代理方式?

Spring 提供了兩種方式來生成代理物件: JDKProxy 和 Cglib,具體使用哪種方式生成由AopProxyFactory 根據 AdvisedSupport 物件的配置來決定。預設的策略是如果目標類是介面, 則使用 JDK 動態代理技術,否則使用 Cglib 來生成代理。

  1. JDK 動態代理 主要涉及到 java.lang.reflect 包中的兩個類:Proxy 和 InvocationHandler。InvocationHandler 是一個介面,通過實現該介面定義橫切邏輯,並通過反射機制呼叫目標類 的程式碼,動態將橫切邏輯和業務邏輯編制在一起。Proxy 利用InvocationHandler 動態建立 一個符合某一介面的例項,生成目標類的代理物件。
  2. CGLib 動態代理 :CGLib 全稱為 Code Generation Library,是一個強大的高效能,高質量的程式碼生成類庫,可以在執行期擴充套件 Java 類與實現 Java 介面,CGLib 封裝了 asm,可以再執行期動態生成新 的 class。和 JDK 動態代理相比較:JDK 建立代理有一個限制,就是隻能為介面建立代理例項, 而對於沒有通過介面定義業務方法的類,則可以通過 CGLib 建立動態代理。

相關文章