爲了面個好公司!拼了!3.5W字的Java面試題整理(答案+學習路線)上!

前言:

本文是在網上看了各種面試指南收集的題目及答案。無意冒犯各位原創作者,如果在您的博客或者寫作平臺有相似問題答案可以跟我說,我給您鏈接加上,我只是爲了方便以後自己需要的時候刷一刷,不用在到處找題。

  • Spring MVC(20題)(Spring全家桶學習路線圖譜)
  • JVM(30題)(學習路線圖譜)
  • Mybatis(25題)
  • Redis(29題)(含學習圖譜)
  • 併發編程(15題)
  • HashMap(21題)

如果你覺得我整理的這些東西對你有幫助的話,
記得點贊+評論+關注哈!感謝支持!
在這裏插入圖片描述
面試資料,學習路線,思維導圖,java知識點戳→:面試自取

SpringMVC(20題)(Spring全家桶學習路線圖譜)

1、SpringMVC工作原理?

1)客戶端發送請求到DispatcherServlet

2)DispatcherServlet查詢handlerMapping找到處理請求的Controller

3)Controller調用業務邏輯後,返回ModelAndView

4)DispatcherServlet查詢ModelAndView,找到指定視圖

5)視圖將結果返回到客戶端

2、SpringMVC流程?

1)用戶發送請求至前端控制器DispatcherServlet。

2)DispatcherServlet收到請求調用HandlerMapping處理器映射器。

3)處理器映射器找到具體的處理器(可以根據xml配置、註解進行查找),生成處理器對象及處理器攔截器(如果有則生成)一併返回給DispatcherServlet。

4)DispatcherServlet調用HandlerAdapter處理器適配器。

5)HandlerAdapter經過適配調用具體的處理器(Controller,也叫後端控制器)。

6)Controller執行完成返回ModelAndView。

7)HandlerAdapter將controller執行結果ModelAndView返回給DispatcherServlet。

8)DispatcherServlet將ModelAndView傳給ViewReslover視圖解析器。

9)ViewReslover解析後返回具體View。

10)DispatcherServlet根據View進行渲染視圖(即將模型數據填充至視圖中)。

11)DispatcherServlet響應用戶。

在這裏插入圖片描述

3、SpringMvc的控制器是不是單例模式,如果是,有什麼問題,怎麼解決?

答:是單例模式,所以在多線程訪問的時候有線程安全問題,不要用同步,會影響性能的,解決方案是在控制器裏面不能寫字段。

4、如果你也用過struts2.簡單介紹下springMVC和struts2的區別有哪些?

答:

1)springmvc的入口是一個servlet即前端控制器,而struts2入口是一個filter過慮器。

2)springmvc是基於方法開發(一個url對應一個方法),請求參數傳遞到方法的形參,可以設計爲單例或多例(建議單例),struts2是基於類開發,傳遞參數是通過類的屬性,只能設計爲多例。

3)Struts採用值棧存儲請求和響應的數據,通過OGNL存取數據,springmvc通過參數解析器是將request請求內容解析,並給方法形參賦值,將數據和視圖封裝成ModelAndView對象,最後又將ModelAndView中的模型數據通過reques域傳輸到頁面。Jsp視圖解析器默認使用jstl。

5、SpingMvc中的控制器的註解一般用那個,有沒有別的註解可以替代?

答:一般用@Conntroller註解,表示是表現層,不能用用別的註解代替。

6、 @RequestMapping註解用在類上面有什麼作用?

答:是一個用來處理請求地址映射的註解,可用於類或方法上。用於類上,表示類中的所有響應請求的方法都是以該地址作爲父路徑。

7、怎麼樣把某個請求映射到特定的方法上面?

答:直接在方法上面加上註解@RequestMapping,並且在這個註解裏面寫上要攔截的路徑

8、如果在攔截請求中,我想攔截get方式提交的方法,怎麼配置?

答:可以在@RequestMapping註解裏面加上method=RequestMethod.GET

9、怎麼樣在方法裏面得到Request,或者Session?

答:直接在方法的形參中聲明request,SpringMvc就自動把request對象傳入

10、我想在攔截的方法裏面得到從前臺傳入的參數,怎麼得到?

答:直接在形參裏面聲明這個參數就可以,但必須名字和傳過來的參數一樣

11、如果前臺有很多個參數傳入,並且這些參數都是一個對象的,那麼怎麼樣快速得到這個對象?

答:直接在方法中聲明這個對象,SpringMvc就自動會把屬性賦值到這個對象裏面。

12、SpringMvc中函數的返回值是什麼?

答:返回值可以有很多類型,有String, ModelAndView,當一般用String比較好。

13、SpringMVC怎麼樣設定重定向和轉發的?

答:在返回值前面加"forward:“就可以讓結果轉發,譬如"forward:user.do?name=method4” 在返回值前面加"redirect:“就可以讓返回值重定向,譬如"redirect:http://www.baidu.com”

14、SpringMvc用什麼對象從後臺向前臺傳遞數據的?

答:通過ModelMap對象,可以在這個對象裏面用put方法,把對象加到裏面,前臺就可以通過el表達式拿到。

15、SpringMvc中有個類把視圖和數據都合併的一起的,叫什麼?

答:叫ModelAndView。

16、怎麼樣把ModelMap裏面的數據放入Session裏面?

答:可以在類上面加上@SessionAttributes註解,裏面包含的字符串就是要放入session裏面的key

17、SpringMvc怎麼和AJAX相互調用的?

答:通過Jackson框架就可以把Java裏面的對象直接轉化成Js可以識別的Json對象。

具體步驟如下 :

 1)加入Jackson.jar 

 2)在配置文件中配置json的映射 

 3)在接受Ajax方法裏面可以直接返回Object,List等,但方法前面要加上@ResponseBody註解

18、當一個方法向AJAX返回特殊對象,譬如Object,List等,需要做什麼處理?

答:要加上@ResponseBody註解

19、SpringMvc裏面攔截器是怎麼寫的

答:有兩種寫法,一種是實現接口,另外一種是繼承適配器類,然後在SpringMvc的配置文件中配置攔截器即可:

<!-- 配置SpringMvc的攔截器 -->
<mvc:interceptors>    
    <!-- 配置一個攔截器的Bean就可以了 默認是對所有請求都攔截 -->   
    <bean id="myInterceptor" class="com.et.action.MyHandlerInterceptor"></bean>    
    <!-- 只針對部分請求攔截 -->    
    <mvc:interceptor>       
        <mvc:mapping path="/modelMap.do" />       
        <bean class="com.et.action.MyHandlerInterceptorAdapter" />  
    </mvc:interceptor>
</mvc:interceptors>

20、講下SpringMvc的執行流程

答:系統啓動的時候根據配置文件創建spring的容器, 首先是發送http請求到核心控制器disPatherServlet,spring容器通過映射器去尋找業務控制器,使用適配器找到相應的業務類,在進業務類時進行數據封裝,在封裝前可能會涉及到類型轉換,執行完業務類後使用ModelAndView進行視圖轉發,數據放在model中,用map傳遞數據進行頁面顯示。

最後附上Spring全家桶學習圖譜:

在這裏插入圖片描述

ps:資源過大,需要的話點擊領取,領取碼:面試

JVM(30題)

1、什麼是Java虛擬機?爲什麼Java被稱作是“平臺無關的編程語言”?

Java虛擬機是一個可以執行Java字節碼的虛擬機進程。Java源文件被編譯成能被Java虛擬機執行的字節碼文件。Java被設計成允許應用程序可以運行在任意的平臺,而不需要程序員爲每一個平臺單獨重寫或者是重新編譯。Java虛擬機讓這個變爲可能,因爲它知道底層硬件平臺的指令長度和其他特性。

2、Java內存結構?

方法區和對是所有線程共享的內存區域;而java棧、本地方法棧和程序員計數器是運行是線程私有的內存區域。

3、內存模型以及分區,需要詳細到每個區放什麼?

JVM 分爲堆區和棧區,還有方法區,初始化的對象放在堆裏面,引用放在棧裏面,class 類信息常量池(static 常量和 static 變量)等放在方法區。
new:

方法區:主要是存儲類信息,常量池(static 常量和 static 變量),編譯後的代碼(字節碼)等數據

堆:初始化的對象,成員變量 (那種非 static 的變量),所有的對象實例和數組都要在堆上分配

棧:棧的結構是棧幀組成的,調用一個方法就壓入一幀,幀上面存儲局部變量表,操作數棧,方法出口等信息,局部變量表存放的是 8 大基礎類型加上一個應用類型,所以還是一個指向地址的指針

本地方法棧:主要爲 Native 方法服務

程序計數器:記錄當前線程執行的行號

4、堆裏面的分區:Eden,survival (from+ to),老年代,各自的特點?

堆裏面分爲新生代和老生代(java8 取消了永久代,採用了 Metaspace),新生代包含 Eden+Survivor 區,survivor 區裏面分爲 from 和 to區,內存回收時,如果用的是複製算法,從 from 複製到 to,當經過一次或者多次 GC 之後,存活下來的對象會被移動到老年區,當 JVM 內存不夠用的時候,會觸發 Full GC,清理 JVM 老年區當新生區滿了之後會觸發 YGC,先把存活的對象放到其中一個 Survice區,然後進行垃圾清理。
因爲如果僅僅清理需要刪除的對象,這樣會導致內存碎片,因此一般會把 Eden 進行完全的清理,然後整理內存。
那麼下次 GC 的時候,就會使用下一個 Survive,這樣循環使用。
如果有特別大的對象,新生代放不下,就會使用老年代的擔保,直接放到老年代裏面。
因爲 JVM 認爲,一般大對象的存活時間一般比較久遠。

5 、解釋內存中的棧(stack)、堆(heap)和方法區(method area)的用法

