
一、Java 基礎知識
1、Object 類相關方法
- getClass 獲取當前運行時對象的 Class 對象。
- hashCode 返回對象的 hash 碼。
- clone 拷貝當前對象, 必須實現 Cloneable 接口。淺拷貝對基本類型進行值拷貝,對引用類型拷貝引用;深拷貝對基本類型進行值拷貝,對引用類型對象不但拷貝對象的引用還拷貝對象的相關屬性和方法。兩者不同在於深拷貝創建了一個新的對象。
- equals 通過內存地址比較兩個對象是否相等,String 類重寫了這個方法使用值來比較是否相等。
- toString 返回類名@哈希碼的 16 進制。
- notify 喚醒當前對象監視器的任一個線程。
- notifyAll 喚醒當前對象監視器上的所有線程。
- wait 1、暫停線程的執行;2、三個不同參數方法(等待多少毫秒;額外等待多少毫秒;一直等待)3、與
Thread.sleep(long time)相比,sleep 使當前線程休眠一段時間,並沒有釋放該對象的鎖,wait 釋放了鎖。 - finalize 對象被垃圾回收器回收時執行的方法。
2、基本數據類型
- 整型:byte(8)、short(16)、int(32)、long(64)
- 浮點型:float(32)、double(64)
- 布爾型:boolean(8)
- 字符型:char(16)
3、序列化
Java 對象實現序列化要實現 Serializable 接口。
- 反序列化並不會調用構造方法。反序列的對象是由 JVM 自己生成的對象,不通過構造方法生成。
- 序列化對象的引用類型成員變量,也必須是可序列化的,否則,會報錯。
- 如果想讓某個變量不被序列化,使用 transient 修飾。
- 單例類序列化,需要重寫 readResolve() 方法。
4、String、StringBuffer、StringBuilder
- String 由 char[] 數組構成,使用了 final 修飾,是不可變對象,可以理解為常量,線程安全;對 String 進行改變時每次都會新生成一個 String 對象,然后把指針指向新的引用對象。
- StringBuffer 線程安全;StringBuiler 線程不安全。
- 操作少量字符數據用 String;單線程操作大量數據用 StringBuilder;多線程操作大量數據用 StringBuffer。
5、重載與重寫
- 重載 發生在同一個類中,方法名相同,參數的類型、個數、順序不同,方法的返回值和修飾符可以不同。
- 重寫 發生在父子類中,方法名和參數相同,返回值范圍小於等於父類,拋出的異常范圍小於等於父類,訪問修飾符范圍大於等於父類;如果父類方法訪問修飾符為 private 或者 final 則子類就不能重寫該方法。
6、final
- 修飾基本類型變量,一經出初始化后就不能夠對其進行修改。
- 修飾引用類型變量,不能夠指向另一個引用。
- 修飾類或方法,不能被繼承或重寫。
7、反射
- 在運行時動態的獲取類的完整信息
- 增加程序的靈活性
- JDK 動態代理使用了反射
8、JDK 動態代理
- 使用步驟
- 創建接口及實現類
- 實現代理處理器:實現 InvokationHandler ,實現 invoke(Proxy proxy,Method method,Object[] args) 方法
- 通過 Proxy.newProxyInstance(ClassLoaderloader, Class[] interfaces, InvocationHandler h) 獲得代理類
- 通過代理類調用方法。
9、Java IO
- 普通 IO ,面向流,同步阻塞線程。
- NIO,面向緩沖區,同步非阻塞。
二、Java 集合框架
1、List(線性結構)
- ArrayList Object[] 數組實現,默認大小為 10 ,支持隨機訪問,連續內存空間,插入末尾時間復雜度 o(1),插入第 i 個位置時間復雜度 o(n - i)。擴容,大小變為 1.5 倍,Arrays.copyOf(底層 System.ArrayCopy),復制到新數組,指針指向新數組。
- Vector 類似 ArrayList,線程安全,擴容默認增長為原來的 2 倍,還可以指定增長空間長度。
- LinkedList 基於鏈表實現,1.7 為雙向鏈表,1.6 為雙向循環鏈表,取消循環更能分清頭尾。
2、Map(K,V 對)
- HashMap
- 底層數據結構,JDK 1.8 是數組 + 鏈表 + 紅黑樹,JDK 1.7 無紅黑樹。鏈表長度大於 8 時,轉化為紅黑樹,優化查詢效率。
- 初始容量為 16,通過 tableSizeFor 保證容量為 2 的冪次方。尋址方式,高位異或,(n-1)&h 取模,優化速度。
- 擴容機制,當元素數量大於容量 x 負載因子 0.75 時,容量擴大為原來的 2 倍,新建一個數組,然后轉移到新數組。
- 基於 Map 實現。
- 線程不安全。
- HashMap (1.7) 多線程循環鏈表問題
- 在多線程環境下,進行擴容時,1.7 下的 HashMap 會形成循環鏈表。
- 怎么形成循環鏈表: 假設有一 HashMap 容量為 2 , 在數組下標 1 位置以 A -> B 鏈表形式存儲。有一線程對該 map 做 put 操作,由於觸發擴容條件,需要進行擴容。這時另一個線程也 put 操作,同樣需要擴容,並完成了擴容操作,由於復制到新數組是頭部插入,所以 1 位置變為 B -> A 。這時第一個線程繼續做擴容操作,首先復制 A ,然后復制 B ,再判斷 B.next 是否為空時,由於第二個線程做了擴容操作,導致 B.next = A,所以在將 A 放到 B 前,A.next 又等於 B ,導致循環鏈表出現。
- HashTable
- 線程安全,方法基本全用 Synchronized 修飾。
- 初始容量為 11 ,擴容為 2n + 1 。
- 繼承 Dictionary 類。
- ConcurrentHashMap
- 線程安全的 HashMap。
- 1.7 采用分段鎖的形式加鎖;1.8 使用 Synchronized 和 CAS 實現同步,若數組的 Node 為空,則通過 CAS 的方式設置值,不為空則加在鏈表的第一個節點。獲取第一個元素是否為空使用 Unsafe 類提供的 getObjectVolatile 保證可見性。
- 對於讀操作,數組由 volatile 修飾,同時數組的元素為 Node,Node 的 K 使用 final 修飾,V 使用 volatile 修飾,下一個節點也用 volatile 修飾,保證多線程的可見性。
- LinkedHashMap LinkedHashMap 繼承自 HashMap,所以它的底層仍然是基於拉鏈式散列結構即由數組和鏈表或紅黑樹組成。另外,LinkedHashMap 在上面結構的基礎上,增加了一條雙向鏈表,使得上面的結構可以保持鍵值對的插入順序。
- TreeMap 有序的 Map,紅黑樹結構,可以自定義比較器來進行排序。
- Collections.synchronizedMap 如何實現 Map 線程安全? 基於 Synchronized ,實際上就是鎖住了當前傳入的 Map 對象。
3、Set(唯一值)
- HashSet 基於 HashMap 實現,使用了 HashMap 的 K 作為元素存儲,V 為 new Object() ,在 add() 方法中如果兩個元素的 Hash 值相同,則通過 equals 方法比較是否相等。
- LinkedHashSet LinkedHashSet 繼承於 HashSet,並且其內部是通過 LinkedHashMap 來實現的。
- TreeSet 紅黑樹實現有序唯一。
三、Java 多線程
1、synchronized
- 修飾代碼塊 底層實現,通過 monitorenter & monitorexit 標志代碼塊為同步代碼塊。
- 修飾方法 底層實現,通過 ACC_SYNCHRONIZED 標志方法是同步方法。
- 修飾類 class 對象時,實際鎖在類的實例上面。
- 單例模式
public class Singleton {
private static volatile Singleton instance = null;
private Singleton(){}
public static Singleton getInstance(){
if (null == instance) {
synchronized (Singleton.class) {
if (null == instance) {
instance = new Singleton();
}
}
}
return instance;
}
}
- 偏向鎖,自旋鎖,輕量級鎖,重量級鎖
- 通過 synchronized 加鎖,第一個線程獲取的鎖為偏向鎖,這時有其他線程參與鎖競爭,升級為輕量級鎖,其他線程通過循環的方式嘗試獲得鎖,稱自旋鎖。若果自旋的次數達到一定的閾值,則升級為重量級鎖。
- 需要注意的是,在第二個線程獲取鎖時,會先判斷第一個線程是否仍然存活,如果不存活,不會升級為輕量級鎖。
2、Lock
- ReentrantLock
- 基於 AQS (AbstractQueuedSynchronizer)實現,主要有 state (資源) + FIFO (線程等待隊列) 組成。
- 公平鎖與非公平鎖:區別在於在獲取鎖時,公平鎖會判斷當前隊列是否有正在等待的線程,如果有則進行排隊。
- 使用 lock() 和 unLock() 方法來加鎖解鎖。
- ReentrantReadWriteLock
- 同樣基於 AQS 實現,內部采用內部類的形式實現了讀鎖(共享鎖)和寫鎖 (排它鎖)。
- 非公平鎖吞吐量高 在獲取鎖的階段來分析,當某一線程要獲取鎖時,非公平鎖可以直接嘗試獲取鎖,而不是判斷當前隊列中是否有線程在等待。一定情況下可以避免線程頻繁的上下文切換,這樣,活躍的線程有可能獲得鎖,而在隊列中的鎖還要進行喚醒才能繼續嘗試獲取鎖,而且線程的執行順序一般來說不影響程序的運行。
3、volatile
- Java 內存模型

