Java面試必備八股文
一、Java基礎(chǔ)篇

二、多線程篇

三、JVM篇

四、MYSQL篇

五、Redis系列

六、Spring系列

七、分布式

一、Java基礎(chǔ)篇
1.1)Java有哪幾種數(shù)據(jù)類型
基本數(shù)據(jù)類型:byte(1字節(jié)) short(2字節(jié)) int(4字節(jié)) long(8字節(jié)) float(4字節(jié)) double(8字節(jié)) char(2字節(jié)) boolean(1字節(jié))
引用數(shù)據(jù)類型:String 類 接口 抽象類 枚舉 數(shù)組
1.2)JVM、JRE和JDK的關(guān)系
JVM指的是Java的虛擬機(jī),Java程序需要運(yùn)行在虛擬機(jī)上,不同的平臺都有自己的虛擬機(jī),所以Java語言實(shí)現(xiàn)了跨平臺。
JRE指的是Java的運(yùn)行時(shí)環(huán)境,包括需要的大量的類庫和Java的虛擬機(jī)。
JDK指的運(yùn)行時(shí)候的需要的一些工具類和運(yùn)行時(shí)環(huán)境,比如包括javac.exe ,javadoxc.exe 一系列用于編譯字節(jié)碼工具 打包文檔的工具
1.3)Switch支持的數(shù)據(jù)類型?
jdk1.5之前 byte、short、int、char
jdk5 ~ jdk1.7 加入了enum
jdk1.7之后 加入了String
*注意 不支持long類型
1.4)為什么float=3.4報(bào)錯(cuò)
3.4 默認(rèn)是浮點(diǎn)double類型的,如果賦值給float是向下轉(zhuǎn)型,會出現(xiàn)精度缺失,,需要強(qiáng)制轉(zhuǎn)換
1.5)final 有什么用?
用于修飾類,方法,變量(屬性):
如果修飾類,就是不能夠繼承
如果修飾方法就是不能夠重寫
修飾變量:修飾的變量是不能夠修改的,如果是基本類型那么就是數(shù)值不能修改,如果是引用類型就是地址不能夠修改。
成員變量:必須實(shí)現(xiàn)賦值
局部變量:聲明的時(shí)候可以不賦值,后續(xù)允許一次賦值,賦值之后不能夠修改
1.6)String有哪些特性
首先String是 private final char[]
數(shù)組 所以長度不可變
final 不能夠被繼承
private 內(nèi)容不能夠修改
1.6)Stringbuffer和 Stringbuilder有什么不同?
首先他們都是繼承AbstractStringBuilder,相對于String來說是可變的字符串,只不過Stringbuffer加了synchronized所以是線程安全的,而Stringbuilder是線程非安全的
1.7)== 和 equals 的區(qū)別
== 默認(rèn)是比較兩個(gè)對象的引用地址的,而equals默認(rèn)情況是==比較規(guī)則,但是不同的類會重寫掉Object類的equals從而改變了equals的比較規(guī)則,比如String類的equals方法就是比較他們兩個(gè)的內(nèi)容是否相等
1.8)hashCode和equals
兩個(gè)對象相等他們的hashCode和equals一定相等,但是hashCode相等的兩個(gè)對象未必相等
1.9)方法重載和方法重寫區(qū)別
實(shí)現(xiàn)方式:方法重載是在一個(gè)類當(dāng)中;而方法重寫是在父子類中實(shí)現(xiàn)的
方法重載是方法名字相同,參數(shù)個(gè)數(shù),參數(shù)類型不同。和訪問修飾符 和 返回類型無關(guān);方法重寫是方法名字相同,參數(shù)個(gè)數(shù),參數(shù)類型必須相同,子類的返回值,拋出的異常必須小于等于父類。子類的訪問修飾符大于等于父類。
1.10)面向?qū)ο蠛兔嫦蜻^程的區(qū)別
面向過程:就是具體化的一步一步的去實(shí)現(xiàn),優(yōu)點(diǎn) 就是性能比較快因?yàn)椴恍枰M(jìn)行實(shí)例化。 缺點(diǎn)就是不容易進(jìn)行維護(hù),擴(kuò)展和復(fù)用
面向?qū)ο螅阂驗(yàn)槊嫦驅(qū)ο蟮娜筇攸c(diǎn)就是 封裝、繼承和多態(tài)
封裝就是增加了代碼的安全性,開發(fā)者不用在乎細(xì)節(jié),只知道如何使用就可以了
繼承就是代碼的復(fù)用,結(jié)果設(shè)計(jì)模式就可以達(dá)到已于擴(kuò)展和復(fù)用的特點(diǎn)
多態(tài)使用比較靈活,可以設(shè)計(jì)出低耦合的系統(tǒng)從而便于維護(hù)
1.11)ArrayList 和LinkedList 的區(qū)別
數(shù)據(jù)結(jié)構(gòu):
ArrayList是底層是數(shù)組存儲的,用于一大塊連續(xù)的空間進(jìn)行存儲的,默認(rèn)的初始容量是10 一般情況下當(dāng)容量不夠的時(shí)候會進(jìn)行1.5 倍的一個(gè)擴(kuò)容,因?yàn)槭菙?shù)組形式長度是不變的,所以在擴(kuò)容的時(shí)候需要數(shù)據(jù)的搬遷,從而導(dǎo)致頻繁的擴(kuò)容會導(dǎo)致性能效率,所以我們在使用時(shí)候最好指定好大小。 因?yàn)橛兴饕栽诓檎?、修改?shù)據(jù)時(shí)候都是O(1)時(shí)間復(fù)雜度,而數(shù)據(jù)的增加刪除確要移動后面的元素,所以時(shí)間復(fù)雜度是O(n)。
LinkedList是雙向鏈表的形式存儲的,可以充分用碎片化的空間進(jìn)行存儲,查找,修改數(shù)據(jù)的時(shí)間復(fù)雜度都是O(n),因?yàn)槎际且闅v整個(gè)鏈表。插入和刪除除非是在指定的指針后面下進(jìn)行插入和刪除不然還是要遍歷整個(gè)鏈表。所以說我們通常使用ArrayList進(jìn)行數(shù)據(jù)存儲。
1.12)說一說HashMap數(shù)據(jù)結(jié)構(gòu)
HashMap通常是key-value鍵值對的方式進(jìn)行存儲,在JDK1.7的時(shí)候就是通過數(shù)組+鏈表的形式存儲,每個(gè)數(shù)組存儲的是一個(gè)一個(gè)的Entity組成的鏈表,這些鏈表上的數(shù)字都有一個(gè)特點(diǎn)就是Hash值相同,當(dāng)數(shù)據(jù)量比較大的時(shí)候鏈表的長度比較大,這個(gè)時(shí)候查找的時(shí)間復(fù)雜度就比較大所以。在JDK1.8的時(shí)候引入了紅黑樹概念,當(dāng)一個(gè)鏈表上的長度大于8的時(shí)候并且數(shù)組長度大于64這個(gè)時(shí)候鏈表就自動轉(zhuǎn)換成紅黑樹,如果數(shù)組長度沒有達(dá)到64那么此時(shí)就對數(shù)組進(jìn)行1倍的擴(kuò)容。
而且在JDK1.7和1.8還有一個(gè)改動就是插入的方式,由于JDK1.7的時(shí)候插入方式是頭插法,在多線程的方式下會出選循環(huán)鏈表情況,所以1.8改為了尾插法方式。
擴(kuò)容有三個(gè)地方
初始化數(shù)組長度為0
元素個(gè)數(shù)達(dá)到了 最大個(gè)數(shù)*0.75
單個(gè)鏈表的長度大于8并且數(shù)組長度大于64
HashMap是線程不安全的,如果要保證線程安全那么可以使用ConcurrentHashMap
1.13)ConcurrentHashMap原如何保證的線程安全?
JDK 1.7 時(shí)候是使用分成16個(gè)Seagment段,每個(gè)Seagment里面存儲著一個(gè)HashMap和一個(gè)鎖,所以說1.7能支持最大的并發(fā)量也就是16個(gè)線程
JDK1.8采用的CAS + Synchronized,每次插入的時(shí)候判斷是否是第一次插入,是就通過CAS插入,然后判斷f.hash是否=-1,如果是的那么其他線程在擴(kuò)容,當(dāng)前線程也會參與擴(kuò)容;刪除方法用了synchronized修飾,保障并發(fā)下刪除元素的安全
1.14)抽象類和接口的區(qū)別
繼承:抽象類是只能單繼承的,接口是可以多實(shí)現(xiàn)的
屬性:屬性修飾符也不一樣,抽象累可以是public protect 默認(rèn)不寫 還有final 修飾,但是接口只能是public static final修飾
方法:抽象類既可以抽象的方法,也可以具體的方法,接口只有抽象的方法,而且子類必須實(shí)現(xiàn)
二、多線程篇
2.1) *簡述線程、進(jìn)程的基本概念。以及他們之間關(guān)系是什么
進(jìn)程:是程序的一次執(zhí)行的過程,是系統(tǒng)運(yùn)行的基本單位,其中包含著程序運(yùn)行過程中一些內(nèi)存空間和系統(tǒng)資源。進(jìn)程在運(yùn)行過程中都是相互獨(dú)立,但是線程之間運(yùn)行可以相互影響。
線程:是進(jìn)程的更小的單位。一個(gè)進(jìn)程中包含著多個(gè)線程。和進(jìn)程不同的是線程是共享著進(jìn)程中的內(nèi)存空間和系統(tǒng)資源的。所以在切換線程過程中開銷要比進(jìn)程小的多。
2.2)線程池
*線程池有七個(gè)參數(shù)?
corePoolSize: 線程池核心線程數(shù)最大值
maximumPoolSize: 線程池最大線程數(shù)大小
keepAliveTime: 線程池中非核心線程空閑的存活時(shí)間大小
unit: 線程空閑存活時(shí)間單位
workQueue: 存放任務(wù)的阻塞隊(duì)列
threadFactory: 用于設(shè)置創(chuàng)建線程的工廠,可以給創(chuàng)建的線程設(shè)置有意義的名字,可方便排查問題。
handler: 線程池的飽和策略事件,主要有四種類型。
*線程池的執(zhí)行過程?
當(dāng)提交一個(gè)線程執(zhí)行任務(wù)時(shí)候 ,先看有沒有空閑的核心線程如果有就執(zhí)行,如果沒有就看阻塞隊(duì)列中有沒有滿的狀態(tài),如果沒有放滿則放入隊(duì)列中,否則就直接看線程數(shù)量是否大于非核心線程數(shù)量,如果沒有就直接創(chuàng)建一個(gè)非核心線程,否則就是按照指定的拒絕策略給處理。。如果一個(gè)非核心線程在某個(gè)一段時(shí)間類是空閑的那么線程池就會把這個(gè)線程自動銷毀掉。
四種拒絕策略
AbortPolicy(拋出一個(gè)異常,默認(rèn)的)
DiscardPolicy(直接丟棄任務(wù))
DiscardOldestPolicy(丟棄隊(duì)列里最老的任務(wù),將當(dāng)前這個(gè)任務(wù)繼續(xù)提交給線程池)
CallerRunsPolicy(交給線程池調(diào)用所在的線程進(jìn)行處理)
五種阻塞隊(duì)列 (5)
ArrayBlockingQueue(有界隊(duì)列)是一個(gè)用數(shù)組實(shí)現(xiàn)的有界阻塞隊(duì)列,按FIFO排序量。
LinkedBlockingQueue(可設(shè)置容量隊(duì)列)基于鏈表結(jié)構(gòu)的阻塞隊(duì)列,按FIFO排序任務(wù),容量可以選擇進(jìn)行設(shè)置,不設(shè)置的話,將是一個(gè)無邊界的阻塞隊(duì)列,最大長度為Integer.MAX_VALUE,吞吐量通常要高于ArrayBlockingQuene;newFixedThreadPool線程池使用了這個(gè)隊(duì)列
DelayQueue(延遲隊(duì)列)是一個(gè)任務(wù)定時(shí)周期的延遲執(zhí)行的隊(duì)列。根據(jù)指定的執(zhí)行時(shí)間從小到大排序,否則根據(jù)插入到隊(duì)列的先后排序。newScheduledThreadPool線程池使用了這個(gè)隊(duì)列。
PriorityBlockingQueue(優(yōu)先級隊(duì)列)是具有優(yōu)先級的無界阻塞隊(duì)列;
SynchronousQueue(同步隊(duì)列)一個(gè)不存儲元素的阻塞隊(duì)列,每個(gè)插入操作必須等到另一個(gè)線程調(diào)用移除操作,否則插入操作一直處于阻塞狀態(tài),吞吐量通常要高于LinkedBlockingQuene,newCachedThreadPool線程池使用了這個(gè)隊(duì)列。
五種創(chuàng)建線程的方式池的方式:
newFixedThreadPool (固定數(shù)目線程的線程池)
newCachedThreadPool(可緩存線程的線程池)
newSingleThreadExecutor(單線程的線程池)
newScheduledThreadPool(定時(shí)及周期執(zhí)行的線程池)
new ThreadPoolExecutor() 自定義的方式創(chuàng)建
使用無界隊(duì)列的線程池會導(dǎo)致內(nèi)存飆升嗎?
會的,newFixedThreadPool使用了無界的阻塞隊(duì)列LinkedBlockingQueue,如果線程獲取一個(gè)任務(wù)后,任務(wù)的執(zhí)行時(shí)間比較長(比如,上面demo設(shè)置了10秒),會導(dǎo)致隊(duì)列的任務(wù)越積越多,導(dǎo)致機(jī)器內(nèi)存使用不停飆升,最終導(dǎo)致OOM。
2.2)線程、進(jìn)程、協(xié)程的區(qū)別
進(jìn)程:進(jìn)程是操作系統(tǒng)分配系統(tǒng)資源和內(nèi)存空間的最小單位。進(jìn)程是獨(dú)立的一塊空間,所以資源和內(nèi)存空間的切換是特別消耗資源的。
線程:線程也叫做輕量級的進(jìn)程,是操作系統(tǒng)調(diào)用執(zhí)行的最小單位。線程的資源是依賴于他的父進(jìn)程,所以他的資源是共享的,線程的切換需要轉(zhuǎn)換到內(nèi)核態(tài)開銷相對于小一些。
協(xié)程:協(xié)程是一種輕量級的線程,協(xié)程是直接在用戶態(tài)就可以控制,具有對內(nèi)核態(tài)來說是不可見的,所以協(xié)程的上下文切換更加的節(jié)約資源消耗。
2.3)什么是上下文的切換
上下文的切換指的是CPU寄存器和程序計(jì)數(shù)器存儲的數(shù)據(jù)進(jìn)行切換,而內(nèi)存數(shù)據(jù)的改變只有在內(nèi)核態(tài)才能進(jìn)行。所以上下文切換對系統(tǒng)來說是巨大的成本。
先是暫停原有的進(jìn)程,保存好進(jìn)程中寄存器中的數(shù)據(jù)在某個(gè)地方
內(nèi)存獲取中下一個(gè)進(jìn)程上下文,存儲到寄存器中
2.4)Java有幾種創(chuàng)建線程的方式
new 一個(gè)Thread
繼承Runnable類
使用Callable
使用線程池
2.5)sleep和wait區(qū)別
sleep 屬于 Thread類,wait屬于Object類
wait會釋放掉鎖,sleep不會釋放鎖
wait必須在同步代碼方法和同步代碼塊中,sleep沒有這一個(gè)限制
wait()要調(diào)用notify()或notifyall()喚醒,sleep()自動喚醒
2.6)Java的內(nèi)存模型
JMM屏蔽了各種硬件和操作系統(tǒng)的內(nèi)存訪問差異,實(shí)現(xiàn)讓Java程序在各平臺都能夠達(dá)到一致的內(nèi)存訪問效果,它定義了Java如何將程序中的變量在主存中讀取
具體定義:所有變量都在主存中,主存是線程的共享區(qū)域,每個(gè)線程都有自己獨(dú)有的工作內(nèi)存,線程想要操作變量必須從主存中copy一份到自己的工作區(qū)域,每個(gè)獨(dú)立內(nèi)存區(qū)域相互隔離。
所以這個(gè)時(shí)候讀寫存在延遲,且不是原子操作,所以就出現(xiàn)了一些列的線程安全操作。比如 原子性、可見性、有序性。