通常我們定義一個基本數據類型的變量,一個對象的引用,還有就是函數調用的現場保存都使用JVM中的棧空間;而通過new關鍵字和構造器創建的對象則放在堆空間,堆是垃圾收集器管理的主要區域,由於現在的垃圾收集器都採用分代收集算法,所以堆空間還可以細分爲新生代和老生代,再具體一點可以分爲Eden、Survivor(又可分爲From Survivor和To Survivor)、Tenured;方法區和堆都是各個線程共享的內存區域,用於存儲已經被JVM加載的類信息、常量、靜態變量、JIT編譯器編譯後的代碼等數據;程序中的字面量(literal)如直接書寫的100、”hello”和常量都是放在常量池中,常量池是方法區的一部分,。棧空間操作起來最快但是棧很小,通常大量的對象都是放在堆空間,棧和堆的大小都可以通過JVM的啓動參數來進行調整,棧空間用光了會引發StackOverflowError,而堆和常量池空間不足則會引發OutOfMemoryError。

String str = new String("hello");

上面的語句中變量str放在棧上,用new創建出來的字符串對象放在堆上,而”hello”這個字面量是放在方法區的。

補充1:較新版本的Java(從Java 6的某個更新開始)中,由於JIT編譯器的發展和”逃逸分析”技術的逐漸成熟,棧上分配、標量替換等優化技術使得對象一定分配在堆上這件事情已經變得不那麼絕對了。

補充2:運行時常量池相當於Class文件常量池具有動態性,Java語言並不要求常量一定只有編譯期間才能產生,運行期間也可以將新的常量放入池中,String類的intern()方法就是這樣的。看看下面代碼的執行結果是什麼並且比較一下Java 7以前和以後的運行結果是否一致。

String s1 = new StringBuilder("go")
    .append("od").toString();
System.out.println(s1.intern() == s1);
String s2 = new StringBuilder("ja")
    .append("va").toString();
System.out.println(s2.intern() == s2);

6、GC 的兩種判定方法?

引用計數法:指的是如果某個地方引用了這個對象就+1,如果失效了就-1,當爲 0 就 會回收但是 JVM 沒有用這種方式,因爲無法判定相互循環引用(A 引用 B,B 引用 A) 的情況。
引用鏈法:通過一種 GC ROOT 的對象(方法區中靜態變量引用的對象等-static 變 量)來判斷,如果有一條鏈能夠到達 GC ROOT 就說明,不能到達 GC ROOT 就說明 可以回收

7、SafePoint 是什麼?

比如 GC 的時候必須要等到 Java 線程都進入到 safepoint 的時候 VMThread 才能開始執行 GC
1.循環的末尾 (防止大循環的時候一直不進入 safepoint,而其他線程在等待它進入safepoint)
2.方法返回前
3.調用方法的 call 之後
4.拋出異常的位置

8、GC 的三種收集方法:標記清除、標記整理、複製算法的原理與特點,分別用在什麼地方,如果讓你優化收集方法,有什麼思路?

先標記,標記完畢之後再清除,效率不高,會產生碎片
複製算法:分爲 8:1 的 Eden 區和 survivor 區,就是上面談到的 YGC
標記整理:標記完畢之後,讓所有存活的對象向一端移動

9、GC 收集器有哪些?CMS 收集器與 G1 收集器的特點?

並行收集器:串行收集器使用一個單獨的線程進行收集,GC 時服務有停頓時間
串行收集器:次要回收中使用多線程來執行
CMS 收集器是基於“標記—清除”算法實現的,經過多次標記纔會被清除
G1 從整體來看是基於“標記—整理”算法實現的收集器,從局部(兩個 Region 之間)上來看是基於“複製”算法實現的
10.Minor GC 與 Full GC 分別在什麼時候發生?
新生代內存不夠用時候發生 MGC 也叫 YGC,JVM 內存不夠的時候發生 FGC

11、幾種常用的內存調試工具:jmap、jstack、jconsole、jhat?

jstack 可以看當前棧的情況,jmap 查看內存,jhat 進行 dump 堆的信息 mat(eclipse 的也要了解一下)

12、什麼是類的加載

類的加載指的是將類的.class文件中的二進制數據讀入到內存中,將其放在運行時數據區的方法區內,然後在堆區創建一個java.lang.Class對象,用來封裝類在方法區內的數據結構。類的加載的最終產品是位於堆區中的Class對象,Class對象封裝了類在方法區內的數據結構,並且向Java程序員提供了訪問方法區內的數據結構的接口。

13.類加載器

在這裏插入圖片描述

  • 啓動類加載器:Bootstrap ClassLoader,負責加載存放在JDK\jre\lib(JDK代表JDK的安裝目錄,下同)下,或被-Xbootclasspath參數指定的路徑中的,並且能被虛擬機識別的類庫

  • 擴展類加載:Extension ClassLoader,該加載器由sun.misc.Launcher$ExtClassLoader實現,它負責加載DK\jre\lib\ext目錄中,或者由java.ext.dirs系統變量指定的路徑中的所有類庫(如javax.*開頭的類),開發者可以直接使用擴展類加載器。

  • 應用程序類加載器:Application ClassLoader,該類加載器由sun.misc.Launcher$AppClassLoader來實現,它負責加載用戶類路徑(ClassPath)所指定的類,開發者可以直接使用該類加載器

14、描述一下JVM加載class文件的原理機制?

JVM中類的裝載是由類加載器(ClassLoader)和它的子類來實現的,Java中的類加載器是一個重要的Java運行時系統組件,它負責在運行時查找和裝入類文件中的類。

由於Java的跨平臺性,經過編譯的Java源程序並不是一個可執行程序,而是一個或多個類文件。當Java程序需要使用某個類時,JVM會確保這個類已經被加載、連接(驗證、準備和解析)和初始化。類的加載是指把類的.class文件中的數據讀入到內存中,通常是創建一個字節數組讀入.class文件,然後產生與所加載類對應的Class對象。加載完成後,Class對象還不完整,所以此時的類還不可用。當類被加載後就進入連接階段,這一階段包括驗證、準備(爲靜態變量分配內存並設置默認的初始值)和解析(將符號引用替換爲直接引用)三個步驟。最後JVM對類進行初始化,包括:

  • 1)如果類存在直接的父類並且這個類還沒有被初始化,那麼就先初始化父類;

  • 2)如果類中存在初始化語句,就依次執行這些初始化語句。

類的加載是由類加載器完成的,類加載器包括:根加載器(BootStrap)、擴展加載器(Extension)、系統加載器(System)和用戶自定義類加載器(java.lang.ClassLoader的子類)。
從Java 2(JDK 1.2)開始,類加載過程採取了父親委託機制(PDM)。PDM更好的保證了Java平臺的安全性,在該機制中,JVM自帶的Bootstrap是根加載器,其他的加載器都有且僅有一個父類加載器。類的加載首先請求父類加載器加載,父類加載器無能爲力時才由其子類加載器自行加載。JVM不會向Java程序提供對Bootstrap的引用。下面是關於幾個類加載器的說明:

Bootstrap:一般用本地代碼實現,負責加載JVM基礎核心類庫(rt.jar);

Extension:從java.ext.dirs系統屬性所指定的目錄中加載類庫,它的父加載器是Bootstrap;

System:又叫應用類加載器,其父類是Extension。它是應用最廣泛的類加載器。它從環境變量classpath或者系統屬性java.class.path所指定的目錄中記載類,是用戶自定義加載器的默認父加載器。

15、Java對象創建過程

1.JVM遇到一條新建對象的指令時首先去檢查這個指令的參數是否能在常量池中定義到一個類的符號引用。然後加載這個類(類加載過程在後邊講)

2.爲對象分配內存。一種辦法“指針碰撞”、一種辦法“空閒列表”,最終常用的辦法“本地線程緩衝分配(TLAB)”

3.將除對象頭外的對象內存空間初始化爲0

4.對對象頭進行必要設置

16、類的生命週期

類的生命週期包括這幾個部分,加載、連接、初始化、使用和卸載,其中前三部是類的加載的過程,如下圖;
在這裏插入圖片描述
java 類加載需要經歷以下 幾個過程:

  • 加載

加載時類加載的第一個過程,在這個階段,將完成以下三件事情:
1.通過一個類的全限定名獲取該類的二進制流。
2.將該二進制流中的靜態存儲結構轉化爲方法去運行時數據結構。
3.在內存中生成該類的 Class 對象,作爲該類的數據訪問入口。

  • 驗證

驗證的目的是爲了確保 Class
文件的字節流中的信息不回危害到虛擬機.在該階段主要完成以下四鍾驗證:
文件格式驗證:驗證字節流是否符合 Class
文件的規範,如主次版本號是否在當前虛擬機範圍內,常量池中的常量是否有不被支持的類型.
元數據驗證:對字節碼描述的信息進行語義分析,如這個類是否有父類,是否集成了不被繼承的類等。
字節碼驗證:是整個驗證過程中最複雜的一個階段,通過驗證數據流和控制流的分析,確定程序語義是否正確,主要針對方法體的驗證。 如: 方法中的類型轉換是否正確,跳轉指令是否正確等。
符號引用驗證: 這個動作在後面的解析過程中發生,主要是爲了確保解析動作能正確執行。

  • 準備

準備階段是爲類的靜態變量分配內存並將其初始化爲默認值,這些內存都將在方法區中進行分配。準備階段不分配類中的實例變量的內存,實例變量將會在對象實例化時隨着對象一起分配在 Java 堆中。

public static int value=123;
//在準備階段 value 初始值爲 0 。
在初始化階段纔會變爲123。
  • 解析

該階段主要完成符號引用到直接引用的轉換動作。解析動作並不一定在初始化動作完成之前,也有可能在初始化之後。

  • 初始化

初始化時類加載的最後一步,前面的類加載過程,除了在加載階段用戶應用程序可以通過自定義類加載器參與之外,其餘動作完全由虛擬機主導和控制。 到了初始化階段,才真正開始執行類中定義的 Java 程序。

17、簡述 java 類加載機制?

虛擬機把描述類的數據從 Class 文件加載到內存,並對數據進行校驗,解析和初始化,最終形成可以被虛擬機直接使用的 java 類型。

18、Java對象結構

