作者:GarbageCollection
來(lái)源:牛客網(wǎng)
基礎(chǔ)篇
一、面向?qū)ο笕筇匦?/p>
封裝、繼承、多態(tài)
二、集合
ArrayList和LinkedList區(qū)別:
ArrayList基于動(dòng)態(tài)數(shù)組實(shí)現(xiàn)。支持隨機(jī)訪(fǎng)問(wèn),但插入刪除的代價(jià)很高,需要移動(dòng)大量元素
LinkedList基于雙向鏈表實(shí)現(xiàn)。不支持隨機(jī)訪(fǎng)問(wèn),但插入刪除方便,只需要改變指針
HashMap實(shí)現(xiàn)原理:
HashMap的內(nèi)部存儲(chǔ)結(jié)構(gòu)其實(shí)是數(shù)組+鏈表+樹(shù)。當(dāng)實(shí)例化一個(gè)HashMap時(shí),會(huì)初始化默認(rèn)長(zhǎng)度(initialCapacity)和加載因子(loadFactor),系統(tǒng)會(huì)創(chuàng)建一個(gè)長(zhǎng)度為默認(rèn)長(zhǎng)度(initialCapacity)的Node數(shù)組,這個(gè)長(zhǎng)度在哈希表中被稱(chēng)為容量(Capacity),在這個(gè)數(shù)組中可以存放元素的位置稱(chēng)之為“桶”(bucket),每個(gè)桶(bucket)都有自己的索引,系統(tǒng)可以根據(jù)索引快速的查找桶(bucket)中的元素。
每個(gè)桶(bucket)中存儲(chǔ)一個(gè)元素,即一個(gè)Node對(duì)象。每一個(gè)Node對(duì)象可以帶一個(gè)引用變量next,用于指向下一個(gè)元素。因此在一個(gè)桶(bucket)中,就有可能生成一個(gè)Node鏈,也可能是一個(gè)個(gè)TreeNode對(duì)象,每個(gè)TreeNode對(duì)象可以有兩個(gè)葉子節(jié)點(diǎn)left和right。因此在一個(gè)桶(bucket)中就有可能生成一個(gè)TreeNode樹(shù)。新添加的元素作為鏈表的last或樹(shù)的葉子節(jié)點(diǎn)。
HashMap擴(kuò)容:當(dāng)HashMap中的元素個(gè)數(shù)超過(guò)數(shù)組大小(initialCapacity)加載因子(loadFactor)時(shí),就會(huì)進(jìn)行數(shù)組擴(kuò)容,加載因子(loadFactor)的默認(rèn)值是0.75。默認(rèn)情況下,數(shù)組大小(initialCapacity)為16,那么當(dāng)HashMap中元素個(gè)數(shù)超過(guò)160.75=12(此值為代碼中的threshold值,即臨界值)時(shí),就把數(shù)組的大小擴(kuò)展為2*16=32,即擴(kuò)大一倍,然后重新計(jì)算每個(gè)元素在數(shù)組中的位置,這是一個(gè)非常消耗性能的操作,所以如果我們已經(jīng)能預(yù)知HashMap中元素的個(gè)數(shù),那么預(yù)設(shè)元素的個(gè)數(shù)能夠有效的提高其性能。
HashMap樹(shù)形化:當(dāng)HashMap中其中一個(gè)鏈的對(duì)象個(gè)數(shù)如果達(dá)到了8個(gè),此時(shí)如果capacity沒(méi)有達(dá)到64,那么HashMap會(huì)先擴(kuò)容解決。若達(dá)到64,那么這個(gè)鏈會(huì)轉(zhuǎn)化為樹(shù),節(jié)點(diǎn)類(lèi)型由Node變?yōu)門(mén)reeNode。如果對(duì)象被刪除后,下次resize方法時(shí)判斷樹(shù)的節(jié)點(diǎn)個(gè)數(shù)低于六個(gè),則會(huì)把樹(shù)轉(zhuǎn)化為鏈表。
JDK1.8相較于之前的變化
HashMap map = new HashMap();//默認(rèn)情況下,先不創(chuàng)建長(zhǎng)度為16的數(shù)組
當(dāng)首次調(diào)用map.put()時(shí),再創(chuàng)建長(zhǎng)度為16的數(shù)組
數(shù)組為Node類(lèi)型,在jdk7中稱(chēng)為Entry類(lèi)型
形成鏈表結(jié)構(gòu)時(shí),新添加的key-value對(duì)在鏈表的尾部(七上八下)
當(dāng)數(shù)組指定索引位置的鏈表長(zhǎng)度>8時(shí),且map中的數(shù)組的長(zhǎng)度> 64時(shí),此索引位置上的所有key-value對(duì)使用紅黑樹(shù)進(jìn)行存儲(chǔ)。
負(fù)載因子值的大小,對(duì)HashMap有什么影響?
負(fù)載因子的大小決定了HashMap的數(shù)據(jù)密度。
負(fù)載因子越大密度越大,發(fā)生碰撞的幾率越高,數(shù)組中的鏈表越容易長(zhǎng),成查詢(xún)或插入時(shí)的比較次數(shù)增多,性能會(huì)下降。
負(fù)載因子越小,就越容易觸發(fā)擴(kuò)容,數(shù)據(jù)密度也越小,意味著發(fā)生碰撞的幾率越小,數(shù)組中的鏈表也就越短,查詢(xún)和插入時(shí)比較的次數(shù)也越小,性能會(huì)更高。但是會(huì)浪費(fèi)一定的內(nèi)容空間。而且經(jīng)常擴(kuò)容也會(huì)影響性能,建議初始化預(yù)設(shè)大一點(diǎn)的空間。
按照其他語(yǔ)言的參考及研究經(jīng)驗(yàn),會(huì)考慮將負(fù)載因子設(shè)置為0.7~0.75,此時(shí)平均檢索長(zhǎng)度接近于常數(shù)。
談?wù)勀銓?duì)HashMap中put/get方法的認(rèn)識(shí)?如果了解再談?wù)凥ashMap的擴(kuò)容機(jī)制?默認(rèn)大小是多少?什么是負(fù)載因子(或填充比)?什么是吞吐臨界值(或閾值、threshold)?
put():
當(dāng)put(k,v)時(shí),創(chuàng)建一個(gè)長(zhǎng)度為16的Node數(shù)組
首先按照k所在類(lèi)的hashcode()計(jì)算其哈希值,用哈希值計(jì)算出k位于哪個(gè)桶
如果該桶為空,則直接插入
如果該桶不為空
如果該桶使用的是紅黑樹(shù),則調(diào)用紅黑樹(shù)的方法插入
用鏈?zhǔn)椒椒ú迦?。如果鏈的長(zhǎng)度達(dá)到臨界值,則把鏈表轉(zhuǎn)化為紅黑樹(shù)
如果桶中存在重復(fù)的k,則為該k替換新值
如果size大于閾值,則進(jìn)行擴(kuò)容
get():
通過(guò)哈希值找到該k映射的桶
桶上的k就是要查找的k,直接命中
桶上的 key 不是要查找的 key,則查看后續(xù)節(jié)點(diǎn):
如果后續(xù)節(jié)點(diǎn)是樹(shù)節(jié)點(diǎn),通過(guò)調(diào)用樹(shù)的方法查找該 key
如果后續(xù)節(jié)點(diǎn)是鏈?zhǔn)焦?jié)點(diǎn),則通過(guò)循環(huán)遍歷鏈查找該 key
擴(kuò)容機(jī)制:默認(rèn)容量為16,負(fù)載因子為0.75,如果使用容量超過(guò)16×0.75=12(閾值)時(shí),進(jìn)行擴(kuò)容,擴(kuò)容至一倍(2×16=32),并重新計(jì)算每個(gè)元素的位置
創(chuàng)建HashSet會(huì)創(chuàng)建一個(gè)初始值為16,負(fù)載因子為0.75的HashMap,key為add()的值,value為Object類(lèi)型常量
HashMap和HashTable區(qū)別
HashTable中的方法是synchronized的
HashMap可以插入鍵為null,HashTable不可以
HashMap不能保證對(duì)著時(shí)間的推移Map中的元素次序是不變的
HashTable直接使用對(duì)象的hashCode,HashMap要重新計(jì)算
AVL和紅黑樹(shù)的區(qū)別
AVL是嚴(yán)格的平衡樹(shù),在增加或刪除節(jié)點(diǎn)的時(shí)候,旋轉(zhuǎn)的次數(shù)比紅黑樹(shù)要多。紅黑樹(shù)用非嚴(yán)格的平衡來(lái)減少旋轉(zhuǎn)次數(shù)
紅黑樹(shù)根節(jié)點(diǎn)是黑色,葉子節(jié)點(diǎn)都為黑色且空,每一個(gè)紅色節(jié)點(diǎn)的兩個(gè)子節(jié)點(diǎn)都是黑色
AVL靠平衡因子旋轉(zhuǎn),紅黑樹(shù)靠節(jié)點(diǎn)顏色以及一些約定旋轉(zhuǎn)
AVL更適合搜索,紅黑樹(shù)更適合增刪
并發(fā)篇
一、java如何開(kāi)啟線(xiàn)程?如何保證線(xiàn)程安全?
線(xiàn)程和進(jìn)程的區(qū)別:進(jìn)程是操作系統(tǒng)進(jìn)行資源分配的最小單元,線(xiàn)程是操作系統(tǒng)進(jìn)行任務(wù)分配的最小單元,線(xiàn)程屬于進(jìn)程
進(jìn)程擁有資源而線(xiàn)程不擁有,線(xiàn)程可以訪(fǎng)問(wèn)隸屬進(jìn)程的資源
線(xiàn)程是獨(dú)立調(diào)度的基本單位,同一進(jìn)程中線(xiàn)程切換不會(huì)引起進(jìn)程切換,不通進(jìn)程中的線(xiàn)程切換會(huì)引起進(jìn)程切換
創(chuàng)建切換進(jìn)程會(huì)產(chǎn)生極大的系統(tǒng)開(kāi)銷(xiāo)(內(nèi)存、I/O、CPU),而線(xiàn)程不會(huì)(只需設(shè)置和保存少量寄存器)
線(xiàn)程可以通過(guò)同一進(jìn)程中的數(shù)據(jù)進(jìn)行通信,進(jìn)程間通信需要借助IPC
進(jìn)程間通信方式:
管道通信: 一種半雙工的通信方式,數(shù)據(jù)只能單向流動(dòng),且只能在具有親緣關(guān)系的進(jìn)程間使用。進(jìn)程的親緣關(guān)系通常指父子進(jìn)程關(guān)系
命名管道通信:與管道通信相似,但是它允許無(wú)親緣關(guān)系進(jìn)程間的通信
信號(hào)量: 是一個(gè)計(jì)數(shù)器,可以用來(lái)控制多個(gè)進(jìn)程對(duì)共享資源的訪(fǎng)問(wèn)。它常作為一種鎖機(jī)制,防止多個(gè)進(jìn)程訪(fǎng)問(wèn)同一資源。主要作為進(jìn)程間以及不同線(xiàn)程之間的同步手段
消息隊(duì)列:由消息產(chǎn)生的鏈表,存放在內(nèi)核中并由消息隊(duì)列標(biāo)識(shí)符標(biāo)識(shí)。其克服了信號(hào)量傳遞少、管道只能承載無(wú)格式字節(jié)流以及緩沖區(qū)受限等缺點(diǎn)
共享內(nèi)存:就是映射一段能被其他進(jìn)程所訪(fǎng)問(wèn)的內(nèi)存,這段共享內(nèi)存由一個(gè)進(jìn)程創(chuàng)建,但多個(gè)進(jìn)程可以訪(fǎng)問(wèn)。共享內(nèi)存是最快的IPC方式,它使很對(duì)其他進(jìn)程間通信方式運(yùn)行效率低而專(zhuān)門(mén)設(shè)計(jì)的。它往往與其他通信機(jī)制來(lái)配合使用來(lái)實(shí)現(xiàn)進(jìn)程間的同步和通信
套接字:可以用于本地進(jìn)程間通信和不同主機(jī)進(jìn)程間通信
線(xiàn)程通信方式:
共享內(nèi)存:線(xiàn)程之間通過(guò)讀寫(xiě)內(nèi)存中的公共狀態(tài)來(lái)隱式通信
消息傳遞:線(xiàn)程之間沒(méi)有公共狀態(tài),通過(guò)明確的發(fā)送信息來(lái)顯示的進(jìn)行通信
管道流
開(kāi)啟線(xiàn)程4種方式:
繼承Thread類(lèi),重寫(xiě)run方法
實(shí)現(xiàn)Runnable接口,實(shí)現(xiàn)run方法
實(shí)現(xiàn)Callable接口,實(shí)現(xiàn)call方法
創(chuàng)建線(xiàn)程池
保證線(xiàn)程安全:加鎖
JVM提供的鎖:Synchronized關(guān)鍵字
JDK提供的鎖:Lock
線(xiàn)程的狀態(tài)
新建:創(chuàng)建一個(gè)線(xiàn)程但還沒(méi)有調(diào)用start方法
就緒:調(diào)用start方法,進(jìn)入隊(duì)列等待cpu時(shí)間片,具備運(yùn)行條件,沒(méi)有被分配到cpu資源
運(yùn)行:就緒的線(xiàn)程獲取到cpu資源
阻塞:遇到鎖或人為掛起時(shí),讓cpu臨時(shí)中止自己的執(zhí)行
死亡:線(xiàn)程完成了它的任務(wù)或被提前強(qiáng)制性中止或出現(xiàn)異常導(dǎo)致結(jié)束
二、Volatile和Synchronized有什么區(qū)別? Volatile能不能保證線(xiàn)程安全?DCL(Double Check Lock)單例為什么要加Volatile?
Synchronized用來(lái)加鎖,Volatile保證變量的內(nèi)存可見(jiàn)性
不能。Volatile不能保證原子性
Volatile可以防止指令重排
三、JAVA線(xiàn)程鎖機(jī)制是怎樣的?偏向鎖、輕量級(jí)鎖、重量級(jí)鎖有什么區(qū)別?鎖機(jī)制是如何升級(jí)的?
JAVA的鎖就是在對(duì)象頭中記錄一個(gè)鎖狀態(tài)
JAVA的鎖機(jī)制就是根據(jù)資源競(jìng)爭(zhēng)的激烈程度不斷進(jìn)行鎖升級(jí)的過(guò)程