2.6)保證并發(fā)安全的三大特性?
原子性:一次或多次操作在執(zhí)行期間不被其他線程影響
可見性:當(dāng)一個(gè)線程在工作內(nèi)存修改了變量,其他線程能立刻知道**(利用內(nèi)存原子操作解決或者內(nèi)存屏障或者lock前綴)**
有序性:JVM對指令的優(yōu)化會讓指令執(zhí)行順序改變,有序性是禁止指令重排**(內(nèi)存屏障)**
2.7)ThreadLocal原理
每一個(gè)線程創(chuàng)建變量的副本,不同線程間是不可見的,保證線程安全。每個(gè)線程都維護(hù)一個(gè)ThreadLocalMap,key為threadlocal實(shí)例,value是保存的副本。
但是使用ThreadLocal會存在內(nèi)存泄漏問題,因?yàn)閗ey 是弱引用,value是強(qiáng)引用,每次GC時(shí)key都會回收,而value不會被回收。所以為了解決內(nèi)存泄漏問題,可以在每次使用完后刪除value或者使用static修飾ThreadLocal,可以隨時(shí)的獲取value。
第二個(gè)會出現(xiàn)內(nèi)存溢出問題,如果使用的是線程池的方式去使用ThreadLocal話,那么就會出現(xiàn)存儲的value一直存在,這樣就一直堆積。

2.8)什么是CAS鎖
CAS鎖可以保證原子性,思想是更新內(nèi)存是會判斷其內(nèi)存的值是否被修改了,如果沒有被修改就直接更新,如果被修改了,就得重新去獲取值,知道更新為止。這樣是有缺點(diǎn)的:
只能支持一個(gè)變量的原子操作,不能保證整個(gè)代碼塊的原子操作
CAS頻繁的失敗會造成CPU的開銷打
會出現(xiàn)ABA問題
解決ABA問題,可以通過加入版本控制