Java對象由三個部分組成:對象頭、實例數據、對齊填充。
對象頭由兩部分組成,第一部分存儲對象自身的運行時數據:哈希碼、GC分代年齡、鎖標識狀態、線程持有的鎖、偏向線程ID(一般佔32/64 bit)。
第二部分是指針類型,指向對象的類元數據類型(即對象代表哪個類)。如果是數組對象,則對象頭中還有一部分用來記錄數組長度。
實例數據用來存儲對象真正的有效信息(包括父類繼承下來的和自己定義的)
對齊填充:JVM要求對象起始地址必須是8字節的整數倍(8字節對齊)
19.Java對象的定位方式
句柄池、直接指針。

20、如和判斷一個對象是否存活?(或者 GC 對象的判定方法)

判斷一個對象是否存活有兩種方法:
1.引用計數法
所謂引用計數法就是給每一個對象設置一個引用計數器,每當有一個地方引用這個對象時,就將計數器加一,引用失效時,計數器就減一。當一個對象的引用計數器爲零時,說明此對象沒有被引用,也就是“死對象”,將會被垃圾回收。
引用計數法有一個缺陷就是無法解決循環引用問題,也就是說當對象 A 引用對象 B,對象B 又引用者對象 A,那麼此時 A,B 對象的引用計數器都不爲零,也就造成無法完成垃圾回收,所以主流的虛擬機都沒有采用這種算法。
2.可達性算法(引用鏈法)
該算法的思想是:從一個被稱爲 GC Roots 的對象開始向下搜索,如果一個對象到 GC Roots沒有任何引用鏈相連時,則說明此對象不可用。
在 java 中可以作爲 GC Roots的對象有以下幾種:
虛擬機棧中引用的對象
方法區類靜態屬性引用的對象
方法區常量池引用的對象
本地方法棧 JNI 引用的對象
雖然這些算法可以判定一個對象是否能被回收,但是當滿足上述條件時,一個對象比不一定會被回收。當一個對象不可達 GC Root 時,這個對象並不會立馬被回收,而是出於一個死緩的階段,若要被真正的回收需要經歷兩次標記如果對象在可達性分析中沒有與 GC Root 的引用鏈,那麼此時就會被第一次標記並且進行一次篩選,篩選的條件是是否有必要執行 finalize()方法。當對象沒有覆蓋 finalize()方法或者已被虛擬機調用過,那麼就認爲是沒必要的。

如果該對象有必要執行 finalize()方法,那麼這個對象將會放在一個稱爲 F-Queue 的對隊列中,虛擬機會觸發一個 Finalize()線程去執行,此線程是低優先級的,並且虛擬機不會承諾一直等待它運行完,這是因爲如果 finalize()執行緩慢或者發生了死鎖,那麼就會造成 FQueue 隊列一直等待,造成了內存回收系統的崩潰。 GC 對處於 F-Queue 中的對象進行第二次被標記,這時,該對象將被移除”即將回收”集合,等待回收。

21、JVM的永久代中會發生垃圾回收麼?

垃圾回收不會發生在永久代,如果永久代滿了或者是超過了臨界值,會觸發完全垃圾回收(Full GC)。如果你仔細查看垃圾收集器的輸出信息,就會發現永久代也是被回收的。這就是爲什麼正確的永久代大小對避免Full GC是非常重要的原因。請參考下Java8:從永久代到元數據區 (注:Java8中已經移除了永久代,新加了一個叫做元數據區的native內存區)

22、簡述 java 內存分配與回收策率以及 Minor GC 和Major GC?

1.對象優先在堆的 Eden 區分配。

2.大對象直接進入老年代.

3.長期存活的對象將直接進入老年代.,當 Eden 區沒有足夠的空間進行分配時,虛擬機會執行一次 Minor GC.Minor Gc 通常發生在新生代的 Eden 區,在這個區的對象生存期短,往往發生 Gc 的頻率較高,
回收速度比較快;Full Gc/Major GC 發生在老年代,一般情況下,觸發老年代 GC的時候不會觸發 Minor GC,但是通過配置,可以在 Full GC 之前進行一次 MinorGC 這樣可以加快老年代的回收速度。

23、判斷一個對象應該被回收

該對象沒有與GC Roots相連
該對象沒有重寫finalize()方法或finalize()已經被執行過則直接回收(第一次標記)、否則將對象加入到F-Queue隊列中(優先級很低的隊列)在這裏finalize()方法被執行,之後進行第二次標記,如果對象仍然應該被GC則GC,否則移除隊列。(在finalize方法中,對象很可能和其他 GC Roots中的某一個對象建立了關聯,finalize方法只會被調用一次,且不推薦使用finalize方法)

24、回收方法區

方法區回收價值很低,主要回收廢棄的常量和無用的類。
如何判斷無用的類:
該類所有實例都被回收(Java堆中沒有該類的對象)
加載該類的ClassLoader已經被回收
該類對應的java.lang.Class對象沒有在任何地方被引用,無法在任何地方利用反射訪問該類

25、垃圾收集算法

GC最基礎的算法有三種:標記 -清除算法、複製算法、標記-壓縮算法,我們常用的垃圾回收器一般都採用分代收集算法。

  • 標記 -清除算法,“標記-清除”(Mark-Sweep)算法,如它的名字一樣,算法分爲“標記”和“清除”兩個階段:首先標記出所有需要回收的對象,在標記完成後統一回收掉所有被標記的對象。

  • 複製算法,“複製”(Copying)的收集算法,它將可用內存按容量劃分爲大小相等的兩塊,每次只使用其中的一塊。當這一塊的內存用完了,就將還存活着的對象複製到另外一塊上面,然後再把已使用過的內存空間一次清理掉。

  • 標記-壓縮算法,標記過程仍然與“標記-清除”算法一樣,但後續步驟不是直接對可回收對象進行清理,而是讓所有存活的對象都向一端移動,然後直接清理掉端邊界以外的內存

  • 分代收集算法,“分代收集”(Generational Collection)算法,把Java堆分爲新生代和老年代,這樣就可以根據各個年代的特點採用最適當的收集算法。

26、垃圾回收器

Serial收集器,串行收集器是最古老,最穩定以及效率高的收集器,可能會產生較長的停頓,只使用一個線程去回收。

ParNew收集器,ParNew收集器其實就是Serial收集器的多線程版本。
Parallel收集器,Parallel Scavenge收集器類似ParNew收集器,Parallel收集器更關注系統的吞吐量。

Parallel Old 收集器,Parallel Old是Parallel Scavenge收集器的老年代版本,使用多線程和“標記-整理”算法
CMS收集器,CMS(Concurrent Mark Sweep)收集器是一種以獲取最短回收停頓時間爲目標的收集器。

G1收集器,G1 (Garbage-First)是一款面向服務器的垃圾收集器,主要針對配備多顆處理器及大容量內存的機器. 以極高概率滿足GC停頓時間要求的同時,還具備高吞吐量性能特徵

27、GC日誌分析

摘錄GC日誌一部分(前部分爲年輕代gc回收;後部分爲full gc回收):

2016-07-05T10:43:18.093+0800: 25.395: [GC [PSYoungGen: 274931K->10738K(274944K)] 371093K->147186K(450048K), 0.0668480 secs] [Times: user=0.17 sys=0.08, real=0.07 secs]
2016-07-05T10:43:18.160+0800: 25.462: [Full GC [PSYoungGen: 10738K->0K(274944K)] [ParOldGen: 136447K->140379K(302592K)] 147186K->140379K(577536K) [PSPermGen: 85411K->85376K(171008K)], 0.6763541 secs] [Times: user=1.75 sys=0.02, real=0.68 secs]

通過上面日誌分析得出,PSYoungGen、ParOldGen、PSPermGen屬於Parallel收集器。其中PSYoungGen表示gc回收前後年輕代的內存變化;
ParOldGen表示gc回收前後老年代的內存變化;PSPermGen表示gc回收前後永久區的內存變化。young gc 主要是針對年輕代進行內存回收比較頻繁,耗時短;full gc 會對整個堆內存進行回城,耗時長,因此一般儘量減少full gc的次數

28、調優命令

Sun JDK監控和故障處理命令有jps jstat jmap jhat jstack jinfo

  • jps,JVM Process Status Tool,顯示指定系統內所有的HotSpot虛擬機進程。

  • jstat,JVM statistics Monitoring是用於監視虛擬機運行時狀態信息的命令,它可以顯示出虛擬機進程中的類裝載、內存、垃圾收集、JIT編譯等運行數據。

  • jmap,JVM Memory Map命令用於生成heap dump文件

  • jhat,JVM Heap Analysis Tool命令是與jmap搭配使用,用來分析jmap生成的dump,jhat內置了一個微型的HTTP/HTML服務器,生成dump的分析結果後,可以在瀏覽器中查看

  • jstack,用於生成java虛擬機當前時刻的線程快照。

  • jinfo,JVM Configuration info 這個命令作用是實時查看和調整虛擬機運行參數。

29、調優工具

常用調優工具分爲兩類,jdk自帶監控工具:jconsole和jvisualvm,第三方有:MAT(Memory Analyzer Tool)、GChisto。
jconsole,Java Monitoring and Management Console是從java5開始,在JDK中自帶的java監控和管理控制檯,用於對JVM中內存,線程和類等的監控
jvisualvm,jdk自帶全能工具,可以分析內存快照、線程快照;監控內存變化、GC變化等。
MAT,Memory Analyzer Tool,一個基於Eclipse的內存分析工具,是一個快速、功能豐富的Java heap分析工具,它可以幫助我們查找內存泄漏和減少內存消耗
GChisto,一款專業分析gc日誌的工具

30、Minor GC與Full GC分別在什麼時候發生?

新生代內存不夠用時候發生MGC也叫YGC,JVM內存不夠的時候發生FGC

最後附上學習圖譜:

在這裏插入圖片描述

ps:資源過大,需要的話點擊領取,領取碼:面試

Mybatis(25題)

1、什麼是Mybatis?