synchronized 鎖升級(jí)原理:在鎖對(duì)象的對(duì)象頭里面有一個(gè) threadid 字段,在第一次訪(fǎng)問(wèn) 的時(shí)候 threadid 為空,jvm 讓其持有偏向鎖,并將 threadid 設(shè)置為其線(xiàn)程 id,再次進(jìn) 入的時(shí)候會(huì)先判斷 threadid 是否與其線(xiàn)程 id 一致,如果一致則可以直接使用此對(duì)象, 如果不一致,則升級(jí)偏向鎖為輕量級(jí)鎖,通過(guò)自旋循環(huán)一定次數(shù)來(lái)獲取鎖,執(zhí)行一定次數(shù)之 后,如果還沒(méi)有正常獲取到要使用的對(duì)象,此時(shí)就會(huì)把鎖從輕量級(jí)升級(jí)為重量級(jí)鎖,此過(guò)程 就構(gòu)成了 synchronized 鎖的升級(jí)。 鎖的升級(jí)的目的:鎖升級(jí)是為了減低了鎖帶來(lái)的性能消耗。在 Java 6 之后優(yōu)化 synchronized 的實(shí)現(xiàn)方式,使用了偏向鎖升級(jí)為輕量級(jí)鎖再升級(jí)到重量級(jí)鎖的方式,從而減低了鎖帶來(lái)的性能消耗
四、談?wù)勀銓?duì)AQS的理解。AQS如何實(shí)現(xiàn)可重入鎖?
AQS(抽象隊(duì)列同步器)是一個(gè)JAVA線(xiàn)程同步框架,是JDK中很多鎖工具的核心實(shí)現(xiàn)框架。在AQS中,維護(hù)了一個(gè)信號(hào)量state和一個(gè)線(xiàn)程組成的雙向鏈表。其線(xiàn)程隊(duì)列就是用來(lái)給線(xiàn)程排隊(duì)的,而state就像是一個(gè)紅綠燈來(lái)控制線(xiàn)程等待或放行。在不同的場(chǎng)景下有不同的意義。
在可重入鎖下,state用來(lái)標(biāo)識(shí)加鎖的次數(shù)。0表示無(wú)鎖,每加一次鎖state加1,每釋放一次state減1。
五、CAS是什么?CAS底層原理?為什么不用Synchronized而用CAS?CAS缺點(diǎn)?ABA問(wèn)題?
CAS(CompareAndSwap,比較并交換):是一條CPU并發(fā)原語(yǔ),功能是判斷內(nèi)存某個(gè)位置的值是否為預(yù)期值,如果是則更改為新的值,這個(gè)過(guò)程是原子的
底層原理:JAVA的CAS操作都依賴(lài)UnSafe類(lèi)中的方法,UnSafe時(shí)CAS的核心類(lèi),其中含有大量的native方法,可以直接調(diào)用操作系統(tǒng)底層資源操作特定內(nèi)存中的數(shù)據(jù)
比較:Synchronized只允許有一個(gè)線(xiàn)程訪(fǎng)問(wèn),保證一致性但沒(méi)有提高并發(fā)性,而CAS既保證了一致性,又提高了并發(fā)性
CAS缺點(diǎn):
循環(huán)時(shí)間長(zhǎng),CPU開(kāi)銷(xiāo)大
只能保證一個(gè)共享變量的原子操作
ABA問(wèn)題:因?yàn)?CAS算法是在某一時(shí)刻取出內(nèi)存值然后在當(dāng)前的時(shí)刻進(jìn)行比較,中間存在一個(gè)時(shí)間差,在這個(gè)時(shí)間差里就可能會(huì)產(chǎn)生 ABA 問(wèn)題。
當(dāng)有兩個(gè)線(xiàn)程 T1 和 T2 從內(nèi)存中獲取到值A(chǔ),線(xiàn)程 T2 通過(guò)某些操作把內(nèi)存 值修改為B,然后又經(jīng)過(guò)某些操作將值修改為A,T2退出。線(xiàn)程 T1 進(jìn)行操作的時(shí)候 ,使用預(yù)期值同內(nèi)存中的值比較,此時(shí)均為A,修改成功退出。但是此時(shí)的A以及不是原先的A了
解決ABA問(wèn)題:
AtomicReference類(lèi),原子引用
AtomicStampedReference,時(shí)間戳原子引用
六、集合類(lèi)不安全,ConcurrentHashMap原理?
故障現(xiàn)象:java.util.ConcurrentModificationException
解決方案:以ArrayList為例
new Vector
Collections.synchronizedList()
CopyOnWriteArrayList(),寫(xiě)時(shí)拷貝,(map是ConcurrentHashMap(其原理是使用了分段鎖))
七、各種鎖
公平鎖與非公平鎖:ReentrantLock(默認(rèn)非公平鎖)
公平鎖:按申請(qǐng)鎖的順序來(lái)獲取鎖。排隊(duì),先來(lái)后到
非公平鎖:后申請(qǐng)鎖的線(xiàn)程有可能比先申請(qǐng)的線(xiàn)程優(yōu)先獲取鎖。高并發(fā)下可能出現(xiàn)優(yōu)先級(jí)反轉(zhuǎn)或饑餓現(xiàn)象
可重入鎖(遞歸鎖):A同步方法中調(diào)用同步方法B,B也可以獲得A的鎖,A和B擁有同一把鎖
Synchronized和ReentrantLock都是可重入鎖
自旋鎖(SpinLock):嘗試獲取鎖的線(xiàn)程不會(huì)立即阻塞,而是采用循環(huán)的方式去嘗試獲取鎖
獨(dú)占(寫(xiě))鎖:該鎖只能被一個(gè)線(xiàn)程所持有
共享(讀)鎖:該鎖可被多個(gè)線(xiàn)程所持有
八、CountDownLatch、CyclicBarrier、Semaphore、LockSupport
CountDownLatch:一個(gè)同步工具類(lèi),用來(lái)協(xié)調(diào)多個(gè)線(xiàn)程之間的同步,或者說(shuō)起到線(xiàn)程之間的通信。它能夠使一個(gè)線(xiàn)程在等待另外一些線(xiàn)程完成各自工作之后,再繼續(xù)執(zhí)行。使用一個(gè)計(jì)數(shù)器進(jìn)行實(shí)現(xiàn)。計(jì)數(shù)器初始值為線(xiàn)程的數(shù)量。當(dāng)每一個(gè)線(xiàn)程完成自己任務(wù)后,計(jì)數(shù)器的值就會(huì)減一。當(dāng)計(jì)數(shù)器的值為0時(shí),表示所有的線(xiàn)程都已經(jīng)完成一些任務(wù),然后在CountDownLatch上等待的線(xiàn)程就可以恢復(fù)執(zhí)行接下來(lái)的任務(wù)
CyclicBarrier:與CountDownLatch相反。初始值為0,達(dá)到要求后才可以恢復(fù)執(zhí)行接下來(lái)的任務(wù)
Semaphore:
用于多個(gè)共享資源的互斥(搶車(chē)位)
用于并發(fā)線(xiàn)程數(shù)的控制
LockSupport:用來(lái)創(chuàng)建鎖和其他同步類(lèi)的基本線(xiàn)程阻塞原語(yǔ),它使用了一種許可證的概念來(lái)做到阻塞和喚醒線(xiàn)程的功能,許可證最多有一個(gè)。其park()與wait()類(lèi)似,unpark()與notify()類(lèi)似
九、阻塞隊(duì)列?
當(dāng)隊(duì)列為空時(shí),從隊(duì)列中獲取元素的操作將會(huì)被阻塞;當(dāng)隊(duì)列為滿(mǎn)時(shí),往隊(duì)列里添加元素的操作將會(huì)被阻塞
優(yōu)點(diǎn):不需要關(guān)心什么時(shí)候需要阻塞線(xiàn)程,什么時(shí)候需要喚醒線(xiàn)程
BlockingQueue接口實(shí)現(xiàn)Collection接口,與List平級(jí)
ArrayBlockingQueue:由數(shù)組構(gòu)成的有界阻塞隊(duì)列
LinkedBlockingQueue:由鏈表構(gòu)成的有界(但大小默認(rèn)為Integer.MaxValue)阻塞隊(duì)列
synchronousQueue:只存儲(chǔ)單個(gè)元素的隊(duì)列
十、Synchronized和Lock有什么區(qū)別?
原始構(gòu)成
synchronized是關(guān)鍵字,屬于JVM層面
Lock是具體類(lèi),屬于API層面
使用方法
synchronized不需要手動(dòng)釋放,當(dāng)其執(zhí)行完后系統(tǒng)會(huì)讓線(xiàn)程釋放對(duì)鎖的占用
Lock需要用戶(hù)去手動(dòng)釋放鎖。若沒(méi)有釋放,可能會(huì)出現(xiàn)死鎖
等待是否可中斷
synchronized除非拋異?;蛘哌\(yùn)行完成,否則不可中斷
Lock可中斷。可設(shè)置超時(shí)方法
加鎖是否公平
synchronized非公平鎖
Lock兩種都可
鎖綁定多個(gè)條件Condition
synchronized沒(méi)有
Lock可精確喚醒,而不是像synchronized要么喚醒一個(gè),要么喚醒全部
十一、線(xiàn)程池
線(xiàn)程池優(yōu)勢(shì):降低資源消耗,提高響應(yīng)速度,提高線(xiàn)程的可管理性
線(xiàn)程池7大參數(shù)
核心線(xiàn)程數(shù)
最大線(xiàn)程數(shù):必須大于1
存活時(shí)間:當(dāng)前線(xiàn)程數(shù)大于核心線(xiàn)程數(shù)、空閑時(shí)間達(dá)到存活時(shí)間時(shí),多余的線(xiàn)程會(huì)被銷(xiāo)毀
時(shí)間單位
阻塞隊(duì)列:被提交但未被執(zhí)行的任務(wù)
創(chuàng)建工廠(chǎng):創(chuàng)建工作線(xiàn)程的工廠(chǎng)。一般默認(rèn)
拒絕策略:當(dāng)隊(duì)列滿(mǎn)了并且工作線(xiàn)程等于最大線(xiàn)程數(shù)時(shí)如何拒絕后來(lái)的任務(wù)
AbortPolicy(默認(rèn)):拋異常阻止運(yùn)行
CallRunSPolicy:將某些任務(wù)回退到調(diào)用者執(zhí)行
DiscardOldestPolicy:拋棄隊(duì)列中等待最久的任務(wù),將當(dāng)前任務(wù)加入到隊(duì)列中
DiscardPolicy:直接丟棄任務(wù)(允許任務(wù)丟失的最好方案)
線(xiàn)程池的底層工作原理
小于核心線(xiàn)程數(shù),立即運(yùn)行此任務(wù)
大于等于核心線(xiàn)程數(shù),放入隊(duì)列等待
隊(duì)列滿(mǎn)且小于最大線(xiàn)程數(shù),創(chuàng)建非核心線(xiàn)程立即運(yùn)行此任務(wù)
等于最大線(xiàn)程數(shù),啟用拒絕策略
空閑線(xiàn)程超過(guò)存活時(shí)間時(shí),判斷大于核心線(xiàn)程數(shù),將其銷(xiāo)毀
線(xiàn)程池完成所有任務(wù)后會(huì)收縮到核心線(xiàn)程池的大小
線(xiàn)程池合理配置參數(shù)
CPU密集型:CPU核數(shù) + 1
IO密集型
CPU核數(shù) * 2
CPU核數(shù)/(1 - 阻塞系數(shù)) 阻塞系數(shù)0.8-0.9
死鎖檢查:jps -l -> jstack 進(jìn)程號(hào)
網(wǎng)絡(luò)篇
一、TCP和UDP有什么區(qū)別?TCP為什么是三次握手,而不是兩次?
TCP(Transfer ControlProtocol)是一種面向連接的、可靠的傳輸層通信協(xié)議
特點(diǎn):面向連接;點(diǎn)對(duì)點(diǎn);高可靠的;效率較低;占用系統(tǒng)資源較多
UDP(User DatagramProtocol)是一種無(wú)連接的、不可靠的傳輸層通信協(xié)議
特點(diǎn):不需要鏈接;可進(jìn)行廣播發(fā)送;傳輸不可靠,有可能丟失消息;效率較高;占用系統(tǒng)資源較少
TCP三次握手過(guò)程:
服務(wù)端一直處于監(jiān)聽(tīng)狀態(tài),等待客戶(hù)端的連接請(qǐng)求
第握手一次:客戶(hù)端向服務(wù)端發(fā)送連接請(qǐng)求報(bào)文。SYN=1,ACK=0,初始序號(hào)x
第二次握手:服務(wù)端收到請(qǐng)求報(bào)文,如果同意建立連接,則向客戶(hù)端發(fā)送確認(rèn)報(bào)文。SYN=1,ACK=1,確認(rèn)號(hào)x+1,初始序號(hào)y(客戶(hù)端發(fā)送功能正常,服務(wù)端接收功能正常)
第三次握手:客戶(hù)端收到服務(wù)端的確認(rèn)報(bào)文,也要想服務(wù)端進(jìn)行確認(rèn)。ACK=1,x+1,y+1(服務(wù)端發(fā)送功能正常,客戶(hù)端接收功能正常)
TCP四次揮手過(guò)程:
第一次揮手:客戶(hù)端發(fā)送釋放連接報(bào)文。FIN=1,初始序號(hào)u
第二次揮手:服務(wù)端收到釋放報(bào)文并向客戶(hù)端發(fā)送確認(rèn)報(bào)文(此時(shí)TCP處于半關(guān)閉狀態(tài))。ACK=1,u+1,初始序號(hào)v
服務(wù)端進(jìn)入CLOSE-WAIT狀態(tài),服務(wù)端將未傳輸完的數(shù)據(jù)繼續(xù)傳輸
第三次揮手:服務(wù)端傳輸完數(shù)據(jù),向客戶(hù)端發(fā)出釋放連接報(bào)文。FIN=1,ACK=1,u+1,初始序號(hào)w
第四次揮手:客戶(hù)端收到釋放報(bào)文并向服務(wù)端發(fā)送確認(rèn)報(bào)文。u+1,w+1
客戶(hù)端進(jìn)入TIME-WAIT狀態(tài),等待2MSL時(shí)間再進(jìn)入CLOSE狀態(tài)。
確認(rèn)發(fā)送的最后一段報(bào)文能到達(dá)服務(wù)端。如果服務(wù)端沒(méi)收到客戶(hù)端的確認(rèn)報(bào)文,服務(wù)端就會(huì)重新向客戶(hù)端發(fā)送釋放連接報(bào)文
讓本連接持續(xù)時(shí)間內(nèi)所產(chǎn)生的報(bào)文都從網(wǎng)絡(luò)中消失,使下一個(gè)新的連接不會(huì)出現(xiàn)舊連接報(bào)文
二、JAVA有哪幾種IO模型?有什么區(qū)別?
BIO 同步阻塞IO:用戶(hù)線(xiàn)程發(fā)起IO讀/寫(xiě)操作之后,線(xiàn)程阻塞,直到可以開(kāi)始處理數(shù)據(jù)
可靠性差,吞吐量低,適用于較少且比較固定的場(chǎng)景
1
2
3
4
5
Client------> Thread <-----
???????????????????????????|
Client------> Thread <-----------Server
???????????????????????????|
Client------> Thread <-----
NIO 同步非阻塞IO:發(fā)起IO請(qǐng)求之后可以立即返回,如果沒(méi)有就緒的數(shù)據(jù),需要不斷地發(fā)起IO請(qǐng)求直到數(shù)據(jù)就緒
可靠性較好,吞吐量較高,適用于連接較多且連接較短(輕操作),編程模型最復(fù)雜
1
2
3
4
5
Client----
??????????↓
Client----> selector---> Thread <----Server
??????????↑
Client----
AIO 異步非阻塞IO:用戶(hù)線(xiàn)程發(fā)出IO請(qǐng)求之后,繼續(xù)執(zhí)行,由內(nèi)核進(jìn)行數(shù)據(jù)的讀取并放在用戶(hù)指定的緩沖區(qū)內(nèi),在IO完成之后通知用戶(hù)線(xiàn)程直接使用。
可靠性最好,吞吐量最高,適用于連接較多且連接較長(zhǎng)(重操作),編程模型較簡(jiǎn)單,需要操作系統(tǒng)支持
1
2
3
4
5
6
|→Client----
|?????????? ↓
|→Client----> selector---> Thread <----Server
|?????????? ↑??????????????????????????? |
|→Client----???????????????????????????? |
|_______________________________________↙
三、JAVA NIO的幾個(gè)核心組件是什么?分別有什么作用?