- 在多線程環境下,保證變量的可見性。使用了 volatile 修飾變量后,在變量修改后會立即同步到主存中,每次用這個變量前會從主存刷新。
- 禁止 JVM 指令重排序。
- 單例模式雙重校驗鎖變量為什么使用 volatile 修飾? 禁止 JVM 指令重排序,new Object()分為三個步驟:申請內存空間,將內存空間引用賦值給變量,變量初始化。如果不禁止重排序,有可能得到一個未經初始化的變量。
4、線程的五種狀態
1). New
一個新的線程被創建,還沒開始運行。
2). Runnable
一個線程准備就緒,隨時可以運行的時候就進入了 Runnable 狀態。
Runnable 狀態可以是實際正在運行的線程,也可以是隨時可以運行的線程。
多線程環境下,每個線程都會被分配一個固定長度的 CPU 計算時間,每個線程運行一會兒就會停止讓其他線程運行,這樣才能讓每個線程公平的運行。這些等待 CPU 和正在運行的線程就處於 Runnable 狀態。
3). Blocked
例如一個線程在等待 I/O 資源,或者它要訪問的被保護代碼已經被其他線程鎖住了,那么它就在阻塞 Blocked 狀態,這個線程所需的資源到位后就轉入 Runnable 狀態。
4). Waiting(無限期等待)
如果一個線程在等待其他線程的喚醒,那么它就處於 Waiting 狀態。以下方法會讓線程進入等待狀態:
- Object.wait()
- Thread.join()
- LockSupport.park()
5). Timed Waiting(有期限等待)
無需等待被其他線程顯示喚醒,在一定時間后有系統自動喚醒。
以下方法會讓線程進入有限等待狀態:
- Thread.sleep(sleeptime)
- Object.wait(timeout)
- Thread.join(timeout)
- LockSupport.parkNanos(timeout)
- LockSupport.parkUntil(timeout)
6). Terminated
一個線程正常執行完畢,或者意外失敗,那么就結束了。
5、 wait() 與 sleep()
- 調用后線程進入 waiting 狀態。
- wait() 釋放鎖,sleep() 沒有釋放鎖。
- 調用 wait() 后需要調用 notify() 或 notifyAll() 方法喚醒線程。
- wait() 方法聲明在 Object 中,sleep() 方法聲明在 Thread 中。
6、 yield()
- 調用后線程進入 runnable 狀態。
- 讓出 CPU 時間片,之后有可能其他線程獲得執行權,也有可能這個線程繼續執行。
7、 join()
- 在線程 B 中調用了線程 A 的 Join()方法,直到線程 A 執行完畢后,才會繼續執行線程 B。
- 可以保證線程的順序執行。
- join() 方法必須在 線程啟動后調用才有意義。
- 使用 wait() 方法實現。
9、線程使用方式
- 繼承 Tread 類
- 實現 Runnable 接口
- 實現 Callable 接口:帶有返回值
10、Runnable 和 Callable 比較
- 方法簽名不同,
void Runnable.run(),V Callable.call() throws Exception - 是否允許有返回值,
Callable允許有返回值 - 是否允許拋出異常,
Callable允許拋出異常。 - 提交任務方式,
Callable使用Future<T> submit(Callable<T> task)返回 Future 對象,調用其 get() 方法可以獲得返回值,Runnable使用void execute(Runnable command)。
11、hapens-before
如果一個操作 happens-before 另一個操作,那么第一個操作的執行結果將對第二個操作可見,而且第一個操作的執行順序排在第二個操作之前。
12、ThreadLocal
- 場景 主要用途是為了保持線程自身對象和避免參數傳遞,主要適用場景是按線程多實例(每個線程對應一個實例)的對象的訪問,並且這個對象很多地方都要用到。
- 原理 為每個線程創建變量副本,不同線程之間不可見,保證線程安全。使用 ThreadLocalMap 存儲變量副本,以 ThreadLocal 為 K,這樣一個線程可以擁有多個 ThreadLocal 對象。
- 實際 使用多數據源時,需要根據數據源的名字切換數據源,假設一個線程設置了一個數據源,這個時候就有可能有另一個線程去修改數據源,可以使用 ThreadLocal 維護這個數據源名字,使每個線程持有數據源名字的副本,避免線程安全問題。
8、線程池
1)、分類
- FixThreadPool 固定數量的線程池,適用於對線程管理,高負載的系統
- SingleThreadPool 只有一個線程的線程池,適用於保證任務順序執行
- CacheThreadPool 創建一個不限制線程數量的線程池,適用於執行短期異步任務的小程序,低負載系統
- ScheduledThreadPool 定時任務使用的線程池,適用於定時任務
2)、線程池的幾個重要參數
- int corePoolSize, 核心線程數
- int maximumPoolSize, 最大線程數
- long keepAliveTime, TimeUnit unit, 超過 corePoolSize 的線程的存活時長,超過這個時間,多余的線程會被回收。
- BlockingQueue workQueue, 任務的排隊隊列
- ThreadFactory threadFactory, 新線程的產生方式
- RejectedExecutionHandler handler) 拒絕策略
3)、線程池線程工作過程
corePoolSize -> 任務隊列 -> maximumPoolSize -> 拒絕策略
核心線程在線程池中一直存活,當有任務需要執行時,直接使用核心線程執行任務。當任務數量大於核心線程數時,加入等待隊列。當任務隊列數量達到隊列最大長度時,繼續創建線程,最多達到最大線程數。當設置回收時間時,核心線程以外的空閑線程會被回收。如果達到了最大線程數還不能夠滿足任務執行需求,則根據拒絕策略做拒絕處理。
4)、線程池拒絕策略(默認拋出異常)
|:---|:---| | AbortPolicy | 拋出 RejectedExecutionException | | DiscardPolicy | 什么也不做,直接忽略 | | DiscardOldestPolicy | 丟棄執行隊列中最老的任務,嘗試為當前提交的任務騰出位置 | | CallerRunsPolicy | 直接由提交任務者執行這個任務 |
5)、如何根據 CPU 核心數設計線程池線程數量
- IO 密集型 2nCPU
- 計算密集型 nCPU+1
- 其中 n 為 CPU 核心數量,可通過
Runtime.getRuntime().availableProcessors()獲得核心數:。 - 為什么加 1:即使當計算密集型的線程偶爾由於缺失故障或者其他原因而暫停時,這個額外的線程也能確保 CPU 的時鍾周期不會被浪費。
- 其中 n 為 CPU 核心數量,可通過
四、Java 虛擬機
1、Java 內存結構