(1)Mybatis是一個半ORM(對象關係映射)框架,它內部封裝了JDBC,開發時只需要關注SQL語句本身,不需要花費精力去處理加載驅動、創建連接、創建statement等繁雜的過程。程序員直接編寫原生態sql,可以嚴格控制sql執行性能,靈活度高。
(2)MyBatis 可以使用XML 或註解來配置和映射原生信息,將POJO映射成數據庫中的記錄,避免了幾乎所有的 JDBC 代碼和手動設置參數以及獲取結果集。
(3)通過xml 文件或註解的方式將要執行的各種 statement 配置起來,並通過java對象和 statement中sql的動態參數進行映射生成最終執行的sql語句,最後由mybatis框架執行sql並將結果映射爲java對象並返回。(從執行sql到返回result的過程)。

2、Mybaits的優點:

(1)基於SQL語句編程,相當靈活,不會對應用程序或者數據庫的現有設計造成任何影響,SQL寫在XML裏,解除sql與程序代碼的耦合,便於統一管理;提供XML標籤,支持編寫動態SQL語句,並可重用。
(2)與JDBC相比,減少了50%以上的代碼量,消除了JDBC大量冗餘的代碼,不需要手動開關連接;
(3)很好的與各種數據庫兼容(因爲MyBatis使用JDBC來連接數據庫,所以只要JDBC支持的數據庫MyBatis都支持)。
(4)能夠與Spring很好的集成;
(5)提供映射標籤,支持對象與數據庫的ORM字段關係映射;提供對象關係映射標籤,支持對象關係組件維護。
3、MyBatis框架的缺點:
(1)SQL語句的編寫工作量較大,尤其當字段多、關聯表多時,對開發人員編寫SQL語句的功底有一定要求。
(2)SQL語句依賴於數據庫,導致數據庫移植性差,不能隨意更換數據庫。

4、MyBatis框架適用場合:

(1)MyBatis專注於SQL本身,是一個足夠靈活的DAO層解決方案。
(2)對性能的要求很高,或者需求變化較多的項目,如互聯網項目,MyBatis將是不錯的選擇。
5、MyBatis與Hibernate有哪些不同?
(1)Mybatis和hibernate不同,它不完全是一個ORM框架,因爲MyBatis需要程序員自己編寫Sql語句。
(2)Mybatis直接編寫原生態sql,可以嚴格控制sql執行性能,靈活度高,非常適合對關係數據模型要求不高的軟件開發,因爲這類軟件需求變化頻繁,一但需求變化要求迅速輸出成果。但是靈活的前提是mybatis無法做到數據庫無關性,如果需要實現支持多種數據庫的軟件,則需要自定義多套sql映射文件,工作量大。
(3)Hibernate對象/關係映射能力強,數據庫無關性好,對於關係模型要求高的軟件,如果用hibernate開發可以節省很多代碼,提高效率。

6、#{}和${}的區別是什麼?

#{}是預編譯處理,${}是字符串替換。
Mybatis在處理#{}時,會將sql中的#{}替換爲?號,調用PreparedStatement的set方法來賦值;
Mybatis在處理{}替換成變量的值。
使用#{}可以有效的防止SQL注入,提高系統安全性。

7、當實體類中的屬性名和表中的字段名不一樣 ,怎麼辦 ?

第1種: 通過在查詢的sql語句中定義字段名的別名,讓字段名的別名和實體類的屬性名一致。

<select id=”selectorder” parametertype=int” resultetype=”me.gacl.domain.order”>
   select order_id id, order_no orderno ,order_price price form orders where order_id=#{id};
    </select>

第2種: 通過來映射字段名和實體類屬性名的一一對應的關係。

 <select id="getOrder" parameterType="int" resultMap="orderresultmap">
   select * from orders where order_id=#{id}
    </select>
 
   <resultMap type=”me.gacl.domain.order” id=”orderresultmap”>
    <!–用id屬性來映射主鍵字段–>
    <id property=”id” column=”order_id”>
 
    <!–用result屬性來映射非主鍵字段,property爲實體類屬性名,column爲數據表中的屬性–>
   <result property = “orderno” column =”order_no”/>
    <result property=”price” column=”order_price” />
    </reslutMap>

8、 模糊查詢like語句該怎麼寫?

第1種:在Java代碼中添加sql通配符。

    string wildcardname =%smi%;
    list<name> names = mapper.selectlike(wildcardname);
 
    <select id=”selectlike”>
 select * from foo where bar like #{value}
    </select>

第2種:在sql語句中拼接通配符,會引起sql注入

    string wildcardname = “smi”;
    list<name> names = mapper.selectlike(wildcardname);
 
    <select id=”selectlike”>
 select * from foo where bar like "%"#{value}"%"
    </select>

9、通常一個Xml映射文件,都會寫一個Dao接口與之對應,請問,這個Dao接口的工作原理是什麼?Dao接口裏的方法,參數不同時,方法能重載嗎?

Dao接口即Mapper接口。接口的全限名,就是映射文件中的namespace的值;接口的方法名,就是映射文件中Mapper的Statement的id值;接口方法內的參數,就是傳遞給sql的參數。
Mapper接口裏的方法,是不能重載的,因爲是使用 全限名+方法名 的保存和尋找策略。Mapper接口的工作原理是JDK動態代理,Mybatis運行時會使用JDK動態代理爲Mapper接口生成代理對象proxy,代理對象會攔截接口方法,轉而執行MapperStatement所代表的sql,然後將sql執行結果返回。

10、Mybatis是如何進行分頁的?分頁插件的原理是什麼?

Mybatis使用RowBounds對象進行分頁,它是針對ResultSet結果集執行的內存分頁,而非物理分頁。可以在sql內直接書寫帶有物理分頁的參數來完成物理分頁功能,也可以使用分頁插件來完成物理分頁。
分頁插件的基本原理是使用Mybatis提供的插件接口,實現自定義插件,在插件的攔截方法內攔截待執行的sql,然後重寫sql,根據dialect方言,添加對應的物理分頁語句和物理分頁參數。

11、Mybatis是如何將sql執行結果封裝爲目標對象並返回的?都有哪些映射形式?

第一種是使用resultMap標籤,逐一定義數據庫列名和對象屬性名之間的映射關係。
第二種是使用sql列的別名功能,將列的別名書寫爲對象屬性名。
有了列名與屬性名的映射關係後,Mybatis通過反射創建對象,同時使用反射給對象的屬性逐一賦值並返回,那些找不到映射關係的屬性,是無法完成賦值的。

12、如何執行批量插入?

首先,創建一個簡單的insert語句:

    <insert id=”insertname”>
    insert into names (name) values (#{value})
    </insert>

然後在java代碼中像下面這樣執行批處理插入:


  list<string> names = new arraylist();
    names.add(“fred”);
    names.add(“barney”);
    names.add(“betty”);
    names.add(“wilma”);
 
    // 注意這裏 executortype.batch
    sqlsession sqlsession = sqlsessionfactory.opensession(executortype.batch);
    try {
 namemapper mapper = sqlsession.getmapper(namemapper.class);
 for (string name : names) {
 mapper.insertname(name);
 }
 sqlsession.commit();
    }catch(Exception e){
 e.printStackTrace();
 sqlSession.rollback(); 
 throw e; 
}
finally {
    sqlsession.close();
    }

13、如何獲取自動生成的(主)鍵值?

insert 方法總是返回一個int值 ,這個值代表的是插入的行數。

如果採用自增長策略,自動生成的鍵值在 insert 方法執行完後可以被設置到傳入的參數對象中。

示例:

<insert id=”insertname” usegeneratedkeys=true” keyproperty=”id”>
 insert into names (name) values (#{name})
</insert>
name name = new name();
    name.setname(“fred”);
 
    int rows = mapper.insertname(name);
    // 完成後,id已經被設置到對象中
    system.out.println(“rows inserted =+ rows);
    system.out.println(“generated key value =+ name.getid());

14、在mapper中如何傳遞多個參數?

1)第一種:
//DAO層的函數
Public UserselectUser(String name,String area);  
//對應的xml,#{0}代表接收的是dao層中的第一個參數,#{1}代表dao層中第二參數,更多參數一致往後加即可。
<select id="selectUser"resultMap="BaseResultMap">  
    select *  fromuser_user_t   whereuser_name = #{0} anduser_area=#{1}  
</select>2)第二種: 使用 @param 註解:
public interface usermapper {
   user selectuser(@param(“username”) string username,@param(“hashedpassword”) string hashedpassword);
}
然後,就可以在xml像下面這樣使用(推薦封裝爲一個map,作爲單個參數傳遞給mapper):
<select id=”selectuser” resulttype=”user”>
 select id, username, hashedpassword
 from some_table
 where username = #{username}
 and hashedpassword = #{hashedpassword}
</select>3)第三種:多個參數封裝成map
try{
//映射文件的命名空間.SQL片段的ID,就可以調用對應的映射文件中的SQL
//由於我們的參數超過了兩個,而方法中只有一個Object參數收集,因此我們使用Map集合來裝載我們的參數
Map<String, Object> map = new HashMap();
 map.put("start", start);
 map.put("end", end);
 return sqlSession.selectList("StudentID.pagination", map);
 }catch(Exception e){
 e.printStackTrace();
 sqlSession.rollback();
    throw e; }
finally{
 MybatisUtil.closeSqlSession();
 }

15、Mybatis的Xml映射文件中,不同的Xml映射文件,id是否可以重複?

不同的Xml映射文件,如果配置了namespace,那麼id可以重複;如果沒有配置namespace,那麼id不能重複;
原因就是namespace+id是作爲Map<String, MapperStatement>的key使用的,如果沒有namespace,就剩下id,那麼,id重複會導致數據互相覆蓋。有了namespace,自然id就可以重複,namespace不同,namespace+id自然也就不同。
但是,在以前的Mybatis版本的namespace是可選的,不過新版本的namespace已經是必須的了。

16、爲什麼說Mybatis是半自動ORM映射工具?它與全自動的區別在哪裏?

Hibernate屬於全自動ORM映射工具,使用Hibernate查詢關聯對象或者關聯集合對象時,可以根據對象關係模型直接獲取,所以它是全自動的。而Mybatis在查詢關聯對象或關聯集合對象時,需要手動編寫sql來完成,所以,稱之爲半自動ORM映射工具。

17、 一對一、一對多的關聯查詢 ?