Buffer(緩沖區(qū)):緩沖區(qū)的出現(xiàn)導(dǎo)致了NIO和BIO 的不同。讀數(shù)據(jù)時(shí)可以先讀一部分到緩沖區(qū)中,然后處理其他事情;寫(xiě)數(shù)據(jù)時(shí)可以先寫(xiě)一部分到緩沖區(qū)中,然后處理其他事情。讀和寫(xiě)可以不再持續(xù),所以不會(huì)阻塞。當(dāng)緩沖區(qū)滿(mǎn)后才會(huì)將其真正的進(jìn)入讀寫(xiě)
Channel:NIO的所有操作都從Channle開(kāi)始。
從通道進(jìn)行數(shù)據(jù)讀?。簞?chuàng)建一個(gè)緩沖區(qū),然后請(qǐng)求通道讀取數(shù)據(jù)
從通道進(jìn)行數(shù)據(jù)寫(xiě)入:創(chuàng)建一個(gè)緩沖區(qū),填充數(shù)據(jù),并要求通道寫(xiě)入數(shù)據(jù)
Selector:選擇器可以讓單個(gè)線(xiàn)程處理多個(gè)通道,達(dá)到復(fù)用的目的
四、描述下HTTP和HTTPS的區(qū)別
HTTP:是互聯(lián)網(wǎng)上應(yīng)用最廣泛的以中網(wǎng)絡(luò)通信協(xié)議,基于TCP協(xié)議,可以使瀏覽器工作更高效,減少網(wǎng)絡(luò)傳輸
狀態(tài)碼
1xx(信息性狀態(tài)碼):接受的請(qǐng)求正在處理
2xx(成功狀態(tài)碼):請(qǐng)求正常處理完畢
3xx(重定向狀態(tài)碼):需要進(jìn)行附加的操作來(lái)完成請(qǐng)求
4xx(客戶(hù)端錯(cuò)誤狀態(tài)碼):服務(wù)器無(wú)法處理請(qǐng)求
5xx(服務(wù)器錯(cuò)誤狀態(tài)碼):服務(wù)器內(nèi)部錯(cuò)誤
HTTPS:是HTTP的加強(qiáng)版,可以認(rèn)為是HTTP+SSL(Secure Socket Layer)。在HTTP的基礎(chǔ)上增加了一系列的安全機(jī)制。一方面保證數(shù)據(jù)傳輸安全,另一方面對(duì)訪(fǎng)問(wèn)者增加了驗(yàn)證機(jī)制。是目前現(xiàn)行架構(gòu)下,最為安全的解決方案
區(qū)別:
HTTP的連接是簡(jiǎn)單無(wú)狀態(tài)的;HTTPS的數(shù)據(jù)傳輸時(shí)經(jīng)過(guò)證書(shū)加密的,安全性更高
HTTP是免費(fèi)的;HTTPS需要申請(qǐng)證書(shū),證書(shū)是要收費(fèi)的
它們的傳輸協(xié)議不同,所以端口也不同,HTTP默認(rèn):80 HTTPS默認(rèn):443
HTTPS的缺點(diǎn):
HTTPS的握手協(xié)議比較費(fèi)時(shí),所以會(huì)影響服務(wù)的響應(yīng)速度以及吞吐量
功能越強(qiáng)大的證書(shū)費(fèi)用越高
五、計(jì)算機(jī)網(wǎng)絡(luò)體系結(jié)構(gòu)