- 堆 由線程共享,存放 new 出來的對象,是垃圾回收器的主要工作區域。
- 棧 線程私有,分為 Java 虛擬機棧和本地方法棧,存放局部變量表、操作棧、動態鏈接、方法出口等信息,方法的執行對應着入棧到出棧的過程。
- 方法區 線程共享,存放已被加載的類信息、常量、靜態變量、即時編譯器編譯后的代碼等信息,JDK 1.8 中方法區被元空間取代,使用直接內存。
2、Java 類加載機制

- 加載 加載字節碼文件。
- 鏈接
- 驗證 驗證字節碼文件的正確性。
- 准備 為靜態變量分配內存。
- 解析 將符號引用(如類的全限定名)解析為直接引用(類在實際內存中的地址)。
- 初始化 為靜態變量賦初值。
雙親委派模式
當一個類需要加載時,判斷當前類是否被加載過。已經被加載的類會直接返回,否則才會嘗試加載。加載的時候,首先會把該請求委派該父類加載器的
loadClass()處理,因此所有的請求最終都應該傳送到頂層的啟動類加載器BootstrapClassLoader中。當父類加載器無法處理時,才由自己來處理。當父類加載器為 null 時,會使用啟動類加載器BootstrapClassLoader作為父類加載器。
3、垃圾回收算法
- Mark-Sweep(標記-清除)算法 標記需要回收的對象,然后清除,會造成許多內存碎片。
- Copying(復制)算法 將內存分為兩塊,只使用一塊,進行垃圾回收時,先將存活的對象復制到另一塊區域,然后清空之前的區域。
- Mark-Compact(標記-整理)算法(壓縮法) 與標記清除算法類似,但是在標記之后,將存活對象向一端移動,然后清除邊界外的垃圾對象。
- Generational Collection(分代收集)算法 分為年輕代和老年代,年輕代時比較活躍的對象,使用復制算法做垃圾回收。老年代每次回收只回收少量對象,使用標記整理法。
4、典型垃圾回收器
-
CMS
- 簡介 以獲取最短回收停頓時間為目標的收集器,它是一種並發收集器,采用的是 Mark-Sweep 算法。
- 場景 如果你的應用需要更快的響應,不希望有長時間的停頓,同時你的 CPU 資源也比較豐富,就適合適用 CMS 收集器。
- 垃圾回收步驟
- 初始標記 (Stop the World 事件 CPU 停頓, 很短) 初始標記僅標記一下 GC Roots 能直接關聯到的對象,速度很快;
- 並發標記 (收集垃圾跟用戶線程一起執行) 並發標記過程就是進行 GC Roots 查找的過程;
- 重新標記 (Stop the World 事件 CPU 停頓,比初始標記稍微長,遠比並發標記短) 修正由於並發標記時應用運行產生變化的標記。
- 並發清理,標記清除算法;
- 缺點
- 並發標記時和應用程序同時進行,占用一部分線程,所以吞吐量有所下降。
- 並發清除時和應用程序同時進行,這段時間產生的垃圾就要等下一次 GC 再清除。
- 采用的標記清除算法,產生內存碎片,如果要新建大對象,會提前觸發 Full GC 。
-
G1
- 簡介 是一款面向服務端應用的收集器,它能充分利用多 CPU、多核環境。因此它是一款並行與並發收集器,並且它能建立可預測的停頓時間模型,即可以設置 STW 的時間。
- 垃圾回收步驟 1、初始標記(stop the world 事件 CPU 停頓只處理垃圾); 2、並發標記(與用戶線程並發執行); 3、最終標記(stop the world 事件 ,CPU 停頓處理垃圾); 4、篩選回收(stop the world 事件 根據用戶期望的 GC 停頓時間回收)
- 特點
- 並發與並行 充分利用多核 CPU ,使用多核來縮短 STW 時間,部分需要停頓應用線程的操作,仍然可以通過並發保證應用程序的執行。
- 分代回收 新生代,幸存帶,老年代
- 空間整合 總體看是采用標記整理算法回收,每個 Region 大小相等,通過復制來回收。
- 可預測的停頓時間 使用 -XX:MaxGCPauseMillis=200 設置最長目標暫停值。
在 Java 語言中,可作為 GC Roots 的對象包括 4 種情況:
a) 虛擬機棧中引用的對象(棧幀中的本地變量表); b) 方法區中類靜態屬性引用的對象; c) 方法區中常量引用的對象; d) 本地方法棧中 Native 方法引用的對象。
五、MySQL (Inno DB)
1、聚簇索引與非聚簇索引