<mapper namespace="com.lcb.mapping.userMapper">  
    <!--association  一對一關聯查詢 -->  
    <select id="getClass" parameterType="int" resultMap="ClassesResultMap">  
    select * from class c,teacher t where c.teacher_id=t.t_id and c.c_id=#{id}  
    </select>  
 
    <resultMap type="com.lcb.user.Classes" id="ClassesResultMap">  
   <!-- 實體類的字段名和數據表的字段名映射 -->  
   <id property="id" column="c_id"/>  
    <result property="name" column="c_name"/>  
    <association property="teacher" javaType="com.lcb.user.Teacher">  
    <id property="id" column="t_id"/>  
    <result property="name" column="t_name"/>  
    </association>  
    </resultMap>  
 
 
    <!--collection  一對多關聯查詢 -->  
    <select id="getClass2" parameterType="int" resultMap="ClassesResultMap2">  
    select * from class c,teacher t,student s where c.teacher_id=t.t_id and c.c_id=s.class_id and c.c_id=#{id}  
    </select>  
 
    <resultMap type="com.lcb.user.Classes" id="ClassesResultMap2">  
    <id property="id" column="c_id"/>  
    <result property="name" column="c_name"/>  
    <association property="teacher" javaType="com.lcb.user.Teacher">  
    <id property="id" column="t_id"/>  
    <result property="name" column="t_name"/>  
    </association>  
 
   <collection property="student" ofType="com.lcb.user.Student">  
    <id property="id" column="s_id"/>  
    <result property="name" column="s_name"/>  
    </collection>  
    </resultMap>  
</mapper>

18、MyBatis實現一對一有幾種方式?具體怎麼操作的?

有聯合查詢和嵌套查詢,聯合查詢是幾個表聯合查詢,只查詢一次,通過在resultMap裏面配置association節點配置一對一的類就可以完成;
嵌套查詢是先查一個表,根據這個表裏面的結果的 外鍵id,去再另外一個表裏面查詢數據,也是通過association配置,但另外一個表的查詢通過select屬性配置。

19、MyBatis實現一對多有幾種方式,怎麼操作的?

有聯合查詢和嵌套查詢。聯合查詢是幾個表聯合查詢,只查詢一次,通過在resultMap裏面的collection節點配置一對多的類就可以完成;嵌套查詢是先查一個表,根據這個表裏面的 結果的外鍵id,去再另外一個表裏面查詢數據,也是通過配置collection,但另外一個表的查詢通過select節點配置。

20、Mybatis是否支持延遲加載?如果支持,它的實現原理是什麼?

答:Mybatis僅支持association關聯對象和collection關聯集合對象的延遲加載,association指的就是一對一,collection指的就是一對多查詢。在Mybatis配置文件中,可以配置是否啓用延遲加載lazyLoadingEnabled=true|false。

它的原理是,使用CGLIB創建目標對象的代理對象,當調用目標方法時,進入攔截器方法,比如調用a.getB().getName(),攔截器invoke()方法發現a.getB()是null值,那麼就會單獨發送事先保存好的查詢關聯B對象的sql,把B查詢上來,然後調用a.setB(b),於是a的對象b屬性就有值了,接着完成a.getB().getName()方法的調用。這就是延遲加載的基本原理。
當然了,不光是Mybatis,幾乎所有的包括Hibernate,支持延遲加載的原理都是一樣的。

21、Mybatis的一級、二級緩存:

1)一級緩存: 基於 PerpetualCache 的 HashMap 本地緩存,其存儲作用域爲 Session,當 Session flush 或 close 之後,該 Session 中的所有 Cache 就將清空,默認打開一級緩存。
2)二級緩存與一級緩存其機制相同,默認也是採用 PerpetualCache,HashMap 存儲,不同在於其存儲作用域爲 Mapper(Namespace),並且可自定義存儲源,如 Ehcache。默認不打開二級緩存,要開啓二級緩存,使用二級緩存屬性類需要實現Serializable序列化接口(可用來保存對象的狀態),可在它的映射文件中配置 ;
3)對於緩存數據更新機制,當某一個作用域(一級緩存 Session/二級緩存Namespaces)的進行了C/U/D 操作後,默認該作用域下所有 select 中的緩存將被 clear 掉並重新更新,如果開啓了二級緩存,則只根據配置判斷是否刷新。

22、什麼是MyBatis的接口綁定?有哪些實現方式?

接口綁定,就是在MyBatis中任意定義接口,然後把接口裏面的方法和SQL語句綁定,我們直接調用接口方法就可以,這樣比起原來了SqlSession提供的方法我們可以有更加靈活的選擇和設置。
接口綁定有兩種實現方式,一種是通過註解綁定,就是在接口的方法上面加上@Select、@Update等註解,裏面包含Sql語句來綁定;另外一種就是通過xml裏面寫SQL來綁定,在這種情況下,要指定xml映射文件裏面的namespace必須爲接口的全路徑名。當Sql語句比較簡單時候,用註解綁定,當SQL語句比較複雜時候,用xml綁定,一般用xml綁定的比較多。

23、使用MyBatis的mapper接口調用時有哪些要求?

①Mapper接口方法名和mapper.xml中定義的每個sql的id相同;
②Mapper接口方法的輸入參數類型和mapper.xml中定義的每個sql 的parameterType的類型相同;
③Mapper接口方法的輸出參數類型和mapper.xml中定義的每個sql的resultType的類型相同;
④Mapper.xml文件中的namespace即是mapper接口的類路徑。

24、Mapper編寫有哪幾種方式?

第一種:接口實現類繼承SqlSessionDaoSupport:使用此種方法需要編寫mapper接口,mapper接口實現類、mapper.xml文件。
(1)在sqlMapConfig.xml中配置mapper.xml的位置

<mappers>
  <mapper resource="mapper.xml文件的地址" />
  <mapper resource="mapper.xml文件的地址" />
</mappers>

(2)定義mapper接口

(3)實現類集成SqlSessionDaoSupport

mapper方法中可以this.getSqlSession()進行數據增刪改查。

(4)spring 配置

<bean id=" " class="mapper接口的實現">
  <property name="sqlSessionFactory" ref="sqlSessionFactory"></property>
</bean>

第二種:使用org.mybatis.spring.mapper.MapperFactoryBean:
(1)在sqlMapConfig.xml中配置mapper.xml的位置,如果mapper.xml和mappre接口的名稱相同且在同一個目錄,這裏可以不用配置

<mappers>
  <mapper resource="mapper.xml文件的地址" />
  <mapper resource="mapper.xml文件的地址" />
</mappers>

(2)定義mapper接口:

①mapper.xml中的namespace爲mapper接口的地址

②mapper接口中的方法名和mapper.xml中的定義的statement的id保持一致

③Spring中定義

<bean id="" class="org.mybatis.spring.mapper.MapperFactoryBean">
  <property name="mapperInterface"  value="mapper接口地址" />
  <property name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>

第三種:使用mapper掃描器:
(1)mapper.xml文件編寫:
mapper.xml中的namespace爲mapper接口的地址;
mapper接口中的方法名和mapper.xml中的定義的statement的id保持一致;
如果將mapper.xml和mapper接口的名稱保持一致則不用在sqlMapConfig.xml中進行配置。
(2)定義mapper接口:
注意mapper.xml的文件名和mapper的接口名稱保持一致,且放在同一個目錄
(3)配置mapper掃描器:

<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
  <property name="basePackage" value="mapper接口包地址"></property>
 <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
</bean>

(4)使用掃描器後從spring容器中獲取mapper的實現對象。

25、簡述Mybatis的插件運行原理,以及如何編寫一個插件。

答:Mybatis僅可以編寫針對ParameterHandler、ResultSetHandler、StatementHandler、Executor這4種接口的插件,Mybatis使用JDK的動態代理,爲需要攔截的接口生成代理對象以實現接口方法攔截功能,每當執行這4種接口對象的方法時,就會進入攔截方法,具體就是InvocationHandler的invoke()方法,當然,只會攔截那些你指定需要攔截的方法。
編寫插件:實現Mybatis的Interceptor接口並複寫intercept()方法,然後在給插件編寫註解,指定要攔截哪一個接口的哪些方法即可,記住,別忘了在配置文件中配置你編寫的插件。

Redis(29題)(含學習圖譜)

1、Redis 的回收策略(淘汰策略)?

volatile-lru:從已設置過期時間的數據集(server.db[i].expires)中挑選最近最少使用的數據淘汰

volatile-ttl:從已設置過期時間的數據集(server.db[i].expires)中挑選將要過期的數據淘汰

volatile-random:從已設置過期時間的數據集(server.db[i].expires)中任意選擇數據淘汰

allkeys-lru:從數據集(server.db[i].dict)中挑選最近最少使用的數據淘汰

allkeys-random:從數據集(server.db[i].dict)中任意選擇數據淘汰

no-enviction(驅逐):禁止驅逐數據

注意這裏的 6 種機制,volatile 和 allkeys 規定了是對已設置過期時間的數據集淘汰數據還是從全部數據集淘汰數據,後面的 lru、ttl 以及 random 是三種不同的淘汰策略,再加上一種 no-enviction 永不回收的策略。

使用策略規則:
(1)如果數據呈現冪律分佈,也就是一部分數據訪問頻率高,一部分數據訪問頻率低,則使用 allkeys-lru

(2)如果數據呈現平等分佈,也就是所有的數據訪問頻率都相同,則使用 allkeys-random

2、爲什麼 edis 需要把所有數據放到內存中?

答 :Redis 爲了達到最快的讀寫速度將數據都讀到內存中,並通過異步的方式將數據寫入磁盤。所以 redis 具有快速和數據持久化的特徵。如果不將數據放在內存中,磁盤 I/O 速度爲嚴重影響 redis 的性能。在內存越來越便宜的今天,redis 將會越來越受歡迎。如果設置了最大使用的內存,則數據已有記錄數達到內存限值後不能繼續插入新值。

3、Redis 的同步機制瞭解麼?

答:Redis 可以使用主從同步,從從同步。第一次同步時,主節點做一次 bgsave,並同時將後續修改操作記錄到內存 buffer,待完成後將 rdb 文件全量同步到複製節點,複製節點接受完成後將 rdb 鏡像加載到內存。加載完成後,再通知主節點將期間修改的操作記錄同步到複製節點進行重放就完成了同步過程。