應(yīng)用層:為特定應(yīng)用程序提供數(shù)據(jù)傳輸服務(wù),如HTTP、DNS等協(xié)議。數(shù)據(jù)單位為報(bào)文。
表示層:負(fù)責(zé)數(shù)據(jù)格式的轉(zhuǎn)換。將應(yīng)用處理的信息轉(zhuǎn)換為適合網(wǎng)絡(luò)傳輸?shù)母袷健?/p>
會(huì)話(huà)層:自動(dòng)發(fā)包,自動(dòng)尋址。建立和管理應(yīng)用程序之間的通訊
傳輸層:為進(jìn)程提供通用數(shù)據(jù)傳輸服務(wù)。由于應(yīng)用層協(xié)議很多,定義通用的傳輸層協(xié)議就可以支持不斷增多的應(yīng)用層協(xié)議。運(yùn)輸層包括兩種協(xié)議:傳輸控制協(xié)議 TCP,提供面向連接、可靠的數(shù)據(jù)傳輸服務(wù),數(shù)據(jù)單位為報(bào)文段;用戶(hù)數(shù)據(jù)報(bào)協(xié)議 UDP,提供無(wú)連接、盡最大努力的數(shù)據(jù)傳輸服務(wù),數(shù)據(jù)單位為用戶(hù)數(shù)據(jù)報(bào)。TCP 主要提供完整***,UDP 主要提供及時(shí)***。
網(wǎng)絡(luò)層:為主機(jī)提供數(shù)據(jù)傳輸服務(wù)。而傳輸層協(xié)議是為主機(jī)中的進(jìn)程提供數(shù)據(jù)傳輸服務(wù)。網(wǎng)絡(luò)層把傳輸層傳遞下來(lái)的報(bào)文段或者用戶(hù)數(shù)據(jù)報(bào)封裝成分組。
數(shù)據(jù)鏈路層:網(wǎng)絡(luò)層針對(duì)的還是主機(jī)之間的數(shù)據(jù)傳輸服務(wù),而主機(jī)之間可以有很多鏈路,鏈路層協(xié)議就是為同一鏈路的主機(jī)提供數(shù)據(jù)傳輸服務(wù)。數(shù)據(jù)鏈路層把網(wǎng)絡(luò)層傳下來(lái)的分組封裝成幀。
物理層:考慮的是怎樣在傳輸媒體上傳數(shù)據(jù)比特流,而不是指具體的傳輸媒體。作用是盡可能屏蔽傳輸媒體和通信手段的差異,使數(shù)據(jù)鏈路層感覺(jué)不到這些差異。