- 都使用 B+ 樹作為數據結構
- 聚簇索引中數據存在主鍵索引的葉子結點中,得到 key 即得到 data ;非聚簇索引的數據存在單獨的空間。
- 聚簇索引中輔助索引的葉子結點存的是主鍵;非聚簇索引中葉子結點存的是數據的地址;
- 聚簇索引的優勢是找到主鍵就找到數據,只需一次磁盤 IO ;當 B+ 樹的結點發生變化時,地址也會發生變化,這時非聚簇索引需要更新所有的地址,增加開銷。
2、為何使用 B 樹做索引而不是紅黑樹?
索引很大,通常作為文件存儲在磁盤上面,每次檢索索引都需要把索引文件加載進內存,所以磁盤 IO 的次數是衡量索引數據結構好壞的重要指標。應用程序在從磁盤讀取數據時,不只是讀取需要的數據,還會連同其他數據以頁的形式做預讀來減少磁盤 IO 的次數。數據庫的設計者將每個節點的大小設置為一頁的大小,同時每次新建節點時都重新申請一個頁,這樣檢索一個節點只需要一次 IO,根據索引定位到數據只需要 h- 1(h 為 B 樹高度,根節點常駐內存) 次 IO,而 d (度,可以理解為寬度)與 h 稱反比,即 d 越大,高度就越小,所以樹越扁,磁盤 IO 次數越少,即漸進復雜度為 logdN ,這也是為什么不選擇紅黑樹做索引的原因。前面可以得出結論,d 越大,索引的性能越好。節點由 key 和 data 組成,頁的大小一定,key 和 data 越小,d 越大。B + 樹去掉了節點內的 data 域,所以有更大的 d , 性能更好。
3、最左前綴原則
在 MySQL 中,可以指定多個列為索引,即聯合索引。比如 index(name,age) ,最左前綴原則是指查詢時精確匹配到從最左邊開始的一列或幾列(name;name&age),就可以命中索引。如果所有列都用到了,順序不同,查詢引擎會自動優化為匹配聯合索引的順序,這樣是能夠命中索引的。
4、什么情況下可以用到 B 樹索引
(1) 定義有主鍵的列一定要建立索引。因為主鍵可以加速定位到表中的某行
(2) 定義有外鍵的列一定要建立索引。外鍵列通常用於表與表之間的連接,在其上創建索引可以加快表間的連接
(3) 對於經常查詢的數據列最好建立索引。
① 對於需要在指定范圍內快速或頻繁查詢的數據列,因為索引已經排序,其指定的范圍是連續的,查詢可以利用索引的排序,加快查詢的時間
② 經常用在 where 子句中的數據列,將索引建立在 where 子句的集合過程中,對於需要加速或頻繁檢索的數據列,可以讓這些經常參與查詢的數據列按照索引的排序進行查詢,加快查詢的時間。
5、事務隔離級別
- Read uncommitted 讀未提交,可能出現臟讀,不可重復讀,幻讀。
- Read committed 讀提交,可能出現不可重復讀,幻讀。
- Repeatable read 可重復讀,可能出現臟讀。
- Serializable 可串行化,同一數據讀寫都加鎖,避免臟讀,性能不忍直視。
Inno DB 默認隔離級別為可重復讀級別,分為快照度和當前讀,並且通過行鎖和間隙鎖解決了幻讀問題。
6、MVCC (多版本並發控制)
- 實現細節
- 每行數據都存在一個版本,每次數據更新時都更新該版本。
- 修改時 Copy 出當前版本隨意修改,各個事務之間互不干擾。
- 保存時比較版本號,如果成功(commit),則覆蓋原記錄;失敗則放棄 copy(rollback)。
- Inno DB 實現
在 InnoDB 中為每行增加兩個隱藏的字段,分別是該行數據創建時的版本號和刪除時的版本號,這里的版本號是系統版本號(可以簡單理解為事務的 ID),每開始一個新的事務,系統版本號就自動遞增,作為事務的 ID 。通常這兩個版本號分別叫做創建時間和刪除時間。
詳細參考:《 臟讀、幻讀和不可重復讀》
六、Spring 相關
1、Bean 的作用域
|:---|:---| | 類別 | 說明 | |singleton| 默認在 Spring 容器中僅存在一個實例 | |prototype| 每次調用 getBean() 都重新生成一個實例 | |request| 為每個 HTTP 請求生成一個實例 | |session| 同一個 HTTP session 使用一個實例,不同 session 使用不同實例 |
2、Bean 生命周期
簡單來說四步:
-
- 實例化 Instantiation
-
- 屬性賦值 Populate
-
- 初始化 Initialization
-
- 銷毀 Destruction
在這四步的基礎上面,Spring 提供了一些拓展點:
- Bean 自身的方法: 這個包括了 Bean 本身調用的方法和通過配置文件中 %3Cbean %3E 的 init-method 和 destroy-method 指定的方法
- Bean 級生命周期接口方法: 這個包括了 BeanNameAware、BeanFactoryAware、InitializingBean 和 DiposableBean 這些接口的方法
- 容器級生命周期接口方法:這個包括了 InstantiationAwareBeanPostProcessor 和 BeanPostProcessor 這兩個接口實現,一般稱它們的實現類為“后處理器”。
- 工廠后處理器接口方法: 這個包括了 AspectJWeavingEnabler, ConfigurationClassPostProcessor, CustomAutowireConfigurer 等等非常有用的工廠后處理器接口的方法。工廠后處理器也是容器級的。在應用上下文裝配配置文件之后立即調用。
3、Spring AOP
實現方式兩種:
- JDK 動態代理:帶有接口的對象,在運行期實現
- CGlib 靜態代理:在編譯期實現。
4、Spring 事務傳播行為
默認 PROPAGATION_REQUIRED ,如果存在一個事務,則支持當前事務。如果沒有事務則開啟一個新的事務。