4、Pipeline 有什麼好處,爲什麼要用 pipeline?

答:可以將多次 IO 往返的時間縮減爲一次,前提是 pipeline 執行的指令之間沒有因果相關性。使用 redis-benchmark 進行壓測的時候可以發現影響 redis 的 QPS 峯值的一個重要因素是 pipeline 批次指令的數目。

5、是否使用過 Redis 集羣,集羣的原理是什麼?

(1)Redis Sentinal 着眼於高可用,在 master 宕機時會自動將 slave 提升爲 master,繼續提供服務。

(2)Redis Cluster 着眼於擴展性,在單個 redis 內存不足時,使用 Cluster 進行分片存儲。

6、Redis 集羣方案什麼情況下會導致整個集羣不可用?

答:有 A,B,C 三個節點的集羣,在沒有複製模型的情況下,如果節點 B 失敗了,那麼整個集羣就會以爲缺少 5501-11000 這個範圍的槽而不可用。

17、Redis 支持的 Java 客戶端都有哪些?官方推薦用哪個?
答:Redisson、Jedis、lettuce 等等,官方推薦使用 Redisson。

7、Jedis 與 Redisson 對比有什麼優缺點?

答:Jedis 是 Redis 的 Java 實現的客戶端,其 API 提供了比較全面的 Redis 命令的支持;Redisson 實現了分佈式和可擴展的 Java 數據結構,和 Jedis 相比,功能較爲簡單,不支持字符串操作,不支持排序、事務、管道、分區等 Redis 特性。

Redisson 的宗旨是促進使用者對 Redis 的關注分離,從而讓使用者能夠將精力更集中地放在處理業務邏輯上。

8、Redis 如何設置密碼及驗證密碼?

設置密碼:config set requirepass 123456

授權密碼:auth 123456

9、說說 Redis 哈希槽的概念?

答:Redis 集羣沒有使用一致性 hash,而是引入了哈希槽的概念,Redis 集羣有 16384 個哈希槽,每個 key 通過 CRC16 校驗後對 16384 取模來決定放置哪個槽,集羣的每個節點負責一部分 hash 槽。

10、Redis 集羣的主從複製模型是怎樣的?

答:爲了使在部分節點失敗或者大部分節點無法通信的情況下集羣仍然可用,所以集羣使用了主從複製模型,每個節點都會有 N-1 個複製品.

11、Redis 集羣會有寫操作丟失嗎?爲什麼?

答 :Redis 並不能保證數據的強一致性,這意味這在實際中集羣在特定的條件下可能會丟失寫操作。

12、Redis 集羣之間是如何複製的?

答:異步複製

13、Redis 集羣最大節點個數是多少?

答:16384 個。

14、Redis 集羣如何選擇數據庫?

答:Redis 集羣目前無法做數據庫選擇,默認在 0 數據庫。

15、怎麼測試 Redis 的連通性?

答:使用 ping 命令。

16、怎麼理解 Redis 事務?

答:

(1)事務是一個單獨的隔離操作:事務中的所有命令都會序列化、按順序地執行。事務在執行的過程中,不會被其他客戶端發送來的命令請求所打斷。

(2)事務是一個原子操作:事務中的命令要麼全部被執行,要麼全部都不執行。

17、Redis 事務相關的命令有哪幾個?

答:MULTI、EXEC、DISCARD、WATCH

18、Redis key 的過期時間和永久有效分別怎麼設置?

答:EXPIRE 和 PERSIST 命令。

19、Redis 如何做內存優化?

答:儘可能使用散列表(hashes),散列表(是說散列表裏面存儲的數少)使用的內存非常小,所以你應該儘可能的將你的數據模型抽象到一個散列表裏面。比如你的 web 系統中有一個用戶對象,不要爲這個用戶的名稱,姓氏,郵箱,密碼設置單獨的 key,而是應該把這個用戶的所有信息存儲到一張散列表裏面。

20、Redis 回收進程如何工作的?

答:一個客戶端運行了新的命令,添加了新的數據。Redi 檢查內存使用情況,如果大於 maxmemory 的限制, 則根據設定好的策略進行回收。一個新的命令被執行,等等。所以我們不斷地穿越內存限制的邊界,通過不斷達到邊界然後不斷地回收回到邊界以下。如果一個命令的結果導致大量內存被使用(例如很大的集合的交集保存到一個新的鍵),不用多久內存限制就會被這個內存使用量超越。

21、都有哪些辦法可以降低 Redis 的內存使用情況呢?

答:如果你使用的是 32 位的 Redis 實例,可以好好利用 Hash,list,sorted set,set 等集合類型數據,因爲通常情況下很多小的 Key-Value 可以用更緊湊的方式存放到一起。

22、Redis 的內存用完了會發生什麼?

答:如果達到設置的上限,Redis 的寫命令會返回錯誤信息(但是讀命令還可以正常返回。)或者你可以將 Redis 當緩存來使用配置淘汰機制,當 Redis 達到內存上限時會沖刷掉舊的內容。

23、一個 Redis 實例最多能存放多少的 keys?List、Set、Sorted Set 他們最多能存放多少元素?

答:理論上 Redis 可以處理多達 2^32 的 keys,並且在實際中進行了測試,每個實例至少存放了 2 億 5 千萬的 keys。我們正在測試一些較大的值。任何 list、set、和 sorted set 都可以放 2^32 個元素。換句話說,Redis 的存儲極限是系統中的可用內存值。

24、MySQL 裏有 2000w 數據,redis 中只存 20w 的數據,如何保證 redis 中的數據都是熱點數據?

答:Redis 內存數據集大小上升到一定大小的時候,就會施行數據淘汰策略。

相關知識:Redis 提供 6 種數據淘汰策略:

volatile-lru:從已設置過期時間的數據集(server.db[i].expires)中挑選最近最少使用的數據淘汰

volatile-ttl:從已設置過期時間的數據集(server.db[i].expires)中挑選將要過期的數據淘汰

volatile-random:從已設置過期時間的數據集(server.db[i].expires)中任意選擇數據淘汰

allkeys-lru:從數據集(server.db[i].dict)中挑選最近最少使用的數據淘汰

allkeys-random:從數據集(server.db[i].dict)中任意選擇數據淘汰

no-enviction(驅逐):禁止驅逐數據

25、Redis 最適合的場景?

1、會話緩存(Session Cache)

最常用的一種使用 Redis 的情景是會話緩存(session cache)。用 Redis 緩存會話比其他存儲(如 Memcached)的優勢在於:Redis 提供持久化。當維護一個不是嚴格要求一致性的緩存時,如果用戶的購物車信息全部丟失,大部分人都會不高興的,現在,他們還會這樣嗎? 幸運的是,隨着 Redis 這些年的改進,很容易找到怎麼恰當的使用 Redis 來緩存會話的文檔。甚至廣爲人知的商業平臺 Magento 也提供 Redis 的插件。

2、全頁緩存(FPC)

除基本的會話 token 之外,Redis 還提供很簡便的 FPC 平臺。回到一致性問題,即使重啓了 Redis 實例,因爲有磁盤的持久化,用戶也不會看到頁面加載速度的下降,這是一個極大改進,類似 PHP 本地 FPC。 再次以 Magento 爲例,Magento 提供一個插件來使用 Redis 作爲全頁緩存後端。 此外,對 WordPress 的用戶來說,Pantheon 有一個非常好的插件 wp-redis,這個插件能幫助你以最快速度加載你曾瀏覽過的頁面。

3、隊列

Reids 在內存存儲引擎領域的一大優點是提供 list 和 set 操作,這使得 Redis 能作爲一個很好的消息隊列平臺來使用。Redis 作爲隊列使用的操作,就類似於本地程序語言(如 Python)對 list 的 push/pop 操作。 如果你快速的在 Google 中搜索“Redis queues”,你馬上就能找到大量的開源項目,這些項目的目的就是利用 Redis 創建非常好的後端工具,以滿足各種隊列需求。例如,Celery 有一個後臺就是使用 Redis 作爲 broker,你可以從這裏去查看。

4,排行榜/計數器

Redis 在內存中對數字進行遞增或遞減的操作實現的非常好。集合(Set)和有序集合(Sorted Set)也使得我們在執行這些操作的時候變的非常簡單,Redis 只是正好提供了這兩種數據結構。所以,我們要從排序集合中獲取到排名最靠前的 10 個用戶–我們稱之爲“userscores”,我們只需要像下面一樣執行即可: 當然,這是假定你是根據你用戶的分數做遞增的排序。如果你想返回用戶及用戶的分數,你需要這樣執行: ZRANGE userscores 0 10 WITHSCORES Agora Games 就是一個很好的例子,用 Ruby 實現的,它的排行榜就是使用 Redis 來存儲數據的,你可以在這裏看到。

5、發佈/訂閱

最後(但肯定不是最不重要的)是 Redis 的發佈/訂閱功能。發佈/訂閱的使用場景確實非常多。我已看見人們在社交網絡連接中使用,還可作爲基於發佈/訂閱的腳本觸發器,甚至用 Redis 的發佈/訂閱功能來建立聊天系統!

26、假如 Redis 裏面有 1 億個 key,其中有 10w 個 key 是以某個固定的已知的前綴開頭的,如果將它們全部找出來?

答:使用 keys 指令可以掃出指定模式的 key 列表。

對方接着追問:如果這個 redis 正在給線上的業務提供服務,那使用 keys 指令會有什麼問題?

這個時候你要回答 redis 關鍵的一個特性:redis 的單線程的。keys 指令會導致線程阻塞一段時間,線上服務會停頓,直到指令執行完畢,服務才能恢復。這個時候可以使用 scan 指令,scan 指令可以無阻塞的提取出指定模式的 key 列表,但是會有一定的重複概率,在客戶端做一次去重就可以了,但是整體所花費的時間會比直接用 keys 指令長。

27、如果有大量的 key 需要設置同一時間過期,一般需要注意什麼?