TCP/IP只有四層,相當(dāng)與五層協(xié)議中數(shù)據(jù)鏈路層和物理層合并為網(wǎng)絡(luò)接口層。它不嚴(yán)格遵循OSI分層概念,應(yīng)用層可能會(huì)直接使用IP層或者網(wǎng)絡(luò)接口層
六、訪(fǎng)問(wèn)一個(gè)url涉及到了那些東西?
到DNS服務(wù)器將url解析為IP地址
得到IP地址后使用TCP協(xié)議進(jìn)行連接
連接完成后發(fā)起HTTP請(qǐng)求
服務(wù)器響應(yīng)HTTP請(qǐng)求
瀏覽器解析代碼并請(qǐng)求資源
斷開(kāi)TCP連接
瀏覽器渲染頁(yè)面
JVM篇
一、JVM的內(nèi)存模型
程序計(jì)數(shù)器
本地方法棧
虛擬機(jī)棧:棧幀(最小單位):局部變量表、操作數(shù)棧、方法返回地址、動(dòng)態(tài)鏈接、一些附加信息
堆:新生代(Eden區(qū)、s0、s1)、老年代
方法區(qū):永久代←1.8→元空間
二、JAVA類(lèi)加載的全過(guò)程是怎樣的?什么是雙親委派機(jī)制?有什么作用?一個(gè)對(duì)象從加載到JVM,再到被GC清除,都經(jīng)歷了什么過(guò)程?
全過(guò)程
加載階段(引導(dǎo)類(lèi)加載器(BootStrap ClassLoader)→擴(kuò)展類(lèi)加載器(Extension ClassLoader)→系統(tǒng)類(lèi)加載器(Application ClassLoader))
通過(guò)一個(gè)類(lèi)的全限定名獲取定義此類(lèi)的二進(jìn)制字節(jié)流
將該字節(jié)流所代表的靜態(tài)存儲(chǔ)結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu)
在內(nèi)存中生成一個(gè)代表該類(lèi)的Class對(duì)象,作為方法區(qū)中該類(lèi)各種數(shù)據(jù)的訪(fǎng)問(wèn)入口
鏈接階段
驗(yàn)證:確保Class文件的字節(jié)流中包含信息符合當(dāng)前虛擬機(jī)要求,保證被加載類(lèi)的正確性,不會(huì)危害虛擬機(jī)自身安全
準(zhǔn)備:為類(lèi)變量(不包含用final修飾的static變量)分配內(nèi)存并且設(shè)置該類(lèi)變量的默認(rèn)值
解析:將常量池的符號(hào)引用替換為直接引用的過(guò)程。往往在初始化階段之后再開(kāi)始
初始化階段:執(zhí)行類(lèi)構(gòu)造器方法()的過(guò)程
雙親委派機(jī)制
如果一個(gè)類(lèi)加載器收到了類(lèi)加載請(qǐng)求,它并不會(huì)自己先去加載,而是把這個(gè)請(qǐng)求委托給父類(lèi)的加載器去執(zhí)行
如果父類(lèi)加載器還存在其父類(lèi)加載器,則進(jìn)一步向上委托,請(qǐng)求最終將到達(dá)頂層的引導(dǎo)類(lèi)加載器
如果父類(lèi)加載器可以完成類(lèi)加載任務(wù),就成功返回,若無(wú)法完成此加載任務(wù),子加載器才會(huì)嘗試自己去加載
作用:
避免類(lèi)的重復(fù)加載
保護(hù)程序安全,防止核心API被隨意篡改
如何打破雙親委派機(jī)制:自定義一個(gè)類(lèi)加載器,繼承ClassLoader類(lèi);重寫(xiě)findClass()和loadClass()
過(guò)程:
JVM首先需要到方法區(qū)找到對(duì)象的類(lèi)型信息,創(chuàng)建對(duì)象
JVM要實(shí)例化一個(gè)對(duì)象,會(huì)在堆中創(chuàng)建一個(gè)對(duì)象
對(duì)象首先分配到Eden區(qū),經(jīng)過(guò)一次Minor GC,對(duì)象存活就會(huì)進(jìn)入S區(qū)。如果對(duì)象經(jīng)歷GC一直存活,就會(huì)在S區(qū)來(lái)回拷貝,每移動(dòng)一次,年齡就會(huì)加一。當(dāng)年齡到15時(shí),就會(huì) 將對(duì)象轉(zhuǎn)入老年代。
當(dāng)方法執(zhí)行結(jié)束后,棧中的指針會(huì)被移除掉
對(duì)象經(jīng)過(guò)Full GC后會(huì)被垃圾回收器回收
三、怎么確定一個(gè)對(duì)象到底是不是垃圾?什么是GC Root?
引用計(jì)數(shù)法:每個(gè)對(duì)象都有一個(gè)計(jì)數(shù)器,如果計(jì)數(shù)器為0,則這個(gè)對(duì)象是垃圾。它無(wú)法解決循環(huán)引用的問(wèn)題(內(nèi)存泄漏)
可達(dá)性分析法:以GC Roots為起始點(diǎn),按照從上至下的方式搜索被根對(duì)象集合所連接的目標(biāo)對(duì)象是否可達(dá),如果不可達(dá),則被認(rèn)為是垃圾
GC Root:
虛擬機(jī)棧中引用的對(duì)象
本地方法棧內(nèi)引用的對(duì)象
方法區(qū)中類(lèi)靜態(tài)屬性引用的對(duì)象
方法區(qū)中常量引用的對(duì)象
所有被同步鎖synchronized持有的對(duì)象
虛擬機(jī)內(nèi)部的引用
四、JVM有哪些垃圾回收算法
標(biāo)記-清除:使用可達(dá)性分析算法將可達(dá)的對(duì)象標(biāo)記起來(lái),然后回收不可達(dá)的對(duì)象(會(huì)產(chǎn)生大量的內(nèi)存碎片)
復(fù)制:將內(nèi)存分為兩份,每次只使用一份,回收時(shí)將可達(dá)的對(duì)象依次復(fù)制到另一份中,回收當(dāng)前份內(nèi)存(空間浪費(fèi),效率與存活對(duì)象的個(gè)數(shù)有關(guān))
標(biāo)記-壓縮:先完成一次標(biāo)記清楚算法后,再進(jìn)行一次內(nèi)存碎片整理
五、JVM有哪些垃圾回收器?他們都是怎么工作的?什么是STW?他都發(fā)生在哪些階段?什么是三色標(biāo)記?為什么要設(shè)計(jì)這么多的垃圾回收器?
STW:Stop The World。在垃圾回收?qǐng)?zhí)行過(guò)程中,需要暫停用戶(hù)線(xiàn)程使垃圾回收線(xiàn)程運(yùn)行
垃圾回收器
Serial(復(fù)制) - Serial Old(標(biāo)整)串行
ParNew(復(fù)制) - CMS(標(biāo)清)
Parallel(復(fù)制) - Parallel Old(標(biāo)整)JDK1.8默認(rèn)
G1(整體標(biāo)整,局部復(fù)制)JDK1.9默認(rèn)
ZGC
CMS垃圾回收器工作原理:耗時(shí)最長(zhǎng)為②④步,總體來(lái)看為并發(fā)執(zhí)行
初始標(biāo)記:標(biāo)記GCRoots能直接關(guān)聯(lián)的對(duì)象,觸發(fā)STW
并發(fā)標(biāo)記:進(jìn)行GCRoots的跟蹤過(guò)程,和用戶(hù)線(xiàn)程一起工作,主要標(biāo)記過(guò)程
重新標(biāo)記:修正在并發(fā)標(biāo)記期間,因用戶(hù)程序繼續(xù)運(yùn)行而導(dǎo)致標(biāo)記產(chǎn)生變動(dòng)的那一部分對(duì)象的標(biāo)記記錄,觸發(fā)STW
并發(fā)清除:清除垃圾
三色標(biāo)記:CMS核心算法,將每個(gè)對(duì)象分成三種顏色
黑色:表示自己和成員變量都已經(jīng)標(biāo)記完成
灰色:自己標(biāo)記完成,成員變量標(biāo)記未完成
白色:自己未標(biāo)記完成
由于內(nèi)存越來(lái)越大,所以需要更強(qiáng)大的垃圾回收器來(lái)管理內(nèi)存
六、如何進(jìn)行JVM調(diào)優(yōu)?JVM參數(shù)有哪些?怎么查看一個(gè)JAVA進(jìn)程的JVM參數(shù)?談?wù)勀懔私獾腏VM參數(shù)。
JVM調(diào)優(yōu)主要是通過(guò)定制JVM運(yùn)行參數(shù)來(lái)提高應(yīng)用程序的運(yùn)行速度
JVM參數(shù):
-開(kāi)頭:標(biāo)準(zhǔn)指令,HotSpot支持的參數(shù),java -help打印
-X開(kāi)頭:非標(biāo)準(zhǔn)指令,與特定HotSpot版本對(duì)應(yīng),java -X打印
-Xms512m:設(shè)置堆初始值
-Xmn512m:新生代內(nèi)存配置
-Xss256k:每個(gè)線(xiàn)程棧最大值
-XX開(kāi)頭:不穩(wěn)定參數(shù),與特定HotSpot版本對(duì)應(yīng)且變化非常大
-XX+PrintCommandLineFlags:查看當(dāng)前命令的不穩(wěn)定指令
-XX+PrintFlagsInitial:查看所有不穩(wěn)定指令的默認(rèn)值
-XX+PrintFlagsFinal:查看所有不穩(wěn)定指令最終生效的實(shí)際值
-XX:+UseG1GC:開(kāi)啟G1垃圾收集器
-XX:-UseG1GC:關(guān)閉G1垃圾收集器
七、OOM相關(guān)
SOFE StackOverFlowError:棧溢出。遞歸。是錯(cuò)誤
OOM java heap space:堆內(nèi)存不足
OOM GC overhead limit exceeded:大量資源在進(jìn)行垃圾回收且回收很少
OOM Direct buffer memory:NIO寫(xiě)入本地內(nèi)存而不是堆內(nèi)存,本地內(nèi)存可能用光
OOM unable to create new native thread:一個(gè)進(jìn)程創(chuàng)建多個(gè)線(xiàn)程,超過(guò)系統(tǒng)承載極限。服務(wù)器不允許應(yīng)用創(chuàng)建這么多線(xiàn)程
OOM Metaspace:元空間內(nèi)存不足
MQ篇
一、MQ有什么用?有哪些具體的使用場(chǎng)景
MQ:消息隊(duì)列。隊(duì)列是一種FIFO(先進(jìn)先出)的數(shù)據(jù)結(jié)構(gòu)。消息由生產(chǎn)者發(fā)送到MQ進(jìn)行排隊(duì),然后由消費(fèi)者對(duì)消息進(jìn)行處理
作用:
異步:提高系統(tǒng)的響應(yīng)速度和吞吐量
** 解耦:** 減少服務(wù)之間的影響,提高系統(tǒng)的穩(wěn)定性和可擴(kuò)展性。解耦之后可以實(shí)現(xiàn)數(shù)據(jù)分發(fā),生產(chǎn)者發(fā)送一個(gè)消息后,可以由多個(gè)消費(fèi)者來(lái)處理
削峰:以穩(wěn)定的系統(tǒng)資源應(yīng)對(duì)突發(fā)的流量沖擊
缺點(diǎn):
系統(tǒng)可用性降低:一旦MQ宕機(jī),整個(gè)業(yè)務(wù)就會(huì)產(chǎn)生影響(高可用)
系統(tǒng)的復(fù)雜度提高:消息丟失,消息重復(fù)調(diào)用,消息亂序
數(shù)據(jù)一致性:兩個(gè)系統(tǒng)一同處理消息,處理結(jié)果不同,則數(shù)據(jù)不一致
二、 如何進(jìn)行產(chǎn)品選型?
** Kafaka:**
優(yōu)點(diǎn):吞吐量非常大,性能非常好,集群高可用
缺點(diǎn):有可能會(huì)丟數(shù)據(jù),功能比較單一
使用場(chǎng)景:日志分析,大數(shù)據(jù)采集
RabbitMQ:
優(yōu)點(diǎn):消息可靠性高,功能全面
缺點(diǎn):吞吐量比較低,消息積累會(huì)嚴(yán)重影響性能,語(yǔ)言不好定制
使用場(chǎng)景:小規(guī)模場(chǎng)景
RocketMQ
優(yōu)點(diǎn):較高吞吐,較高性能,高可用,功能非常全面
缺點(diǎn):開(kāi)源版功能不如云上商業(yè)版。官方文檔和周邊生態(tài)還不夠成熟。客戶(hù)端只支持JAVA
使用場(chǎng)景:幾乎是全場(chǎng)景
三、如何保證消息不丟失?
生產(chǎn)者發(fā)送消息不丟失
Kafka:消息發(fā)送 + 回調(diào)
RocketMQ:消息發(fā)送 + 回調(diào) + 事務(wù)
RabbitMQ:消息發(fā)送 + 回調(diào) + 事務(wù)(channel手動(dòng)事務(wù)(可能會(huì)產(chǎn)生阻塞)) + Publisher Confirm(與RocketMQ事務(wù)機(jī)制基本基本相同)
MQ主從消息同步不丟失
RocketMQ:
普通集群:同步同步、異步同步。前者不會(huì)丟消息,后者效率更高
Dledger集群-兩階段提交
RabbitMQ:
普通集群:消息分散存儲(chǔ),節(jié)點(diǎn)之間不會(huì)主動(dòng)進(jìn)行消息同步,可能丟消息
鏡像集群:會(huì)在節(jié)點(diǎn)之間主動(dòng)進(jìn)行數(shù)據(jù)同步,數(shù)據(jù)安全性得到提高
Kafka:通常用在允許消息少量丟失的場(chǎng)景中
MQ消息存盤(pán)不丟失
RocketMQ:同步刷盤(pán)、異步刷盤(pán),前者不會(huì)丟消息,后者效率更高
RabbitMQ:隊(duì)列配置為持久化隊(duì)列。新增Quorum類(lèi)型的隊(duì)列,采用Raft協(xié)議來(lái)進(jìn)行消息同步
MQ消費(fèi)者消費(fèi)消息不丟失
RocketMQ:使用默認(rèn)的方式消費(fèi),不采用異步
RabbitMQ:關(guān)閉AutoCommit -> 手動(dòng)提交offset
Kafka:手動(dòng)提交offset
四、如何保證消息消費(fèi)的冪等性?
冪等性:對(duì)于同一個(gè)系統(tǒng),在同樣條件下,一次請(qǐng)求和重復(fù)多次請(qǐng)求對(duì)資源的影響是一致的
消息消費(fèi)冪等性:防止消費(fèi)者重復(fù)消費(fèi)消息
所有MQ產(chǎn)品并沒(méi)有提供主動(dòng)解決冪等性的機(jī)制,需要有消費(fèi)者自行控制
RocketMQ:給每個(gè)消息分配了個(gè)MessageID(不太建議),建議使用有業(yè)務(wù)標(biāo)識(shí)的ID
五、如何保證消息的順序?
MQ只需要保證局部有序,不需要保證全局有序
RocketMQ中有完整的設(shè)計(jì):生產(chǎn)者把一組有序的消息放到同一個(gè)隊(duì)列當(dāng)中,消費(fèi)者一次消費(fèi)整個(gè)隊(duì)列中的消息
RabbitMQ:實(shí)現(xiàn)上述方式。保證目標(biāo)Exchange只對(duì)應(yīng)一個(gè)隊(duì)列,一個(gè)隊(duì)列只對(duì)應(yīng)一個(gè)消費(fèi)者
Kafka:實(shí)現(xiàn)上述方式。生產(chǎn)者通過(guò)Partition分配規(guī)則,將消息分配到同一個(gè)Partition。Topic下只有一個(gè)消費(fèi)者
六、如何保證消息的高效讀寫(xiě)?
Kafka和RocketMQ都是通過(guò)零拷貝技術(shù)來(lái)優(yōu)化文件讀寫(xiě)
零拷貝:
mmap(JDK中MappedByteBuffer):文件不經(jīng)過(guò)用戶(hù)空間,直接在內(nèi)核空間完成拷貝。適合比較小的文件
transfile(JDK中FileChannel):文件傳輸過(guò)程中直接使用DMA
RocketMQ:mmap(commitlog)
Kafka:在index日志文件中通過(guò)mmap方式,使用transfile方式將硬盤(pán)數(shù)據(jù)加載到網(wǎng)卡
七、使用MQ如何保證分布式事務(wù)的最終一致性?
分布式事務(wù):業(yè)務(wù)相關(guān)的多個(gè)操作,保證其同時(shí)成功或失敗
最終一致性:系統(tǒng)中的所有分散在不同節(jié)點(diǎn)的數(shù)據(jù),經(jīng)過(guò)一定時(shí)間后,最終能夠達(dá)到符合業(yè)務(wù)定義的一致的狀態(tài)。
保證:
生產(chǎn)者要保證100%的消息投遞
消費(fèi)者需要保證冪等性消費(fèi)
八、如何自己設(shè)計(jì)一個(gè)MQ?
從整體到細(xì)節(jié),從業(yè)務(wù)場(chǎng)景到技術(shù)實(shí)現(xiàn);以RocketMQ為基礎(chǔ)
實(shí)現(xiàn)一個(gè)單機(jī)的隊(duì)列數(shù)據(jù)結(jié)構(gòu)。高效,可擴(kuò)展
將單機(jī)隊(duì)列擴(kuò)展分布式隊(duì)列。分布式集群管理
基于Topic定制消息路由策略。發(fā)送者路由策略,消費(fèi)者與隊(duì)列對(duì)應(yīng)關(guān)系,消費(fèi)者路由策略
實(shí)現(xiàn)高效的網(wǎng)絡(luò)通信
規(guī)劃日志文件,實(shí)現(xiàn)文件高效讀寫(xiě)
定制高級(jí)功能:死信隊(duì)列,延遲隊(duì)列,事務(wù)消息
緩存
一、為什么使用緩存?
高性能、高并發(fā)
二、什么是緩存穿透、緩存擊穿、緩存雪崩?怎么解決?
緩存穿透:數(shù)據(jù)在緩存中查不到,數(shù)據(jù)庫(kù)也查不到
對(duì)參數(shù)進(jìn)行合法性校驗(yàn)
將數(shù)據(jù)庫(kù)沒(méi)查到結(jié)果的數(shù)據(jù)也寫(xiě)入到緩存(短的有效期)
布隆過(guò)濾器(存在一定誤判率),在訪(fǎng)問(wèn)Redis之前判斷數(shù)據(jù)是否存在
緩存擊穿:緩存單個(gè)key失效,去查數(shù)據(jù)庫(kù),數(shù)據(jù)庫(kù)瞬間壓力增大
設(shè)置熱點(diǎn)數(shù)據(jù)永不過(guò)期
加互斥鎖
緩存雪崩:大面積緩存失效,都去請(qǐng)求數(shù)據(jù)庫(kù),造成數(shù)據(jù)庫(kù)短時(shí)間內(nèi)承受大量請(qǐng)求而掛掉
將緩存的失效時(shí)間分散開(kāi)(加隨機(jī)值)
redis高可用
限流降級(jí)
數(shù)據(jù)預(yù)熱
三、如何保證Redis與數(shù)據(jù)庫(kù)的數(shù)據(jù)一致?
雙寫(xiě)模式:先寫(xiě)數(shù)據(jù)庫(kù),再寫(xiě)緩存
失效模式:先寫(xiě)數(shù)據(jù)庫(kù),在刪除緩存
最終一致性
四、如何設(shè)計(jì)一個(gè)分布式鎖?如何對(duì)鎖性能進(jìn)行優(yōu)化?
本質(zhì):在所有進(jìn)程都能訪(fǎng)問(wèn)到的一個(gè)地方,設(shè)置一個(gè)鎖資源,讓這些進(jìn)程來(lái)競(jìng)爭(zhēng)鎖資源。響應(yīng)快、性能高、與業(yè)務(wù)無(wú)關(guān)
Redis實(shí)現(xiàn)分布式鎖
SETNX k v
EXPIRE k locktime
DEL k
GETSET k v
synchronized單機(jī)版鎖。在分布式下不ok
使用redis的SETNX達(dá)成分布式鎖
出現(xiàn)異常無(wú)法釋放鎖。在finally中釋放鎖
宕機(jī)后,代碼沒(méi)走到finally。設(shè)置鎖的過(guò)期時(shí)間
SETNX操作和設(shè)置過(guò)期時(shí)間必須為原子操作
只能刪除自己的鎖,使用lua腳本或redis事務(wù)
判斷鎖所屬業(yè)務(wù)與刪除鎖也必須是原子操作
redis集群環(huán)境下,使用Redisson
五、Redis如何設(shè)置Key的過(guò)期時(shí)間?它的實(shí)現(xiàn)原理是什么?Redis默認(rèn)內(nèi)存大小
EXPIRE SETNX
實(shí)現(xiàn)原理(兩種結(jié)合)
定期刪除:默認(rèn)每隔100ms隨機(jī)抽取設(shè)置了過(guò)期時(shí)間的key,判斷其是否過(guò)期,過(guò)期就刪除
惰性刪除:key過(guò)期后,下一次用到它時(shí)再刪除
沒(méi)有配置默認(rèn)最大內(nèi)存, 生產(chǎn)上推薦設(shè)置為最大物理內(nèi)存的四分之三。當(dāng)達(dá)到最大內(nèi)存時(shí),Redis會(huì)報(bào)OOM
六、海量數(shù)據(jù)下,如何快速查找一條記錄?
使用布隆過(guò)濾器,快速過(guò)濾不存在的記錄
在Redis中建立緩存(以Hash來(lái)存儲(chǔ))
七、五大基本數(shù)據(jù)類(lèi)型
string
應(yīng)用場(chǎng)景:incr items 商品編號(hào)、訂單號(hào)、是否喜歡
數(shù)據(jù)結(jié)構(gòu):簡(jiǎn)單動(dòng)態(tài)字符串(SDS)。是可以修改的字符串,內(nèi)部結(jié)構(gòu)類(lèi)似于ArrayList,采用預(yù)分配冗余空間的方式來(lái)減少內(nèi)存的頻繁分配
list
應(yīng)用場(chǎng)景:微信公眾號(hào)
數(shù)據(jù)結(jié)構(gòu):快速鏈表(quicklist)。在列表元素較少的情況下會(huì)使ziplist,即壓縮列表。他將所有的元素緊挨著一起存儲(chǔ),分配的是一塊連續(xù)的內(nèi)存。數(shù)據(jù)量較多時(shí)改為quicklist。Redis將鏈表和ziplist結(jié)合起來(lái)組成quicklist。也就是將多個(gè)ziplist使用雙向指針串起來(lái)。這樣既滿(mǎn)足了快速的插入刪除性能,又不會(huì)出現(xiàn)太大的空間冗余
hash:Map<String,Map<Object,Object>>
應(yīng)用場(chǎng)景:購(gòu)物車(chē)早期
數(shù)據(jù)結(jié)構(gòu):與HashMap相似,通過(guò)數(shù)組+鏈表的鏈地址法來(lái)解決部分哈希沖突。hash結(jié)構(gòu)的內(nèi)部包含兩個(gè)hashtable,通常情況下只有一個(gè)hashtable是有值的,但是在字典擴(kuò)容時(shí)需要分配新的hashtable,然后進(jìn)行漸進(jìn)式搬遷(rehash)。
漸進(jìn)式rehash會(huì)在rehash同時(shí)保留新舊兩個(gè)hash結(jié)構(gòu),查詢(xún)時(shí)會(huì)同時(shí)查詢(xún)兩個(gè)hash,然后在后續(xù)的定時(shí)任務(wù)以及hash操作指令中,循序漸進(jìn)的把舊hash的內(nèi)容遷移到新hash中。當(dāng)搬遷完成后,新hash取代舊hash