5、Spring IoC

6、Spring MVC 工作流程

七、計算機網絡
1、TCP/IP 五層模型

2、瀏覽器輸入地址后做了什么?

3、三次握手與四次揮手
- 三次握手

- 四次揮手

4、TIME_WAIT 與 CLOSE_WAIT

5、TCP 滑動窗口
TCP 流量控制,主要使用滑動窗口協議,滑動窗口是接受數據端使用的窗口大小,用來告訴發送端接收端的緩存大小,以此可以控制發送端發送數據的大小,從而達到流量控制的目的。這個窗口大小就是我們一次傳輸幾個數據。對所有數據幀按順序賦予編號,發送方在發送過程中始終保持着一個發送窗口,只有落在發送窗口內的幀才允許被發送;同時接收方也維持着一個接收窗口,只有落在接收窗口內的幀才允許接收。
6、TCP 粘包和拆包
- 現象

- 產生原因 1、要發送的數據大於 TCP 發送緩沖區剩余空間大小,將會發生拆包。 2、待發送數據大於 MSS(最大報文長度),TCP 在傳輸前將進行拆包。 3、要發送的數據小於 TCP 發送緩沖區的大小,TCP 將多次寫入緩沖區的數據一次發送出去,將會發生粘包。 4、接收數據端的應用層沒有及時讀取接收緩沖區中的數據,將發生粘包。
- 解決方式 1、發送端給每個數據包添加包首部,首部中應該至少包含數據包的長度,這樣接收端在接收到數據后,通過讀取包首部的長度字段,便知道每一個數據包的實際長度了。 2、發送端將每個數據包封裝為固定長度(不夠的可以通過補 0 填充),這樣接收端每次從接收緩沖區中讀取固定長度的數據就自然而然的把每個數據包拆分開來。 3、可以在數據包之間設置邊界,如添加特殊符號,這樣,接收端通過這個邊界就可以將不同的數據包拆分開。
八、MQ 消息隊列
1、場景作用
削峰填谷,異步解耦。
2、如何保證消息不被重復消費呢?
這個問題可以換個思路,保證消息重復消費,其實是保證程序的冪等性。無論消息如何重復,程序運行的結果是一致的。比如消費消息后做數據庫插入操作,為了防止消息重復消費,可以在插入前先查詢一下有沒有對應的數據。
3、怎么保證從消息隊列里拿到的數據按順序執行?
消費端在接收到消息后放入內存隊列,然后對隊列中的消息進行有序消費。
4、如何解決消息隊列的延時以及過期失效問題?消息隊列滿了以后該怎么處理?有幾百萬消息持續積壓幾小時,說說怎么解決?
消息過期失效問題,如果消息一段時間不消費,導致過期失效了,消息就丟失了,只能重新查出丟失的消息,重新發送。 再來說消息積壓的問題:(思路是快速消費掉積壓的消息)
- 首先排查消費端問題,恢復消費端正常消費速度。
- 然后着手處理隊列中的積壓消息。
- 停掉現有的 consumer。
- 新建一個 topic ,設置之前 10 倍的 partation,之前 10 倍的隊列。
- 寫一個分發程序,將積壓的消息均勻的輪詢寫入這些隊列。
- 然后臨時用 10 倍的機器部署 consumer,每一批 consumer 消費 1 個臨時的隊列。
- 消費完畢后,恢復原有架構。
消息隊列滿了:只能邊接收邊丟棄,然后重新補回丟失的消息,再做消費。
4、如何保證消息的可靠性傳輸(如何處理消息丟失的問題)?
kafka 為例:
- 消費者丟了數據: 每次消息消費后,由自動提交 offset 改為手動提交 offset 。
- kafka 丟了消息: 比較常見的一個場景,就是 kafka 某個 broker 宕機,然后重新選舉 partition 的 leader 時。要是此時其他的 follower 剛好還有些數據沒有同步,結果此時 leader 掛了,然后大家選舉某個 follower 成為 leader 之后,不就少了一些數據。
- 給 topic 設置replication.factor參數:這個值必須大於 1,要求每個 partition 必須有至少兩個副本。
- 在 kafka 服務端設置min.insync.replicas參數:這個值必須大於 1,這個是要求一個 leader 至少感知到有至少一個 follower 還跟自己保持聯系,沒掉隊,這樣才能確保 leader 掛了還有一個 follower。
- 在 producer 端設置acks=all:這個是要求每條數據,必須是寫入所有 replica 之后,才能認為是寫成功了。
- 在 producer 端設置retries=MAX(很大很大很大的一個值,無限次重試的意思):這個是要求一旦寫入失敗,就無限重試,卡在這里。
- 生產者丟了消息: 如果按照上述的思路設置了 ack=all,一定不會丟,要求是,你的 leader 接收到消息,所有的 follower 都同步到了消息之后,才認為本次寫成功了。如果沒滿足這個條件,生產者會自動不斷的重試,重試無限次。
九、Redis
1、數據類型
- String
常用命令: set,get,decr,incr,mget 等。
- Hash
常用命令: hget,hset,hgetall 等
- List
常用命令: lpush,rpush,lpop,rpop,lrange 等
可以通過 lrange 命令,就是從某個元素開始讀取多少個元素,可以基於 list 實現分頁查詢。
- Set
常用命令: sadd,spop,smembers,sunion 等
- Sort Set
常用命令: zadd,zrange,zrem,zcard 等
2、Redis 如何實現 key 的過期刪除?
定期刪除和惰性刪除的形式。
- 定期刪除 Redis 每隔一段時間從設置過期時間的 key 集合中,隨機抽取一些 key ,檢查是否過期,如果已經過期做刪除處理。
- 惰性刪除 Redis 在 key 被訪問的時候檢查 key 是否過期,如果過期則刪除。
3、Redis 的持久化機制
數據快照(RDB)+ 修改數據語句文件(AOF)
4、如何解決 Redis 緩存雪崩和緩存穿透?
-
緩存雪崩 緩存同一時間大面積的失效,所以,后面的請求都會落到數據庫上,造成數據庫短時間內承受大量請求而崩掉。
- 解決方式
- 事前:保證 Redis 集群的穩定性,發現機器宕機盡快補上,設置合適的內存淘汰策略。
- 事中:本地緩存 + 限流降級,避免大量請求落在數據庫上。
- 事后:利用 Redis 持久化機制盡快恢復緩存。
- 解決方式
-
緩存穿透 一般是黑客故意去請求緩存中不存在的數據,導致所有的請求都落到數據庫上,造成數據庫短時間內承受大量請求而崩掉。
- 解決方式 將不存在的數據列舉到一個足夠大的 map 上,這樣遭到攻擊時,直接攔截 map 中的請求,請求到數據庫上面。或是把不存在的也做緩存,值為 null ,設置過期時間。
5、如何使用 Redis 實現消息隊列?
Redis 實現消息隊列依賴於 Redis 集群的穩定性,通常不建議使用。
- Redis 自帶發布訂閱功能,基於 publish 和 subscribe 命令。
- 使用 List 存儲消息,lpush,rpop 分別發送接收消息。
十、Nginx
Nginx 是一款輕量級的 Web 服務器/反向代理服務器及電子郵件(IMAP/POP3)代理服務器。 Nginx 主要提供反向代 理、負載均衡、動靜分離(靜態資源服務)等服務。
1、正向代理和反向代理
- 正向代理 代理客戶端訪問服務器。典型:VPN
- 反向代理 代替服務器接收客戶端請求,然后轉發給服務器,服務器接收請求並將處理的結果通過代理服務器轉發給客戶端。
2、負載均衡
將請求分攤到多台機器上去,高並發,增加吞吐量。
- 負載均衡算法
- 權重輪詢
- fair
- ip_hash
- url_hash
3、動靜分離
動靜分離是讓動態網站里的動態網頁根據一定規則把不變的資源和經常變的資源區分開來,動靜資源做好了拆分以后,我們就可以根據靜態資源的特點將其做緩存操作,這就是網站靜態化處理的核心思路。
4、Nginx 四個組成部分
- Nginx 二進制可執行文件:由各模塊源碼編譯出一個文件
- Nginx.conf 配置文件:控制 Nginx 行為
- acess.log 訪問日志: 記錄每一條 HTTP 請求信息
- error.log 錯誤日志:定位問題