答:如果大量的 key 過期時間設置的過於集中,到過期的那個時間點,redis 可能會出現短暫的卡頓現象。一般需要在時間上加一個隨機值,使得過期時間分散一些。

28、使用過 Redis 做異步隊列麼,你是怎麼用的?

一般使用 list 結構作爲隊列,rpush 生產消息,lpop 消費消息。當 lpop 沒有消息的時候,要適當 sleep 一會再重試。如果對方追問可不可以不用 sleep 呢?list 還有個指令叫 blpop,在沒有消息的時候,它會阻塞住直到消息到來。如果對方追問能不能生產一次消費多次呢?使用 pub/sub 主題訂閱者模式,可以實現 1:N 的消息隊列。

如果對方追問 pub/sub 有什麼缺點?

在消費者下線的情況下,生產的消息會丟失,得使用專業的消息隊列如 RabbitMQ 等。

如果對方追問 redis 如何實現延時隊列?

我估計現在你很想把面試官一棒打死如果你手上有一根棒球棍的話,怎麼問的這麼詳細。但是你很剋制,然後神態自若的回答道:使用 sortedset,拿時間戳作爲 score,消息內容作爲 key 調用 zadd 來生產消息,消費者用 zrangebyscore 指令獲取 N 秒之前的數據輪詢進行處理。到這裏,面試官暗地裏已經對你豎起了大拇指。但是他不知道的是此刻你卻豎起了中指,在椅子背後。

29、使用過 Redis 分佈式鎖麼,它是什麼回事?

先拿 setnx 來爭搶鎖,搶到之後,再用 expire 給鎖加一個過期時間防止鎖忘記了釋放。

這時候對方會告訴你說你回答得不錯,然後接着問如果在 setnx 之後執行 expire 之前進程意外 crash 或者要重啓維護了,那會怎麼樣?這時候你要給予驚訝的反饋:唉,是喔,這個鎖就永遠得不到釋放了。緊接着你需要抓一抓自己得腦袋,故作思考片刻,好像接下來的結果是你主動思考出來的,然後回答:我記得 set 指令有非常複雜的參數,這個應該是可以同時把 setnx 和 expire 合成一條指令來用的!

最後附上學習圖譜:

在這裏插入圖片描述

ps:資源過大,需要的話點擊領取,領取碼:面試

併發編程(15題)

1、現在有T1、T2、T3三個線程,你怎樣保證T2在T1執行完後執行,T3在T2執行完後執行?

這個線程問題通常會在第一輪或電話面試階段被問到,目的是檢測你對”join”方法是否熟悉。這個多線程問題比較簡單,可以用join方法實現。

2、在Java中Lock接口比synchronized塊的優勢是什麼?你需要實現一個高效的緩存,它允許多個用戶讀,但只允許一個用戶寫,以此來保持它的完整性,你會怎樣去實現它?

lock接口在多線程和併發編程中最大的優勢是它們爲讀和寫分別提供了鎖,它能滿足你寫像ConcurrentHashMap這樣的高性能數據結構和有條件的阻塞。Java線程面試的問題越來越會根據面試者的回答來提問。我強烈建議在你去參加多線程的面試之前認真讀一下Locks,因爲當前其大量用於構建電子交易終統的客戶端緩存和交易連接空間。

3、在java中wait和sleep方法的不同?

通常會在電話面試中經常被問到的Java線程面試問題。最大的不同是在等待時wait會釋放鎖,而sleep一直持有鎖。Wait通常被用於線程間交互,sleep通常被用於暫停執行。

4、用Java實現阻塞隊列。

這是一個相對艱難的多線程面試問題,它能達到很多的目的。第一,它可以檢測侯選者是否能實際的用Java線程寫程序;第二,可以檢測侯選者對併發場景的理解,並且你可以根據這個問很多問題。如果他用wait()和notify()方法來實現阻塞隊列,你可以要求他用最新的Java 5中的併發類來再寫一次。

5、用Java寫代碼來解決生產者——消費者問題。

與上面的問題很類似,但這個問題更經典,有些時候面試都會問下面的問題。在Java中怎麼解決生產者——消費者問題,當然有很多解決方法,我已經分享了一種用阻塞隊列實現的方法。有些時候他們甚至會問怎麼實現哲學家進餐問題。

6、用Java編程一個會導致死鎖的程序,你將怎麼解決?

這是我最喜歡的Java線程面試問題,因爲即使死鎖問題在寫多線程併發程序時非常普遍,但是很多侯選者並不能寫deadlock free code(無死鎖代碼?),他們很掙扎。只要告訴他們,你有N個資源和N個線程,並且你需要所有的資源來完成一個操作。爲了簡單這裏的n可以替換爲2,越大的數據會使問題看起來更復雜。通過避免Java中的死鎖來得到關於死鎖的更多信息。

7、什麼是原子操作,Java中的原子操作是什麼?

非常簡單的java線程面試問題,接下來的問題是你需要同步一個原子操作。

8、 Java中的volatile關鍵是什麼作用?怎樣使用它?在Java中它跟synchronized方法有什麼不同?

自從Java 5和Java內存模型改變以後,基於volatile關鍵字的線程問題越來越流行。應該準備好回答關於volatile變量怎樣在併發環境中確保可見性。

9、 什麼是競爭條件?你怎樣發現和解決競爭?

這是一道出現在多線程面試的高級階段的問題。大多數的面試官會問最近你遇到的競爭條件,以及你是怎麼解決的。有些時間他們會寫簡單的代碼,然後讓你檢測出代碼的競爭條件。可以參考我之前發佈的關於Java競爭條件的文章。在我看來這是最好的java線程面試問題之一,它可以確切的檢測候選者解決競爭條件的經驗,or writing code which is free of data race or anyother race condition。關於這方面最好的書是《Concurrency practices in Java》。

10、 你將如何使用threaddump?你將如何分析Thread dump?

在UNIX中你可以使用kill -3,然後thread dump將會打印日誌,在windows中你可以使用”CTRL+Break”。非常簡單和專業的線程面試問題,但是如果他問你怎樣分析它,就會很棘手。

11、爲什麼我們調用start()方法時會執行run()方法,爲什麼我們不能直接調用run()方法?

這是另一個非常經典的java多線程面試問題。這也是我剛開始寫線程程序時候的困惑。現在這個問題通常在電話面試或者是在初中級Java面試的第一輪被問到。這個問題的回答應該是這樣的,當你調用start()方法時你將創建新的線程,並且執行在run()方法裏的代碼。但是如果你直接調用run()方法,它不會創建新的線程也不會執行調用線程的代碼。閱讀我之前寫的《start與run方法的區別》這篇文章來獲得更多信息。

12、Java中你怎樣喚醒一個阻塞的線程?

這是個關於線程和阻塞的棘手的問題,它有很多解決方法。如果線程遇到了IO阻塞,我並且不認爲有一種方法可以中止線程。如果線程因爲調用wait()、sleep()、或者join()方法而導致的阻塞,你可以中斷線程,並且通過拋出InterruptedException來喚醒它。我之前寫的《How to deal with blocking methods in java》有很多關於處理線程阻塞的信息。

13、在Java中CycliBarriar和CountdownLatch有什麼區別?

這個線程問題主要用來檢測你是否熟悉JDK5中的併發包。這兩個的區別是CyclicBarrier可以重複使用已經通過的障礙,而CountdownLatch不能重複使用。

14、什麼是不可變對象,它對寫併發應用有什麼幫助?

另一個多線程經典面試問題,並不直接跟線程有關,但間接幫助很多。這個java面試問題可以變的非常棘手,如果他要求你寫一個不可變對象,或者問你爲什麼String是不可變的。

15、你在多線程環境中遇到的常見的問題是什麼?你是怎麼解決它的?

多線程和併發程序中常遇到的有Memory-interface、競爭條件、死鎖、活鎖和飢餓。問題是沒有止境的,如果你弄錯了,將很難發現和調試。這是大多數基於面試的,而不是基於實際應用的Java線程問題。

HashMap(21題)

1、HashMap 的數據結構?

A:哈希表結構(鏈表散列:數組+鏈表)實現,結合數組和鏈表的優點。當鏈表長度超過 8 時,鏈表轉換爲紅黑樹。

transient Node<K,V>[] table;

2、HashMap 的工作原理?

HashMap 底層是 hash 數組和單向鏈表實現,數組中的每個元素都是鏈表,由 Node 內部類(實現 Map.Entry接口)實現,HashMap 通過 put & get 方法存儲和獲取對象。

存儲對象時,將 K/V 鍵值傳給 put() 方法:

①、調用 hash(K) 方法計算 K 的 hash 值,然後結合數組長度,計算得數組下標;
②、調整數組大小(當容器中的元素個數大於 capacity * loadfactor 時,容器會進行擴容resize 爲 2n);
③、i.如果 K 的 hash 值在 HashMap 中不存在,則執行插入,若存在,則發生碰撞;ii.如果 K 的 hash 值在 HashMap 中存在,且它們兩者 equals 返回 true,則更新鍵值對;iii. 如果 K 的 hash 值在 HashMap 中存在,且它們兩者 equals 返回 false,則插入鏈表的尾部(尾插法)或者紅黑樹中(樹的添加方式)。(JDK 1.7 之前使用頭插法、JDK 1.8 使用尾插法)(注意:當碰撞導致鏈表大於 TREEIFY_THRESHOLD = 8 時,就把鏈表轉換成紅黑樹)

獲取對象時,將 K 傳給 get() 方法:

①、調用 hash(K) 方法(計算 K 的 hash 值)從而獲取該鍵值所在鏈表的數組下標;
②、順序遍歷鏈表,equals()方法查找相同 Node 鏈表中 K 值對應的 V 值。

hashCode 是定位的,存儲位置;
equals是定性的,比較兩者是否相等。

3、當兩個對象的 hashCode 相同會發生什麼?

因爲 hashCode 相同,不一定就是相等的(equals方法比較),所以兩個對象所在數組的下標相同,"碰撞"就此發生。又因爲 HashMap 使用鏈表存儲對象,這個 Node 會存儲到鏈表中。

4、你知道 hash 的實現嗎?爲什麼要這樣實現?