set:HashSet 無(wú)序無(wú)重復(fù)
應(yīng)用場(chǎng)景:微信抽獎(jiǎng)小程序,微信朋友圈點(diǎn)贊,抖音共同關(guān)注、可能認(rèn)識(shí)的人(交并差集)
數(shù)據(jù)結(jié)構(gòu):與HashSet相似,內(nèi)部鍵值對(duì)是無(wú)序且唯一的。內(nèi)部實(shí)現(xiàn)一個(gè)特殊的hash,其中所有的v指都是null
zset
應(yīng)用場(chǎng)景:商品排序,熱搜熱度
數(shù)據(jù)結(jié)構(gòu):數(shù)據(jù)少時(shí),使用ziplist,每個(gè)元素都是(數(shù)據(jù)+score)的方式連續(xù)存儲(chǔ),按照score從小到大排序。數(shù)據(jù)多時(shí),使用hash+跳表。hash用來(lái)根據(jù)數(shù)據(jù)查score,調(diào)表用來(lái)根據(jù)score查數(shù)據(jù)。跳表是基于一條有序單鏈表構(gòu)造的,通過(guò)構(gòu)建索引提高查找效率,空間換時(shí)間,查找方式是從最上面的鏈表層層往下查找,最后在最底層的鏈表找到對(duì)應(yīng)節(jié)點(diǎn)
八、LRU算法
1最近最少使用,常見(jiàn)的頁(yè)面置換算法。選擇最近最久未使用的數(shù)據(jù)予以淘汰
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//巧用LinkedHashMap完成
classLRUCache<k,v> extendsLinkedHashMap<k,v> {
????privateintcapacity;
????publicLRUCache(intcapacity) {
????????super(capacity,0.75,true);
????????this.capacity = capacity;
????}
????protectedbooleanremoveEldestEntry(Map.Entry<k,v> eldest){
????????returnsuper.size > capacity;
????}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
publicclassLRUCache {
????/**
?????* 146. LRU 緩存機(jī)制
?????* 運(yùn)用你所掌握的數(shù)據(jù)結(jié)構(gòu),設(shè)計(jì)和實(shí)現(xiàn)一個(gè)? LRU (最近最少使用) 緩存機(jī)制 。
?????* 實(shí)現(xiàn) LRUCache 類(lèi):
?????* LRUCache(int capacity) 以正整數(shù)作為容量 capacity 初始化 LRU 緩存
?????* int get(int key) 如果關(guān)鍵字 key 存在于緩存中,則返回關(guān)鍵字的值,否則返回 -1 。
?????* void put(int key, int value) 如果關(guān)鍵字已經(jīng)存在,則變更其數(shù)據(jù)值;如果關(guān)鍵字不存在,則???????????????
?????* 插入該組「關(guān)鍵字-值」。
?????* 當(dāng)緩存容量達(dá)到上限時(shí),它應(yīng)該在寫(xiě)入新數(shù)據(jù)之前刪除最久未使用的數(shù)據(jù)值,從而為新的數(shù)據(jù)值留出???????????
?????* 空間。
?????* 進(jìn)階:你是否可以在 O(1) 時(shí)間復(fù)雜度內(nèi)完成這兩種操作?
?????*/
????//map負(fù)責(zé)查找,構(gòu)建一個(gè)虛擬的雙向鏈表,里面是一個(gè)個(gè)Node節(jié)點(diǎn)作為數(shù)據(jù)載體
????//1.構(gòu)造Node節(jié)點(diǎn)作為數(shù)據(jù)載體
????classNode<K, V> {
????????K key;
????????V value;
????????Node<K, V> prev;
????????Node<K, V> next;
????????publicNode() {
????????????this.prev = this.next = null;
????????}
????????publicNode(K key, V value) {
????????????this.key = key;
????????????this.value = value;
????????????this.prev = this.next = null;
????????}
????}
????//2.構(gòu)造一個(gè)虛擬的雙向鏈表,里面放Node
????classDoubleLinkedList<K,V>{
????????Node<K,V> head;
????????Node<K,V> tail;
????????//2.1 構(gòu)造方法
????????publicDoubleLinkedList(){
????????????head = newNode<>();
????????????tail = newNode<>();
????????????head.next = tail;?????????????? //頭節(jié)點(diǎn)下一個(gè)節(jié)點(diǎn)指向尾節(jié)點(diǎn)
????????????tail.prev = head;?????????????? //尾節(jié)點(diǎn)上一個(gè)節(jié)點(diǎn)指向頭節(jié)點(diǎn)
????????}
????????//2.2 添加節(jié)點(diǎn)
????????publicvoidaddHead(Node<K,V> node){
????????????node.next = head.next;????????? //當(dāng)前節(jié)點(diǎn)下一個(gè)節(jié)點(diǎn)指向尾節(jié)點(diǎn)
????????????node.prev = head;?????????????? //當(dāng)前節(jié)點(diǎn)上一個(gè)節(jié)點(diǎn)指向頭節(jié)點(diǎn)
????????????head.next.prev = node;????????? //尾節(jié)點(diǎn)上一個(gè)節(jié)點(diǎn)指向當(dāng)前節(jié)點(diǎn)
????????????head.next = node;?????????????? //頭節(jié)點(diǎn)下一個(gè)節(jié)點(diǎn)指向當(dāng)前節(jié)點(diǎn)
????????}
????????//2.3 刪除節(jié)點(diǎn)
????????publicvoidremove(Node<K,V> node){
????????????node.next.prev = node.prev;???? //尾節(jié)點(diǎn)上一個(gè)節(jié)點(diǎn)指向頭節(jié)點(diǎn)
????????????node.prev.next = node.next;???? //頭節(jié)點(diǎn)下一個(gè)節(jié)點(diǎn)指向尾節(jié)點(diǎn)
????????????node.prev = null;?????????????? //當(dāng)前節(jié)點(diǎn)上一個(gè)節(jié)點(diǎn)置空
????????????node.next = null;?????????????? //當(dāng)前節(jié)點(diǎn)下一個(gè)節(jié)點(diǎn)置空
????????}
????????//2.4 獲取最后一個(gè)節(jié)點(diǎn)
????????publicNode<K,V> getLast(){
????????????returntail.prev;
????????}
????}
????privateintcapacity;
????Map<Integer,Node<Integer,Integer>> map;
????DoubleLinkedList<Integer,Integer> list;
????publicLRUCache(intcapacity) {
????????this.capacity = capacity;
????????map = newHashMap<>();
????????list = newDoubleLinkedList<>();
????}
????publicintget(intkey) {
????????if(!map.containsKey(key)) return-1;
????????Node<Integer, Integer> node = map.get(key);
????????list.remove(node);
????????list.addHead(node);
????????returnnode.value;
????}
????publicvoidput(intkey, intvalue) {
????????if(map.containsKey(key)){
????????????Node<Integer, Integer> node = map.get(key);
????????????node.value = value;
????????????map.put(key,node);
????????????list.remove(node);
????????????list.addHead(node);
????????}else{
????????????if(map.size() == capacity){
????????????????Node<Integer, Integer> lastNode = list.getLast();
????????????????map.remove(lastNode.key);
????????????????list.remove(lastNode);
????????????}
????????????Node<Integer, Integer> newNode = newNode<>(key, value);
????????????map.put(key,newNode);
????????????list.addHead(newNode);
????????}
????}
}
微服務(wù)篇
一、談?wù)勀銓?duì)微服務(wù)的理解,微服務(wù)有哪些優(yōu)缺點(diǎn)?
微服務(wù)是一種架構(gòu)風(fēng)格,通過(guò)將單體的應(yīng)用劃分為較小的服務(wù)單元,從而降低整個(gè)系統(tǒng)的復(fù)雜度
優(yōu)點(diǎn):
服務(wù)部署更靈活:每個(gè)應(yīng)用都可以是一個(gè)獨(dú)立的項(xiàng)目,可以獨(dú)立部署,耦合性低
技術(shù)更新靈活:微服務(wù)可以根據(jù)業(yè)務(wù)特點(diǎn),靈活選擇技術(shù)棧
應(yīng)用性能高:一個(gè)應(yīng)用可以部署到多個(gè)服務(wù)器
代碼復(fù)用:很多底層服務(wù)可以以REST API的方式對(duì)外提供統(tǒng)一的服務(wù),所以基礎(chǔ)服務(wù)可以在整個(gè)微服務(wù)中通用
缺點(diǎn):
服務(wù)調(diào)用的復(fù)雜性提高:網(wǎng)絡(luò)問(wèn)題,容錯(cuò)問(wèn)題,負(fù)載問(wèn)題,高并發(fā)問(wèn)題
分布式事務(wù):盡量不要使用
測(cè)試難度提升
運(yùn)維難度提升
二、SpringCloud和SpringCloudAlibaba都有哪些組件?都解決了什么問(wèn)題?
SpringCloud:是提供構(gòu)建微服務(wù)系統(tǒng)多需要的一組通用開(kāi)發(fā)模式以及一系列快速實(shí)現(xiàn)這些開(kāi)發(fā)模式的工具
Eureka:服務(wù)注冊(cè)與發(fā)現(xiàn)
Hytrix:服務(wù)熔斷降級(jí)
Feign - Ribbon:遠(yuǎn)程服務(wù)調(diào)用
OpenFeign:遠(yuǎn)程服務(wù)調(diào)用
Zuul:網(wǎng)關(guān)路由
Config:配置中心
SpringCloudAlibaba:
Sentinel:服務(wù)熔斷降級(jí)
Nacos:注冊(cè)中心和配置中心
Stream RocketMQ:消息中間件整合
Seata:分布式事務(wù)
三、分布式事務(wù)如何處理?怎么保證事務(wù)一致性?
分布式事務(wù):不同節(jié)點(diǎn)的實(shí)務(wù)操作提供操作原子性保證。在原本沒(méi)有直接關(guān)聯(lián)的事務(wù)之間建立聯(lián)系,
HTTP:最大努力通知,事后補(bǔ)償
MQ:事務(wù)消息機(jī)制
Seata:通過(guò)TC來(lái)在多個(gè)事務(wù)之間建立聯(lián)系
兩階段:AT XA (鎖資源)
三階段:TCC (在兩階段前增加一個(gè)準(zhǔn)備階段,不鎖資源)
SAGA:類(lèi)似于熔斷,業(yè)務(wù)自己實(shí)現(xiàn)正向操作和補(bǔ)償操作的邏輯
四、怎么拆分微服務(wù)?怎么樣設(shè)計(jì)出高內(nèi)聚,低耦合的微服務(wù)?有沒(méi)有了解過(guò)DDD領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)?什么是中臺(tái)?中臺(tái)和微服務(wù)有什么關(guān)系?
拆分微服務(wù)的基本準(zhǔn)則:
微服務(wù)之間盡量不要有業(yè)務(wù)交叉
微服務(wù)之間只能通過(guò)接口進(jìn)行調(diào)用
高內(nèi)聚,低耦合
高內(nèi)聚低耦合是一種從上到下指導(dǎo)微服務(wù)設(shè)計(jì)的方法,實(shí)現(xiàn)其工具主要有同步的接口調(diào)用和異步的事件驅(qū)動(dòng)
DDD領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)是一種通過(guò)將實(shí)現(xiàn)連接到持續(xù)進(jìn)化的模型來(lái)滿(mǎn)足復(fù)雜需求的軟件開(kāi)發(fā)方法。
中臺(tái):將各個(gè)業(yè)務(wù)線(xiàn)中可以復(fù)用的一些功能抽取出來(lái),剝離個(gè)性,提取共性,形成一些可復(fù)用的組件
業(yè)務(wù)中臺(tái)、數(shù)據(jù)中臺(tái)、技術(shù)中臺(tái)
五、你的項(xiàng)目中是怎么保證微服務(wù)敏捷開(kāi)發(fā)的?微服務(wù)的鏈路追蹤、持續(xù)集成、AB發(fā)布要怎么做?
敏捷開(kāi)發(fā):為了提高團(tuán)隊(duì)的交付效率,快速迭代,快速試錯(cuò)
鏈路追蹤:基于日志,形成全局事務(wù)ID,落地到日志文件
持續(xù)集成:maven pom -> build ->shell
AB發(fā)布:
藍(lán)綠發(fā)布,紅黑發(fā)布。新老版本同時(shí)存在
灰度發(fā)布
Spring篇
一、Spring框架中Bean的創(chuàng)建過(guò)程是怎么樣的?
實(shí)例化、屬性賦值、初始化、銷(xiāo)毀
實(shí)例化
當(dāng)客戶(hù)端容器申請(qǐng)一個(gè)Bean時(shí)
當(dāng)容器在初始化一個(gè)Bean時(shí)發(fā)現(xiàn)還需要依賴(lài)另一個(gè)Bean時(shí)
以BeanDefinition對(duì)象的形式保存
屬性賦值(依賴(lài)注入):Spring通過(guò)BeanDefinition找到對(duì)象依賴(lài)的其他對(duì)象,并將這些對(duì)象賦予當(dāng)前對(duì)象
處理Aware接口:Spring會(huì)檢測(cè)對(duì)象是否實(shí)現(xiàn)了xxxAware接口,實(shí)現(xiàn)了則調(diào)用相應(yīng)的方法
BeanPostProcessor前置處理:調(diào)用BeanPostProcessor的postProcessBeforeInitialization()
InitializationBean:Spring檢測(cè)對(duì)象如果實(shí)現(xiàn)此接口,就會(huì)執(zhí)行它的afterPropertiesSet()方法,定制初始化邏輯
init-method:Spring發(fā)現(xiàn)Bean配置了這個(gè)屬性,就會(huì)調(diào)用它的配置方法,執(zhí)行它的初始化邏輯 @PostConStruct
BeanPostProcessor后置處理:調(diào)用BeanPostProcessor的postProcessAfterInitialization()
·····Bean創(chuàng)建完成·····
DisposableBean:如果Bean實(shí)現(xiàn)此接口,在對(duì)象銷(xiāo)毀前會(huì)調(diào)用destory()方法
destory-method:@PreDestory
二、Spring中Bean是線(xiàn)程安全的嗎?如果不安全,要如何處理?
不是
SpringBean作用域:
singleton:?jiǎn)卫?/p>
prototype:為每個(gè)Bean請(qǐng)求創(chuàng)建實(shí)例
request:為每一個(gè)request請(qǐng)求創(chuàng)建一個(gè)實(shí)例,請(qǐng)求完成后失效
session:為每一個(gè)session請(qǐng)求創(chuàng)建一個(gè)實(shí)例,請(qǐng)求完成后失效
global-session:全局
singleton:默認(rèn)線(xiàn)程不安全。大部分Bean無(wú)狀態(tài),不需要保證線(xiàn)程安全
prototype:每次都生成一個(gè)新的對(duì)象,所以不存在線(xiàn)程安全問(wèn)題
三、Spring如何處理循環(huán)依賴(lài)問(wèn)題?
循環(huán)依賴(lài):多個(gè)Bean之間相互依賴(lài),形成了一個(gè)閉環(huán)。A依賴(lài)B,B依賴(lài)C,C依賴(lài)A
Spring中singleton支持循環(huán)依賴(lài),Prototype不支持。只有單例的Bean會(huì)通過(guò)三級(jí)緩存提前暴露來(lái)解決循環(huán)依賴(lài)問(wèn)題,而非單例Bean每次都是從容器中獲取一個(gè)新的對(duì)象,都會(huì)重新創(chuàng)建,所以 非單例的Bean是沒(méi)有緩存的,不會(huì)將其放到三級(jí)緩存中
三級(jí)緩存
第一級(jí)緩存(單例池):singletonObjects,存放已經(jīng)經(jīng)歷了完整生命周期的Bean對(duì)象
第二級(jí)緩存:earlySingletonObjects,存放早期暴露出來(lái)的Bean對(duì)象,Bean的生命周期未結(jié)束(屬性還未填充完)
第三級(jí)緩存:Map> singletonFactorices,存放可以生成Bean的工廠(chǎng)
三級(jí)緩存的遷移過(guò)程
A創(chuàng)建過(guò)程中需要注入屬性B,于是將A放入三級(jí)緩存,去創(chuàng)建B
B創(chuàng)建過(guò)程中發(fā)現(xiàn)需要注入屬性A,于是B先查一級(jí)緩存,沒(méi)有再查二級(jí)緩存,沒(méi)有再查三級(jí)緩存,在三級(jí)緩存中找到A,然后將三級(jí)緩存的A放入二級(jí)緩存并刪除三級(jí)緩存的A
B創(chuàng)建完畢,將自己放入一級(jí)緩存(此時(shí)B的屬性A的狀態(tài)依然是創(chuàng)建中),然后繼續(xù)創(chuàng)建A,從一級(jí)緩存里拿到B,完成A的創(chuàng)建,將A放入一級(jí)緩存
四、Spring如何處理事務(wù)
編程式事務(wù)
聲明式事務(wù):Spring在AOP基礎(chǔ)上提供的事務(wù)實(shí)現(xiàn)機(jī)制
傳播級(jí)別
PROPAGATION_REQUIRED:默認(rèn)傳播行為。如果當(dāng)前沒(méi)有事務(wù),就創(chuàng)建一個(gè)新事務(wù),如果當(dāng)前存在事務(wù),就加入到事務(wù)中。
PROPAGATION_SUPPORTS:如果當(dāng)前存在事務(wù),就加入到該事務(wù)。如果當(dāng)前不存在事務(wù),就以非事務(wù)方式運(yùn)行。
PROPAGATION_MANDATORY:如果當(dāng)前存在事務(wù),就加入該事務(wù)。如果當(dāng)前不存在事務(wù)就拋出異常。
PROPAGATION_REQUIRES_NEW:無(wú)論當(dāng)前存不存在事務(wù),都創(chuàng)建新事務(wù)進(jìn)行執(zhí)行。
PROPAGATION_NOT_SUPPORTED:以非事務(wù)方式運(yùn)行。如果當(dāng)前存在事務(wù),就將當(dāng)前事務(wù)掛起。
PROPAGATION_NEVER:以非事務(wù)方式運(yùn)行。如果當(dāng)前存在事務(wù),就拋出異常。
PROPAGATION_NESTED:如果當(dāng)前存在事務(wù),則在嵌套事務(wù)內(nèi)執(zhí)行;如果當(dāng)前沒(méi)有事務(wù)則按REQUEIRED屬性執(zhí)行。
@Transactional注解的失效場(chǎng)景
@Transactional 應(yīng)用在非 public 修飾的方法上:事務(wù)攔截器中會(huì)檢查目標(biāo)方法的修飾符,不是public不會(huì)獲取@Transactional的屬性配置信息
@Transactional 注解屬性 propagation 設(shè)置錯(cuò)誤
@Transactional 注解屬性 rollbackFor 設(shè)置錯(cuò)誤
同一個(gè)類(lèi)中方法調(diào)用,導(dǎo)致@Transactional失效
異常被catch"吃了"導(dǎo)致@Transactional失效
數(shù)據(jù)庫(kù)引擎不支持事務(wù)
隔離級(jí)別
ISOLATION_DEFAULT:使用數(shù)據(jù)庫(kù)默認(rèn)的事務(wù)隔離級(jí)別。
ISOLATION_READ_UNCOMMITTED:讀未提交。允許事務(wù)在執(zhí)行過(guò)程中,讀取其他事務(wù)未提交的數(shù)據(jù)。
ISOLATION_READ_COMMITTED:讀已提交。允許事務(wù)在執(zhí)行過(guò)程中,讀取其他事務(wù)已經(jīng)提交的數(shù)據(jù)。
ISOLATION_REPEATABLE_READ:可重復(fù)讀。在同一個(gè)事務(wù)內(nèi),任意時(shí)刻的查詢(xún)結(jié)果是一致的。
ISOLATION_SERIALIZABLE:所有事務(wù)依次執(zhí)行。
五、SpringMVC中的控制器是不是單例模式?如果是,如何保證線(xiàn)程安全?
是
單例模式下就會(huì)有線(xiàn)程安全問(wèn)題
Spring中保證線(xiàn)程安全的方法
將scop設(shè)置成非singleton:prototype,request
將控制器設(shè)計(jì)成無(wú)狀態(tài)模式。在controller中不要攜帶數(shù)據(jù),但是可以引用無(wú)狀態(tài)的service和dao
六、Spring AOP順序
正常
環(huán)繞通知前
@Before
被調(diào)用方法
@AfterReturning
@After
環(huán)繞通知后
異常
環(huán)繞通知前
@Before
被調(diào)用方法
@AfterThrowing
@After
Linux篇
一、生產(chǎn)環(huán)境服務(wù)器變慢,診斷思路和性能評(píng)估?
整機(jī):top(精簡(jiǎn)版uptime)
load average(系統(tǒng)負(fù)載均衡):1,5,15系統(tǒng)平均負(fù)載值,三個(gè)值相加除以3高于60%,系統(tǒng)壓力重
CPU:vmstat
內(nèi)存:free -m
硬盤(pán):df -h
磁盤(pán)IO:iostat -xdk
網(wǎng)絡(luò)IO:ifstat
二、生產(chǎn)環(huán)境出現(xiàn)CPU占用過(guò)高,分析思路和定位?
先用top命令找出CPU占比最高的
ps -ef或jps進(jìn)一步定位
定位到具體線(xiàn)程或者代碼
ps -mp 進(jìn)程ID -o THREAD,tid,time
將需要的線(xiàn)程ID轉(zhuǎn)換為16進(jìn)制格式(英文小寫(xiě))
jstack進(jìn)程ID|grep tid(16進(jìn)制線(xiàn)程ID英文小寫(xiě)) -A60
MySql篇
一、索引
索引是幫助Sql高效獲取數(shù)據(jù)的數(shù)據(jù)結(jié)構(gòu)。索引存儲(chǔ)在文件系統(tǒng)中。索引的文件存儲(chǔ)形式與存儲(chǔ)引擎(InnoDB、MyISAM)有關(guān)。索引文件的結(jié)構(gòu)有hash、二叉樹(shù)、B樹(shù)、B+樹(shù)
MySql索引類(lèi)型
主鍵索引
唯一索引:索引列的所有值都只能出現(xiàn)一次,必須唯一,值可以為空
普通索引:基本索引類(lèi)型,值可以為空,沒(méi)有唯一性的限制
全文索引:索引類(lèi)型為FILLTEXT??梢栽趘archar、char、text類(lèi)型的列上創(chuàng)建
組合索引:多列值組成一個(gè)索引,用于組合搜索
為什么要用B+樹(shù):
hash缺點(diǎn):比較耗費(fèi)內(nèi)存空間,不適合范圍查找
二叉樹(shù)、紅黑樹(shù)缺點(diǎn):會(huì)由于樹(shù)的深度過(guò)深而造成io次數(shù)變多,影響讀取效率
B樹(shù)缺點(diǎn):不能存取較大的數(shù)據(jù)
名詞解釋
回表:從索引中查出主鍵,再用主鍵去查索引中沒(méi)有的數(shù)據(jù)
覆蓋索引:從索引中就可以查到,不需要回表
最左匹配:在組合索引中,如果sql語(yǔ)句中用到了組合索引中的最左邊的索引,那么這條sql語(yǔ)句就可以利用這個(gè)組合索引去進(jìn)行匹配。當(dāng)遇到范圍查詢(xún)(>、<、between、like)就會(huì)停止匹配
索引下推:在最左匹配原則的基礎(chǔ)上,直接按照條件查詢(xún)的所有條件做判斷,并從存儲(chǔ)引擎中拉去符合條件的數(shù)據(jù)
索引優(yōu)化
當(dāng)使用索引進(jìn)行查詢(xún)的時(shí)候盡量不要使用表達(dá)式,把計(jì)算放到業(yè)務(wù)層而不是數(shù)據(jù)庫(kù)層
盡量使用主鍵查詢(xún),而不是其他索引,因?yàn)橹麈I查詢(xún)不會(huì)觸發(fā)會(huì)標(biāo)查詢(xún)
使用前綴索引
使用索引掃描來(lái)排序
union all、in、or都能夠使用索引,但是推薦用in
范圍列可以用到索引
強(qiáng)制類(lèi)型轉(zhuǎn)換會(huì)全表掃描
更新十分頻繁,數(shù)據(jù)區(qū)分度不高的字段上不宜建立索引
創(chuàng)建索引的列,不允許為null,可能會(huì)得到不符合預(yù)期的效果
最好不要超過(guò)三張表連接,因?yàn)樾枰猨oin的字段,數(shù)據(jù)類(lèi)型必須一致
能用limit盡量用limit
單表索引建議控制在5個(gè)以?xún)?nèi)
組合索引時(shí),但索引字段數(shù)不允許超過(guò)5個(gè)
索引不是越多越好
不要在不了解系統(tǒng)的情況下進(jìn)行過(guò)早優(yōu)化
like、不等于、大于小于走不走索引?
like語(yǔ)句要使索引生效,like后不能以%開(kāi)始,也就是說(shuō) (like %字段名%) 、(like %字段名)這類(lèi)語(yǔ)句會(huì)使索引失效,而(like 字段名)、(like 字段名%)這類(lèi)語(yǔ)句索引是可以正常使用
在使用不等于(?。交蛘撸迹?的時(shí)候無(wú)法使用索引導(dǎo)致全表掃描 is not null也無(wú)法使用索引,但是is null是可以使用索引的.
聚簇索引和非聚簇索引
聚簇索引的B+樹(shù)葉子節(jié)點(diǎn)的data是完整的數(shù)據(jù)記錄
非聚簇索引的B+樹(shù)葉子節(jié)點(diǎn)的data是主鍵的值
二、事務(wù)
原子性A:要么全部成功,要么全部失敗
一致性C:所有事務(wù)讀取同一個(gè)數(shù)據(jù)的結(jié)果是相同的
隔離性I:一個(gè)事務(wù)在提交之前,對(duì)其他事務(wù)不可見(jiàn)
持久性D:一旦事務(wù)提交,所做的操作永久保存在數(shù)據(jù)庫(kù)中
三、存儲(chǔ)引擎
InnoDB:默認(rèn)隔離級(jí)別可重復(fù)讀
解決幻讀:MVCC+Next-key Locking
MVCC:寫(xiě)操作更新最新的版本快照,讀操作讀舊版本快照,快照存儲(chǔ)在Undo日志中
Next-key Locking:鎖定一個(gè)記錄上的索引和索引之間的間隙。鎖定一個(gè)前開(kāi)后閉區(qū)間。一個(gè)索引包含:10,11,13,20。鎖定的區(qū)間為(-∞,10](10,11](11,13](13,20](20,+∞)
MyISAM