JDK 1.8 中,是通過 hashCode() 的高 16 位異或低 16 位實現的:(h = k.hashCode()) ^ (h >>> 16),主要是從速度,功效和質量來考慮的,減少系統的開銷,也不會造成因爲高位沒有參與下標的計算,從而引起的碰撞。

5、爲什麼要用異或運算符?

保證了對象的 hashCode 的 32 位值只要有一位發生改變,整個 hash() 返回值就會改變。儘可能的減少碰撞。

6、HashMap 的 table 的容量如何確定?loadFactor 是什麼?該容量如何變化?這種變化會帶來什麼問題?

①、table 數組大小是由 capacity 這個參數確定的,默認是16,也可以構造時傳入,最大限制是1<<30;
②、loadFactor 是裝載因子,主要目的是用來確認table 數組是否需要動態擴展,默認值是0.75,比如table 數組大小爲 16,裝載因子爲 0.75 時,threshold 就是12,當 table 的實際大小超過 12 時,table就需要動態擴容;
③、擴容時,調用 resize() 方法,將 table 長度變爲原來的兩倍(注意是 table 長度,而不是 threshold)
④、如果數據很大的情況下,擴展時將會帶來性能的損失,在性能要求很高的地方,這種損失很可能很致命。

7、HashMap中put方法的過程?

A:調用哈希函數獲取Key對應的hash值,再計算其數組下標;
如果沒有出現哈希衝突,則直接放入數組;如果出現哈希衝突,則以鏈表的方式放在鏈表後面;
如果鏈表長度超過閥值( TREEIFY THRESHOLD==8),就把鏈表轉成紅黑樹,鏈表長度低於6,就把紅黑樹轉回鏈表;
如果結點的key已經存在,則替換其value即可;
如果集合中的鍵值對大於12,調用resize方法進行數組擴容。

8、數組擴容的過程?

創建一個新的數組,其容量爲舊數組的兩倍,並重新計算舊數組中結點的存儲位置。結點在新數組中的位置只有兩種,原下標位置或原下標+舊數組的大小。

9、拉鍊法導致的鏈表過深問題爲什麼不用二叉查找樹代替,而選擇紅黑樹?爲什麼不一直使用紅黑樹?

之所以選擇紅黑樹是爲了解決二叉查找樹的缺陷,二叉查找樹在特殊情況下會變成一條線性結構(這就跟原來使用鏈表結構一樣了,造成很深的問題),遍歷查找會非常慢。
而紅黑樹在插入新數據後可能需要通過左旋,右旋、變色這些操作來保持平衡,引入紅黑樹就是爲了查找數據快,解決鏈表查詢深度的問題,我們知道紅黑樹屬於平衡二叉樹,但是爲了保持“平衡”是需要付出代價的,但是該代價所損耗的資源要比遍歷線性鏈表要少,所以當長度大於8的時候,會使用紅黑樹,如果鏈表長度很短的話,根本不需要引入紅黑樹,引入反而會慢。

10、說說你對紅黑樹的見解?

每個節點非紅即黑
根節點總是黑色的
如果節點是紅色的,則它的子節點必須是黑色的(反之不一定)
每個葉子節點都是黑色的空節點(NIL節點)
從根節點到葉節點或空子節點的每條路徑,必須包含相同數目的黑色節點(即相同的黑色高度)

11、jdk8中對HashMap做了哪些改變?

在java 1.8中,如果鏈表的長度超過了8,那麼鏈表將轉換爲紅黑樹。(桶的數量必須大於64,小於64的時候只會擴容,當容量達到64時纔可以樹化);
發生hash碰撞時,java 1.7 會在鏈表的頭部插入,而java 1.8會在鏈表的尾部插入;
在java 1.8中,Entry被Node替代(換了一個馬甲)。

12、HashMap,LinkedHashMap,TreeMap 有什麼區別?

LinkedHashMap 保存了記錄的插入順序,在用 Iterator 遍歷時,先取到的記錄肯定是先插入的;遍歷比 HashMap 慢;
TreeMap 實現 SortMap 接口,能夠把它保存的記錄根據鍵排序(默認按鍵值升序排序,也可以指定排序的比較器)

13、HashMap & TreeMap & LinkedHashMap 使用場景?

一般情況下,使用最多的是 HashMap。
HashMap:在 Map 中插入、刪除和定位元素時;
TreeMap:在需要按自然順序或自定義順序遍歷鍵的情況下;
LinkedHashMap:在需要輸出的順序和輸入的順序相同的情況下。

14、HashMap 和 HashTable 有什麼區別?

①、HashMap 是線程不安全的,HashTable 是線程安全的;
②、由於線程安全,所以 HashTable 的效率比不上 HashMap;
③、HashMap最多隻允許一條記錄的鍵爲null,允許多條記錄的值爲null,而 HashTable不允許;
④、HashMap 默認初始化數組的大小爲16,HashTable 爲 11,前者擴容時,擴大兩倍,後者擴大兩倍+1;
⑤、HashMap 需要重新計算 hash 值,而 HashTable 直接使用對象的 hashCode

15、Java 中的另一個線程安全的與 HashMap 極其類似的類是什麼?同樣是線程安全,它與 HashTable 在線程同步上有什麼不同?

ConcurrentHashMap 類(是 Java併發包 java.util.concurrent 中提供的一個線程安全且高效的 HashMap 實現)。
HashTable 是使用 synchronize 關鍵字加鎖的原理(就是對對象加鎖);
而針對 ConcurrentHashMap,在 JDK 1.7 中採用分段鎖的方式;JDK 1.8 中直接採用了CAS(無鎖算法)+ synchronized。

16、HashMap & ConcurrentHashMap 的區別?

除了加鎖,原理上無太大區別。另外,HashMap 的鍵值對允許有null,但是ConCurrentHashMap 都不允許。

17、爲什麼 ConcurrentHashMap 比 HashTable 效率要高?

HashTable 使用一把鎖(鎖住整個鏈表結構)處理併發問題,多個線程競爭一把鎖,容易阻塞;
ConcurrentHashMap:

JDK 1.7 中使用分段鎖(ReentrantLock + Segment + HashEntry),相當於把一個 HashMap 分成多個段,每段分配一把鎖,這樣支持多線程訪問。鎖粒度:基於 Segment,包含多個 HashEntry。
JDK 1.8 中使用 CAS + synchronized + Node + 紅黑樹。鎖粒度:Node(首結點)(實現 Map.Entry)。鎖粒度降低了。

18、針對 ConcurrentHashMap 鎖機制具體分析(JDK 1.7 VS JDK 1.8)?

JDK 1.7 中,採用分段鎖的機制,實現併發的更新操作,底層採用數組+鏈表的存儲結構,包括兩個核心靜態內部類 Segment 和 HashEntry。
①、Segment 繼承 ReentrantLock(重入鎖) 用來充當鎖的角色,每個 Segment 對象守護每個散列映射表的若干個桶;
②、HashEntry 用來封裝映射表的鍵-值對;
③、每個桶是由若干個 HashEntry 對象鏈接起來的鏈表
在這裏插入圖片描述
JDK 1.8 中,採用Node + CAS + Synchronized來保證併發安全。取消類 Segment,直接用 table 數組存儲鍵值對;當 HashEntry 對象組成的鏈表長度超過 TREEIFY_THRESHOLD 時,鏈表轉換爲紅黑樹,提升性能。底層變更爲數組 + 鏈表 + 紅黑樹。
在這裏插入圖片描述

19、ConcurrentHashMap 在 JDK 1.8 中,爲什麼要使用內置鎖 synchronized 來代替重入鎖 ReentrantLock?

①、粒度降低了;
②、JVM 開發團隊沒有放棄 synchronized,而且基於 JVM 的 synchronized 優化空間更大,更加自然。
③、在大量的數據操作下,對於 JVM 的內存壓力,基於 API 的 ReentrantLock 會開銷更多的內存。
20.ConcurrentHashMap 簡單介紹?
①、重要的常量:

private transient volatile int sizeCtl;

當爲負數時,-1 表示正在初始化,-N 表示 N - 1 個線程正在進行擴容;

當爲 0 時,表示 table 還沒有初始化;

當爲其他正數時,表示初始化或者下一次進行擴容的大小。

②、數據結構:

Node 是存儲結構的基本單元,繼承 HashMap 中的 Entry,用於存儲數據;

TreeNode 繼承 Node,但是數據結構換成了二叉樹結構,是紅黑樹的存儲結構,用於紅黑樹中存儲數據;

TreeBin 是封裝 TreeNode 的容器,提供轉換紅黑樹的一些條件和鎖的控制。

③、存儲對象時(put() 方法):

如果沒有初始化,就調用 initTable() 方法來進行初始化;

如果沒有 hash 衝突就直接 CAS 無鎖插入;

如果需要擴容,就先進行擴容;

如果存在 hash 衝突,就加鎖來保證線程安全,兩種情況:一種是鏈表形式就直接遍歷到尾端插入,一種是紅黑樹就按照紅黑樹結構插入;

如果該鏈表的數量大於閥值 8,就要先轉換成紅黑樹的結構,break 再一次進入循環;如果添加成功就調用 addCount() 方法統計 size,並且檢查是否需要擴容。

④、擴容方法 transfer():默認容量爲 16,擴容時,容量變爲原來的兩倍。
helpTransfer():調用多個工作線程一起幫助進行擴容,這樣的效率就會更高。
⑤、獲取對象時(get()方法):

計算 hash 值,定位到該 table 索引位置,如果是首結點符合就返回;

如果遇到擴容時,會調用標記正在擴容結點 ForwardingNode.find()方法,查找該結點,匹配就返回;

以上都不符合的話,就往下遍歷結點,匹配就返回,否則最後就返回 null。

21、ConcurrentHashMap 的併發度是什麼?

程序運行時能夠同時更新 ConccurentHashMap 且不產生鎖競爭的最大線程數。默認爲 16,且可以在構造函數中設置。
當用戶設置併發度時,ConcurrentHashMap 會使用大於等於該值的最小2冪指數作爲實際併發度(假如用戶設置併發度爲17,實際併發度則爲32)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章