問題集錦

JVM-Class類文件結(jié)構(gòu)

常量池:字面量(字符串和final常量)和符號引用(類和接口的全限定名、字段的名稱和描述符跟啤、方法句柄和方法類型诽表、方法的名稱和描述符 )
字段表、方法表隅肥、屬性表(code屬性存放代碼)

JVM-運行時數(shù)據(jù)區(qū)域

方法區(qū)(線程共享竿奏,可能會內(nèi)存溢出):用于存儲已被虛擬機加載的類型信息、常量腥放、靜態(tài)變量泛啸、即時編譯器編譯后的代碼緩存等數(shù)據(jù)
1、以前使用永久代來實現(xiàn)方法區(qū)導(dǎo)致Java應(yīng)用更容易遇到內(nèi)存溢出的問題秃症,現(xiàn)在使用本地內(nèi)存來實現(xiàn)方法區(qū)
2候址、JDK 7的HotSpot,已經(jīng)把原本放在永久代的字符串常量池种柑、靜態(tài)變量等移出(移到堆內(nèi))
3岗仑、在JDK 8,完全廢棄了永久代聚请,使用本地內(nèi)存中實現(xiàn)的元空間來代替荠雕,把JDK 7中永久代還剩余的內(nèi)容(主要是類型信息)全部移到元空間中
4、運行時常量池:Class文件中常量池在類加載后存放到方法區(qū)的運行時常量池中(也存放直接引用)
堆(線程共享,可能會內(nèi)存溢出):
1舞虱、存放對象實例欢际, “幾乎”所有的對象實例都在這里分配內(nèi)存(由于即時編譯技術(shù)的進步,尤其是逃逸分析技術(shù)的日漸強大矾兜,棧上分配损趋、標(biāo)量替換優(yōu)化手段已經(jīng)導(dǎo)致對象可以不在堆內(nèi)分配)
2、堆中可以劃分出多個線程私有的分配緩沖區(qū) 椅寺,以更好地回收內(nèi)存浑槽,或者更快地分配內(nèi)存
3、通過參數(shù)-Xmx和-Xms設(shè)定堆內(nèi)存大小
直接內(nèi)存(可能會內(nèi)存溢出):
1返帕、直接內(nèi)存并不是虛擬機運行時數(shù)據(jù)區(qū)的一部分桐玻,也不是Java中的內(nèi)存區(qū)域
2、應(yīng)用:NIO是一種基于通道(Channel)與緩沖區(qū) (Buffer)的I/O方式荆萤,它可以使用Native函數(shù)庫直接分配堆外內(nèi)存镊靴,然后通過一個存儲在Java堆里面的 DirectByteBuffer對象作為這塊內(nèi)存的引用進行操作,這樣能在一些場景中顯著提高性能链韭,因為避免了在Java堆和Native堆中來回復(fù)制數(shù)據(jù)(零拷貝)
棧(線程私有偏竟,可能會內(nèi)存溢出和堆棧溢出):
1、每個方法被執(zhí)行的時候敞峭,Java虛擬機都會同步創(chuàng)建一個棧幀用于存儲局部變量表踊谋、操作數(shù)棧、動態(tài)連接旋讹、方法返回地址等信息
2殖蚕、每一個方法被調(diào)用直至執(zhí)行完畢的過程,就對應(yīng)著一個棧幀在虛擬機棧中從入棧到出棧的過程
局部變量表(棧幀中沉迹,大小在編譯期已經(jīng)確定睦疫,code屬性中存儲大小):
1鞭呕、用于存放方法參數(shù)和方法內(nèi)部定義的局部變量(基本類型和對象引用)笼痛、returnAddress 類型(指向了一條字節(jié)碼指令的地址),這些數(shù)據(jù)類型在局部變量表中的存儲空間以局部變量槽來表示
2琅拌、當(dāng)一個方法被調(diào)用時,使用局部變量表來完成實參到形參的傳遞
3摘刑、如果執(zhí)行的是實例方法(沒有被static修飾的方法)进宝,局部變量表中第0位存放this引用,在方法中直接訪問
4枷恕、局部變量表中的變量槽是可以重用的党晋;復(fù)用的時機是其他變量需要用到這塊變量槽,因此即使該變量已經(jīng)不被使用,也不會垃圾回收未玻,除非賦值null(不建議灾而,會被優(yōu)化掉)
5、局部變量不像類變量那樣存在“準(zhǔn)備階段”扳剿,類變量有兩次賦初始值的過程旁趟,一次在準(zhǔn)備階段,賦予系統(tǒng)初始值庇绽;另外一次在初始化階段锡搜,賦予代碼定義的初始值,因此即使在初始化階段代碼沒有為類變量賦值也沒有關(guān)系瞧掺,類變量仍然具有一個確定的初始值耕餐,不會產(chǎn)生歧義;但局部變量就不一樣了辟狈,如果一個局部變量定義了但沒有賦初始值肠缔,那它是完全不能使用的
操作數(shù)棧(棧幀中,大小在編譯期已經(jīng)確定哼转,code屬性中存儲大忻魑础):
1、兩個棧幀會出現(xiàn)一部分重疊释簿,讓下面棧幀的部分操作數(shù)棧與上面棧幀的部分局部變量表重疊在一起亚隅,這樣做不僅節(jié)約了一些空間,更重要的是在進行方法調(diào)用時就可以直接共用一部分?jǐn)?shù)據(jù)庶溶,無須進行額外的參數(shù)復(fù)制傳遞
動態(tài)連接(棧幀中):
1煮纵、每個棧幀都包含一個指向運行時常量池中該棧幀所屬方法的引用,持有這個引用是為了支持方法調(diào)用過程中的動態(tài)連接
2偏螺、Class文件的常量池中存有大量的符號引用行疏,字節(jié)碼中的方法調(diào)用指令就以常量池里指向方法的符號引用作為參數(shù),這些符號引用一部分會在類加載階段或者第一次使用的時候就被轉(zhuǎn)化為直接引用套像,這種轉(zhuǎn)化被稱為靜態(tài)解析酿联;另外一部分將在每一次運行期間都轉(zhuǎn)化為直接引用,這部分就稱為動態(tài)連接
方法返回地址(棧幀中):方法退出時使用
程序計數(shù)器(線程私有夺巩,不會內(nèi)存溢出):字節(jié)碼解釋器工作時通過改變計數(shù)器的值來選取下一條需要執(zhí)行的字節(jié)碼指令(線程切換時為了標(biāo)識每個線程執(zhí)行位置)
執(zhí)行引擎:
1贞让、在不同的虛擬機實現(xiàn)中,執(zhí)行引擎在執(zhí)行字節(jié)碼的時候柳譬,通常會有解釋執(zhí)行(通過解釋器執(zhí)行)和編譯執(zhí)行(通過即時編譯器產(chǎn)生本地代碼執(zhí)行)兩種選擇喳张,也可能兩者兼?zhèn)洌€可能會有同時包含幾個不同級別的即時編譯器一起工作的執(zhí)行引擎
2美澳、從外觀上來看销部,所有的Java虛擬機的執(zhí)行引擎輸入摸航、輸出都是一致的:輸入的是字節(jié)碼二進制流,處理過程是字節(jié)碼解析執(zhí)行的等效過程舅桩,輸出的是執(zhí)行結(jié)果
方法調(diào)用:
1酱虎、方法調(diào)用并不等同于方法中的代碼被執(zhí)行,方法調(diào)用階段唯一的任務(wù)就是確定被調(diào)用方法的版本 (即調(diào)用哪一個方法)擂涛,暫時還未涉及方法內(nèi)部的具體運行過程
2读串、Class文件的編譯過程中不包含傳統(tǒng)程序語言編譯的連接步驟,一切方法調(diào)用在Class文件里面存儲的都只是符號引用歼指,而不是直接引用
3爹土、解析:所有方法調(diào)用的目標(biāo)方法在Class文件里面都是一個常量池中的符號引用,在類加載的解析階段踩身,會將其中的一部分符號引用轉(zhuǎn)化為直接引用胀茵,即調(diào)用目標(biāo)在程序代碼寫好、編譯器進行編譯那一刻就已經(jīng)確定下來挟阻,這類方法的調(diào)用被稱為解析
(靜態(tài)方法琼娘、私有方法、final方法附鸽、構(gòu)造方法脱拼、父類方法)
4、分派
**靜態(tài)分派:重載
引用類型和實際類型在程序中都可能會發(fā)生變化坷备,區(qū)別是引用類型的變化僅僅在使用時發(fā)生熄浓,引用類型不會被改變,并且最終的引用類型是在編譯期可知的省撑;而實際類型變化的結(jié)果在運行期才可確定赌蔑,編譯器在編譯程序的時候并不知道一個對象的實際類型是什么

// 實際類型變化 
Human human = (new Random()).nextBoolean() ? new Man() : new Woman(); 
// 引用類型變化 
sr.sayHello((Man) human) ;
sr.sayHello((Woman) human);

虛擬機(或者準(zhǔn)確地說是編譯器)在重載時是通過參數(shù)的引用類型而不是實際類型作為判定依據(jù)的,由于引用類型在編譯期可知竟秫,所以在編譯階段娃惯,Javac編譯器就根據(jù)參數(shù)的引用類型決定了會使用哪個重載版本
所有依賴引用類型來決定方法執(zhí)行版本的分派動作,都稱為靜態(tài)分派肥败,靜態(tài)分派的最典型應(yīng)用表現(xiàn)就是方法重載趾浅,靜態(tài)分派發(fā)生在編譯階段,因此確定靜態(tài)分派的動作實際上不是由虛擬機來執(zhí)行的
**動態(tài)分派 覆蓋
在運行期根據(jù)實際類型確定方法執(zhí)行版本的分派過程稱為動態(tài)分派
**單分派和多分配派 重載+覆蓋
方法的接收者與方法的參數(shù)統(tǒng)稱為方法的宗量
單分派是根據(jù)一個宗量對目標(biāo)方法進行選擇馒稍,多分派則是根據(jù)多于一個宗量對目標(biāo)方法進行選擇
編譯階段:根據(jù)方法接收者的引用類型+方法參數(shù)類型才能確定使用哪個類的哪個方法皿哨,因此靜態(tài)分派屬于多分派類型
運行階段:根據(jù)方法接收者的實際類型就能確定使用哪個類(會覆蓋靜態(tài)分派的判斷,使用哪個方法是靜態(tài)分派確定)纽谒,因此動態(tài)分派屬于單分派類型

JVM-類加載機制

定義:Java虛擬機把描述類的數(shù)據(jù)從Class文件加載到內(nèi)存往史,并對數(shù)據(jù)進行校驗、轉(zhuǎn)換解析和初始化佛舱,最終形成可以被虛擬機直接使用的Java類型的過程
在Java語言里面椎例,類型的加載、連接和初始化過程都是在程序運行期間完成的
類的生命周期:加載 请祖、連接(驗證订歪、準(zhǔn)備、解析)肆捕、初始化 刷晋、使用和卸載
類加載的時機
對于初始化階段,有且只有6種情況必須對類進行初始化(主動引用)慎陵,其余都是被動引用:
1眼虱、new對象、讀寫類型靜態(tài)字段(final常量除外席纽,編譯期已放入常量池)捏悬、調(diào)用類型靜態(tài)方法時
2、對類型反射調(diào)用時
3润梯、初始化子類時过牙,要先初始化父類
4、虛擬機啟動時纺铭,初始化主類
5寇钉、方法句柄對應(yīng)的類初始化
6、接口定義了默認(rèn)方法舶赔,實現(xiàn)類初始化時扫倡,要先初始化接口
被動引用:
1、通過子類引用父類的靜態(tài)字段竟纳,不會導(dǎo)致子類初始化(對于靜態(tài)字段撵溃, 只有直接定義這個字段的類才會被初始化)
2、通過數(shù)組定義來引用類蚁袭,不會觸發(fā)此類的初始化(與數(shù)組是不同的類)
3征懈、常量在編譯階段會存入調(diào)用類的常量池中,本質(zhì)上沒有直接引用到定義常量的類,因此不會觸發(fā)定義常量的類的初始化(在編譯階段通過常量傳播優(yōu)化,已經(jīng)將常量的值直接存儲在調(diào)用類的常量池中崇决,以后調(diào)用類對常量的引用锈死,實際都被轉(zhuǎn)化為對自身常量池的引用了;也就是說薇溃,實際上調(diào)用類的Class文件之中并沒有原類的符號引用入口,這兩個類在編譯成 Class文件后就已經(jīng)不存在任何聯(lián)系了)
類加載的過程
加載:
1、通過類的全限定名獲取定義類的二進制字節(jié)流(非數(shù)組類型的加載既可以使用引導(dǎo)類加載器來完成维贺,也可以由用戶自定義的類加載器去完成(重寫一個類加載器的findClass或loadClass方法);數(shù)組類本身不通過類加載器創(chuàng)建巴帮,是由Java虛擬機直接在內(nèi)存中動態(tài)構(gòu)造出來的溯泣,但數(shù)組類與類加載器仍然有很密切的關(guān)系虐秋,因為數(shù)組類的元素類型最終還是要靠類加載器來完成加載)
2、將字節(jié)流所代表的靜態(tài)存儲結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)的運行時數(shù)據(jù)結(jié)構(gòu)
3垃沦、在內(nèi)存中生成代表類的Class對象客给,作為方法區(qū)中類的各種數(shù)據(jù)的訪問入口
驗證:
1、文件格式驗證
2肢簿、元數(shù)據(jù)驗證(是否有父類靶剑、是否繼承了不該繼承的如final類、是否實現(xiàn)了抽象類的所有方法等)
3池充、字節(jié)碼驗證:對類的方法體(Class文件中的code屬性)進行檢驗分析
4桩引、符號引用驗證:將符號引用轉(zhuǎn)換為直接引用時驗證(解析階段),通過全限定名是否能找到對應(yīng)的類等
準(zhǔn)備:
1收夸、準(zhǔn)備階段是正式為類變量分配內(nèi)存并設(shè)置初始值(默認(rèn)值)的階段坑匠,這些類變量所使用的內(nèi)存都在方法區(qū)(jdk1.8在堆中)中進行分配
2、實例變量將會在對象實例化時隨著對象一起分配在Java堆中
3咱圆、類常量在這個階段會被初始化(不是默認(rèn)值)
解析:將常量池內(nèi)的符號引用替換為直接引用
1笛辟、符號引用:符號引用與虛擬機實現(xiàn)的內(nèi)存布局無關(guān),引用的目標(biāo)并不一定是已經(jīng)加載到虛擬機內(nèi)存當(dāng)中的內(nèi)容
2序苏、直接引用:直接引用是可以直接指向目標(biāo)的指針手幢、相對偏移量或者是一個能間接定位到目標(biāo)的句柄,直接引用是和虛擬機實現(xiàn)的內(nèi)存布局直接相關(guān)的忱详,同一個符號引用在不同虛擬機實例上翻譯出來的直接引用一般不會相同围来,如果有了直接引用,那引用的目標(biāo)必定已經(jīng)在虛擬機的內(nèi)存中存在
初始化:
1匈睁、初始化階段就是執(zhí)行類構(gòu)造器方法(所有類變量的賦值動作和靜態(tài)語句塊(static{}塊)中的語句监透,執(zhí)行順序是代碼中的順序)的過程,靜態(tài)語句塊中只能訪問到定義在靜態(tài)語句塊之前的變量航唆,定義在它之后的變量胀蛮,在前面的靜態(tài)語句塊可以賦值,但是不能訪問
2糯钙、父類的類構(gòu)造器方法先執(zhí)行粪狼,父類中定義的靜態(tài)語句塊要優(yōu)先于子類的變量賦值操作
3、Java虛擬機必須保證一個類的類構(gòu)造器方法在多線程環(huán)境中被正確地加鎖同步任岸,如果多個線程同時去初始化一個類再榄,那么只會有其中一個線程去執(zhí)行這個類的類構(gòu)造器方法,其他線程都需要阻塞等待享潜,直到活動線程執(zhí)行完畢類構(gòu)造器方法困鸥,如果在一個類的類構(gòu)造器方法中有耗時很長的操作,那就可能造成多個進程阻塞剑按,在實際應(yīng)用中這種阻塞往往是很隱蔽的
4疾就、同一個類加載器下澜术,一個類型只會被初始化一次
類加載器
1、類加載器通過一個類的全限定名來獲取描述該類的二進制字節(jié)流
2猬腰、對于任意一個類瘪板,都必須由加載它的類加載器和這個類本身一起共同確立其在Java虛擬機中的唯一性,每一個類加載器漆诽,都擁有一個獨立的類名稱空間,即比較兩個類是否“相等”锣枝,只有在這兩個類是由同一個類加載器加載的前提下才有意義厢拭,否則,即使這兩個類來源于同一個 Class文件撇叁,被同一個Java虛擬機加載供鸠,只要加載它們的類加載器不同,那這兩個類就必定不相等

線程-java內(nèi)存模型

JUC-AQS

1陨闹、AQS支持獨占鎖(如ReentrantLock楞捂、ReadWriteLock的寫鎖)和共享鎖(如CountDownLatch、ReadWriteLock的讀鎖)
2趋厉、state:表示資源數(shù)寨闹,volatile修飾,需要保證可見性君账、有序性繁堡、利用CAS保證原子性操作
3、隊列節(jié)點等待狀態(tài):初始狀態(tài)乡数、取消狀態(tài)(線程超時或被中斷時椭蹄,會進入取消狀態(tài),取消狀態(tài)不會再往下執(zhí)行)净赴、喚醒狀態(tài)(表示當(dāng)前節(jié)點的后繼節(jié)點正在等待獲取資源绳矩,當(dāng)前節(jié)點在release或cancel時需要執(zhí)行unpark來喚醒后繼節(jié)點)、條件狀態(tài)(當(dāng)前節(jié)點為條件隊列節(jié)點玖翅,這個狀態(tài)在同步隊列里不會被用到)翼馆、傳播狀態(tài)(針對共享鎖,設(shè)置在head節(jié)點releaseShared(釋放共享鎖)操作需要被傳遞到下一個節(jié)點烧栋,用來保證后繼節(jié)點可以獲取共享資源)
4写妥、nextWaiter屬性:連接下一個等待condition的節(jié)點,或者在共享模式下作為一個特殊節(jié)點保存审姓,用來判斷是否為共享模式
5珍特、同步隊列:雙向鏈表,隊尾插入時會有線程競爭(自旋+CAS插入魔吐,需要前驅(qū)有效節(jié)點喚醒扎筒,使用LockSupport.park/unpark阻塞釋放線程)莱找;隊首表示獲取資源的線程節(jié)點
**acquire方法:調(diào)用子類的tryAcquire方法嘗試CAS獲取資源,成功直接返回嗜桌;失敗則阻塞線程奥溺,創(chuàng)建節(jié)點,自旋+CAS將節(jié)點放入同步隊列尾部骨宠,當(dāng)前驅(qū)有效節(jié)點是頭節(jié)點時浮定,自旋+CAS嘗試獲取資源(前驅(qū)有效節(jié)點不為頭節(jié)點時,阻塞當(dāng)前線程层亿,直到被前驅(qū)節(jié)點喚醒桦卒;即使當(dāng)前線程中間被中斷過,也可以自旋+CAS嘗試獲取資源)匿又;如果自旋+CAS嘗試獲取資源出現(xiàn)異常方灾,要將當(dāng)前節(jié)點狀態(tài)置為取消
**release方法:調(diào)用子類的tryRelease方法嘗試自旋+CAS釋放資源,喚醒同步隊列中的后繼節(jié)點(中間會移除取消狀態(tài)的節(jié)點)碌更,后繼節(jié)點自旋+CAS嘗試獲取資源裕偿,獲取成功,則從acquire方法返回
**acquireShared方法:調(diào)用子類的tryAcquireShared方法嘗試CAS獲取資源痛单,成功直接返回嘿棘;失敗則阻塞線程,創(chuàng)建節(jié)點桦他,自旋+CAS將節(jié)點放入同步隊列尾部蔫巩,當(dāng)前驅(qū)有效節(jié)點是頭節(jié)點時,自旋+CAS嘗試獲取資源(前驅(qū)有效節(jié)點不為頭節(jié)點時快压,阻塞當(dāng)前線程圆仔,直到被前驅(qū)節(jié)點喚醒;即使當(dāng)前線程中間被中斷過蔫劣,也可以自旋+CAS嘗試獲取資源)坪郭,獲取資源成功后,將當(dāng)前節(jié)點設(shè)置為頭節(jié)點脉幢,如果還有剩余資源歪沃,讓后續(xù)節(jié)點也嘗試獲取資源;如果自旋+CAS嘗試獲取資源出現(xiàn)異常(超時)嫌松,要將當(dāng)前節(jié)點狀態(tài)置為取消
**releaseShared方法:調(diào)用子類的tryReleaseShared方法嘗試自旋+CAS釋放資源沪曙,喚醒同步隊列中的后繼節(jié)點(中間會移除取消狀態(tài)的節(jié)點),后繼節(jié)點自旋+CAS嘗試獲取資源萎羔,獲取成功液走,則從acquireShared方法返回
6、子類需要實現(xiàn)的方法:tryAcquire/tryRelease(獨占)、tryAcquireShared/tryReleaseShared(共享)缘眶、isHeldExclusively(有用到Condition才需要實現(xiàn))
7嘱根、條件隊列:雙向鏈表,只有在使用了Condition(AQS的內(nèi)部類)才會存在條件隊列巷懈,在使用Condition的方法之前需要先獲取鎖
**await方法:調(diào)用之前當(dāng)前線程需要先獲取資源该抒;創(chuàng)建節(jié)點,自旋+CAS將節(jié)點放入條件隊列尾部顶燕;調(diào)用release方法釋放當(dāng)前線程已經(jīng)持有的資源凑保,并移除同步隊列節(jié)點,喚醒同步隊列中的后繼節(jié)點涌攻,如果釋放資源出現(xiàn)異常(超時)愉适,要將當(dāng)前節(jié)點狀態(tài)置為取消;當(dāng)當(dāng)前線程沒有加入到同步隊列時癣漆,進行自旋,并阻塞當(dāng)前線程剂买,直到當(dāng)前線程被喚醒(從條件隊列移到同步隊列)或期間被中斷惠爽,并記錄中斷狀態(tài);在同步隊列中自旋+CAS嘗試獲取資源瞬哼;如果當(dāng)前節(jié)點的nextWaiter不為空婚肆,說明節(jié)點在獲取鎖時由于異常或者被中斷而被取消坐慰,此時需要移除等待隊列中取消狀態(tài)的節(jié)點较性;如果期間被中斷過,拋出中斷異常
**signal方法:自旋+CAS將條件隊列中節(jié)點移除结胀,并創(chuàng)建新節(jié)點赞咙,添加到同步隊列尾部,并喚醒線程糟港,在同步中自旋+CAS嘗試獲取資源攀操,獲取成功從await方法返回


JUC-ReentrantLock

lock是顯式的獲取鎖,擁有鎖獲取與釋放的可操作性秸抚、非阻塞獲取鎖速和、可中斷的獲取鎖(獲取到鎖的線程能響應(yīng)中斷:當(dāng)獲取到鎖的線程被中斷時,鎖也會被釋放)以及超時獲取鎖(如果超時未獲取鎖剥汤,會返回)等多種synchronized關(guān)鍵字所不具備的同步特性颠放;不能將獲取lock鎖的過程寫在try塊中,因為如果在獲取鎖時發(fā)生了異常(已經(jīng)獲取鎖)吭敢,異常拋出時碰凶,鎖也會被釋放
ReentrantLock是一個可重入的互斥鎖,也被稱為“獨占鎖”,“獨占鎖”在同一個時間點只能被一個線程持有痒留,而“可重入鎖”可以被單個線程多次獲惹绰蟆;ReentrantLock又分為“公平鎖”和“非公平鎖”(默認(rèn))伸头,它們的區(qū)別體現(xiàn)在獲取鎖的機制上:在“公平鎖”的機制下匾效,線程依次排隊獲取鎖;而“非公平鎖”機制下恤磷,如果鎖是可獲取狀態(tài)面哼,不管自己是不是在隊列的head節(jié)點都會去嘗試獲取鎖
內(nèi)部有一個Sync類繼承自AQS,有兩個子類FairSync和NonfairSync
1扫步、lock方法:
非公平:調(diào)用NonfairSync中的lock方法魔策;嘗試CAS獲取資源,成功河胎,設(shè)置當(dāng)前線程持有資源闯袒;失敗,調(diào)用AQS的acquire方法獲取資源游岳;調(diào)用NonfairSync中的tryAcquire方法嘗試CAS獲取資源(不會判斷當(dāng)前節(jié)點前是否還有節(jié)點政敢,與公平鎖區(qū)別):如果沒有線程獲取資源,CAS嘗試獲取資源胚迫,設(shè)置當(dāng)前線程持有資源喷户,如果當(dāng)前線程已經(jīng)獲取過資源(可重入),在原來基礎(chǔ)上+1(不需要CAS访锻,因為無競爭)褪尝,成功直接返回;失敗則阻塞線程期犬,創(chuàng)建節(jié)點河哑,自旋+CAS將節(jié)點放入同步隊列尾部,當(dāng)前驅(qū)有效節(jié)點是頭節(jié)點時龟虎,自旋+CAS嘗試獲取資源(前驅(qū)有效節(jié)點不為頭節(jié)點時灾馒,阻塞當(dāng)前線程,直到被前驅(qū)節(jié)點喚醒遣总;即使當(dāng)前線程中間被中斷過睬罗,也可以自旋+CAS嘗試獲取資源);如果自旋+CAS嘗試獲取資源出現(xiàn)異常旭斥,要將當(dāng)前節(jié)點狀態(tài)置為取消
公平:調(diào)用FairSync中的lock方法容达;調(diào)用AQS的acquire方法獲取資源;調(diào)用FairSync中的tryAcquire方法嘗試CAS獲取資源(要判斷當(dāng)前節(jié)點前是否還有節(jié)點垂券,與非公平鎖區(qū)別):如果沒有線程獲取資源花盐,CAS嘗試獲取資源羡滑,設(shè)置當(dāng)前線程持有資源,如果當(dāng)前線程已經(jīng)獲取過資源(可重入)算芯,在原來基礎(chǔ)上+1(不需要CAS柒昏,因為無競爭),成功直接返回熙揍;失敗則阻塞線程职祷,創(chuàng)建節(jié)點,自旋+CAS將節(jié)點放入同步隊列尾部届囚,當(dāng)前驅(qū)有效節(jié)點是頭節(jié)點時有梆,自旋+CAS嘗試獲取資源(前驅(qū)有效節(jié)點不為頭節(jié)點時,阻塞當(dāng)前線程意系,直到被前驅(qū)節(jié)點喚醒泥耀;即使當(dāng)前線程中間被中斷過,也可以自旋+CAS嘗試獲取資源)蛔添;如果自旋+CAS嘗試獲取資源出現(xiàn)異常痰催,要將當(dāng)前節(jié)點狀態(tài)置為取消
2、unLock方法:公平和非公平一致迎瞧;調(diào)用AQS的release方法釋放資源陨囊;調(diào)用Sync的tryRelease方法嘗試釋放資源:獲取資源的次數(shù)必須要等于釋放資源的次數(shù),這樣才算是真正釋放了資源夹攒,才可以設(shè)置持有資源的線程為空,不需要CAS胁塞,因為無競爭咏尝;喚醒同步隊列中的后繼節(jié)點(中間會移除取消狀態(tài)的節(jié)點),后繼節(jié)點自旋+CAS嘗試獲取資源啸罢,獲取成功编检,則從acquire方法返回
3、tryLock方法:調(diào)用Sync的nonfairTryAcquire方法嘗試請求獲取資源(不會判斷當(dāng)前節(jié)點前是否還有節(jié)點扰才,非公平):如果沒有線程獲取資源允懂,CAS嘗試獲取資源,設(shè)置當(dāng)前線程持有資源衩匣,如果當(dāng)前線程已經(jīng)獲取過資源(可重入)蕾总,在原來基礎(chǔ)上+1(不需要CAS,因為無競爭)琅捏,成功直接返回生百;失敗則阻塞線程,創(chuàng)建節(jié)點柄延,自旋+CAS將節(jié)點放入同步隊列尾部蚀浆,當(dāng)前驅(qū)有效節(jié)點是頭節(jié)點時,自旋+CAS嘗試獲取資源(前驅(qū)有效節(jié)點不為頭節(jié)點時,阻塞當(dāng)前線程市俊,直到被前驅(qū)節(jié)點喚醒杨凑;即使當(dāng)前線程中間被中斷過,也可以自旋+CAS嘗試獲取資源)摆昧;如果自旋+CAS嘗試獲取資源出現(xiàn)異常撩满,要將當(dāng)前節(jié)點狀態(tài)置為取消
4、等待通知機制:使用AQS的Condition實現(xiàn)

JUC-CountDownLatch

CountDownLatch是一個同步輔助類据忘,是通過AQS實現(xiàn)的一個可重入的共享鎖鹦牛,可響應(yīng)中斷,會直接拋出中斷異常勇吊;在其他線程完成操作之前曼追,可以有一個或多個線程等待;內(nèi)部有一個Sync類汉规,繼承自AQS礼殊,實現(xiàn)了tryAcquireShared和tryReleaseShared方法
1、await方法:調(diào)用AQS的acquireSharedInterruptibly方法獲取共享資源;回調(diào)子類Sync的tryAcquireShared方法嘗試獲取資源(當(dāng)state=0的時候才可以獲取資源),成功直接返回泌参;失敗則阻塞線程,創(chuàng)建節(jié)點醉鳖,自旋+CAS將節(jié)點放入同步隊列尾部鲫竞,當(dāng)前驅(qū)有效節(jié)點是頭節(jié)點時,自旋+CAS嘗試獲取資源(前驅(qū)有效節(jié)點不為頭節(jié)點時驻债,阻塞當(dāng)前線程拆祈,直到被前驅(qū)節(jié)點喚醒;即使當(dāng)前線程中間被中斷過桨吊,也可以自旋+CAS嘗試獲取資源)伸刃,獲取資源成功后症歇,將當(dāng)前節(jié)點設(shè)置為頭節(jié)點忘晤,如果還有剩余資源蚤吹,讓后續(xù)節(jié)點也嘗試獲取資源;如果自旋+CAS嘗試獲取資源出現(xiàn)異常(超時)二驰,要將當(dāng)前節(jié)點狀態(tài)置為取消矿酵;如果中間被中斷唬复,直接拋出中斷異常
2、countDown方法:調(diào)用AQS的releaseShared方法釋放共享資源坏瘩;回調(diào)子類Sync的tryReleaseShared方法嘗試自旋+CAS釋放資源盅抚,喚醒同步隊列中的后繼節(jié)點(中間會移除取消狀態(tài)的節(jié)點),后繼節(jié)點自旋+CAS嘗試獲取資源倔矾,獲取成功妄均,則從acquireShared方法返回

JUC-ReentrantReadWriteLock

ReentrantReadWriteLock維護了一對相關(guān)的鎖:共享鎖readLock和獨占鎖writeLock
共享鎖readLock用于讀操作,能同時被多個線程獲饶淖浴丰包;獨占鎖writeLock用于寫入操作,只能被一個線程持有(支持鎖降級:持有寫鎖的線程可以在寫鎖未釋放之前獲得讀鎖)
Condition只有在寫鎖中用到壤巷,讀鎖是不支持Condition的
內(nèi)部有一個Sync類繼承自AQS邑彪,有兩個子類FairSync和NonfairSync:讀鎖和寫鎖共用一個狀態(tài),高16位標(biāo)識讀計數(shù)了胧华,低16位標(biāo)識寫重入次數(shù)寄症;內(nèi)部有一個靜態(tài)內(nèi)部類繼承自ThreadLocal用于讀記錄讀線程重入次數(shù)
內(nèi)部有兩個靜態(tài)內(nèi)部類:ReadLock和WriteLock
1、ReadLock:內(nèi)部有l(wèi)ock和unLock方法矩动,參考CountDownLatch
**lock方法:調(diào)用AQS的acquireShared方法獲取資源有巧;回調(diào)Sync的tryAcquireShared方法嘗試CAS獲取資源(持有寫鎖的線程可以繼續(xù)獲取讀鎖,沒有線程獲取寫鎖悲没,可以嘗試CAS獲取資源)篮迎,成功,更新當(dāng)前線程鎖重入次數(shù)示姿,直接返回甜橱;失敗,則阻塞線程栈戳,創(chuàng)建節(jié)點岂傲,自旋+CAS將節(jié)點放入同步隊列尾部,當(dāng)前驅(qū)有效節(jié)點是頭節(jié)點時子檀,自旋+CAS嘗試獲取資源(前驅(qū)有效節(jié)點不為頭節(jié)點時镊掖,阻塞當(dāng)前線程,直到被前驅(qū)節(jié)點喚醒命锄;即使當(dāng)前線程中間被中斷過堰乔,也可以自旋+CAS嘗試獲取資源)偏化,獲取資源成功后脐恩,將當(dāng)前節(jié)點設(shè)置為頭節(jié)點,如果還有剩余資源侦讨,讓后續(xù)節(jié)點也嘗試獲取資源驶冒;如果自旋+CAS嘗試獲取資源出現(xiàn)異常(超時)苟翻,要將當(dāng)前節(jié)點狀態(tài)置為取消;如果中間被中斷骗污,直接拋出中斷異常
**unLock方法:調(diào)用AQS的releaseShared方法釋放共享資源崇猫;回調(diào)子類Sync的tryReleaseShared方法嘗試自旋+CAS釋放資源,更新線程重入次數(shù)需忿;喚醒同步隊列中的后繼節(jié)點(中間會移除取消狀態(tài)的節(jié)點)诅炉,后繼節(jié)點自旋+CAS嘗試獲取資源,獲取成功屋厘,則從acquireShared方法返回
2涕烧、WriteLock:內(nèi)部有l(wèi)ock和unLock方法,參考ReentrantLock

Spring-IOC

1汗洒、解析注冊:使用Resource定位xml配置议纯;使用BeanDefinitionReader讀取配置,并封裝成BeanDefinition溢谤;使用BeanDefinitionRegistry將BeanDefinition注冊到BeanDefinitionMap中
2瞻凤、BeanFatory中bean的加載過程
**轉(zhuǎn)換對應(yīng)的beanName:傳入的參數(shù)可能不是bean的name,可能是別名、FactoryBean(&開頭)
**如果是單例遂鹊,嘗試從緩存中獲取單例bean摔踱,獲取失敗再嘗試從singletonFactories中獲取單例工廠,通過單例工廠去加載(在創(chuàng)建單例bean時為解決依賴注入结笨,不等bean創(chuàng)建完成就將創(chuàng)建bean的工廠提早曝光并加入緩存中,其他單例bean創(chuàng)建時如果需要依賴該bean湿镀,直接從緩存中獲取單例bean或獲取工廠去創(chuàng)建)(調(diào)用工廠的getObject方法先獲取實例化但還未初始化的單例bean炕吸,加入到earlySingletonObjects緩存中,將單例工廠從singletonFactories中移除勉痴,返回單例bean)赫模,單例bean的轉(zhuǎn)換:獲取的bean可能是原始狀態(tài)(有可能獲取的是Factorybean,需要調(diào)用FactoryBean中的getObject方法獲取單例bean)
**如果緩存中沒有蒸矛,以下開始創(chuàng)建單例bean
**根據(jù)beanName嘗試從beanDefinitionMap中獲取對應(yīng)的beanDefinition中的配置瀑罗,如果獲取不到配置,嘗試遞歸根據(jù)parentBeanFactory去加載(調(diào)用父類工廠的getBean方法)
**前置處理:創(chuàng)建單例bean之前雏掠,記錄單例bean正在創(chuàng)建狀態(tài)斩祭,用于檢測循環(huán)依賴
**通過單例工廠創(chuàng)建單例bean,調(diào)用單例工廠的getObject方法獲取提早曝光的單例bean(實例化還未初始化):處理override屬性:為了后面實例化單例bean時更好的處理乡话,這里先預(yù)先判斷一下是否需要覆蓋或重載摧玫,后面處理的原理就是在實例化bean時如果檢測到methodOverrides時,會動態(tài)地為當(dāng)前bean生成代理并使用對應(yīng)的攔截器為bean做增強處理绑青;實例化單例bean前處理诬像;短路處理:Spring AOP代理實現(xiàn)屋群,如果需要使用代理bean且代理bean已經(jīng)創(chuàng)建,直接返回坏挠;實例化單例bean(將BeanDefinition轉(zhuǎn)換為BeanWrapper(對反射相關(guān)API的簡單封裝芍躏,使得上層使用反射完成相關(guān)的業(yè)務(wù)邏輯大大的簡化),需要選擇不同的實例化策略:如果有需要覆蓋或動態(tài)替換的方法降狠,需要使用cglib進行動態(tài)代理对竣,因為可以在創(chuàng)建代理的同時將動態(tài)方法織入類中,否則可以直接用反射榜配;構(gòu)造函數(shù)注入循環(huán)依賴問題spring不能解決 循環(huán)依賴是在實例化后處理的)柏肪;實例化bean后處理;如果需要解決循環(huán)依賴(滿足3個條件:單例芥牌、允許循環(huán)依賴烦味、當(dāng)前bean正在創(chuàng)建),則提早曝光單例工廠(將單例工廠放入工廠緩存singletonFactories中壁拉,其他單例bean在創(chuàng)建時調(diào)用getObject方法可以獲取未創(chuàng)建好的單例bean谬俄,getObject方法中實現(xiàn)Spring AOP 的advice動態(tài)織入;屬性注入(填充)(遞歸初始化)弃理;激活aware方法(通過aware方法可以獲取對應(yīng)的資源:BeanNameAware 獲取bean名稱溃论,BeanClassLoaderAware 獲取bean的類加載器;BeanFactory 獲取bean的工廠痘昌,即加載到IOC容器中)钥勋;初始化單例bean前處理;激活用戶自定義的init方法:如afterPropertiesSet方法辆苔、init-method算灸,afterPropertiesSet先執(zhí)行,init-method后執(zhí)行驻啤;初始化單例bean后處理(spring AOP 在這里實現(xiàn))菲驴;檢測循環(huán)依賴;注冊destroy-method銷毀方法)骑冗;
**后置處理:創(chuàng)建單例bean之后赊瞬,移除單例bean正在創(chuàng)建狀態(tài),用于檢測循環(huán)依賴
**將單例bean放入單例緩存singletonObjects贼涩,從單例工廠緩存singletonFactories中移除單例工廠巧涧,從單例bean緩存earlySingletonObjects中移除單例bean,保存已注冊的單例bean
**類型轉(zhuǎn)換:將bean轉(zhuǎn)換為需要的類型
3遥倦、ApplicationContext
**環(huán)境準(zhǔn)備:如系統(tǒng)屬性或環(huán)境變量的準(zhǔn)備及驗證
**加載BeanFactory谤绳,并讀取配置文件:創(chuàng)建BeanFactory(DefaultListableBeanFactory);定制BeanFactory(在基本容器的基礎(chǔ)上,增加了是否允許覆蓋是否允許擴展的設(shè)置闷供,并提供了對注解@Qualifier、@Autowired的支持)统诺;加載beanDefinition歪脏,讀取配置文件(通過beanDefinitionReader加載beanDefinition(并注冊到beanFactory的BeanDefinitionMap中));使用全局變量記錄beanFactory
**對BeanFactory進行功能填充:如對@Qualifier和@Autowired注解的支持粮呢;增加AspectJ支持婿失;增加屬性注冊編輯器(Spring DI 依賴注入時 Date類型是無法識別的)
**子類通過覆蓋方法做額外處理
**激活(調(diào)用)BeanFactory處理器(容器級),可以有多個啄寡,通過排序依次處理豪硅;beanFactory處理器可以在實例化任何bean之前獲得配置元數(shù)據(jù)并修改BeanDefinition(如${}替換);@ComponentScan就是在這里實現(xiàn)的挺物;注冊bean處理器(BeanFactory沒有注冊(因此不能使用)懒浮,需要手動注冊),在bean創(chuàng)建時調(diào)用
**注冊攔截bean創(chuàng)建的bean處理器识藤,這里只是注冊砚著,真正調(diào)用是在getBean方法中
**國際化處理
**初始化應(yīng)用消息廣播器
**子類覆蓋方法處理
**在所有注冊的bean中查找要監(jiān)聽的bean,注冊到消息廣播器中(注冊監(jiān)聽器痴昧,并在合適的時候通知監(jiān)聽器)
**通過beanFactory加載bean(非延遲加載):ApplicationContext在啟動時會加載所有的單例bean稽穆,調(diào)用getBean方法(上面Spring中BeanFactory加載bean的過程)
**完成刷新,通知生命周期管理器lifecycleProcessor刷新過程赶撰,并通過事件通知監(jiān)聽者


Spring-FactoryBean舌镶、BeanFactory、ObjectFactory

1豪娜、FactoryBean:
一般情況下餐胀,Spring通過反射機制來實例化bean,而這樣可能需要很多配置瘤载,可以通過實現(xiàn)FactoryBean接口以編碼的方式來代替
在IOC容器的基礎(chǔ)上給Bean的實現(xiàn)加上了一個簡單工廠模式和裝飾模式骂澄,是一個可以生產(chǎn)對象和裝飾對象的工廠bean
它是泛型的,只能固定生產(chǎn)某一類對象惕虑,而不像BeanFactory那樣可以生產(chǎn)多種類型的Bean
當(dāng)bean實現(xiàn)FactoryBean接口時坟冲,通過工廠的getBean方法返回的是FactoryBean中的getObject方法返回的實例,如果想要返回當(dāng)前bean溃蔫,需要以&開頭
2健提、BeanFactory:對象工廠,用于實例化和保存對象
3伟叛、ObjectFactory:某個特定的工廠私痹,用于在項目啟動時,延遲實例化對象,解決循環(huán)依賴問題紊遵, 調(diào)用它的getObject方法時账千,才會觸發(fā) Bean 實例化


Spring單例下如何解決循環(huán)依賴(三級緩存)

Spring中循環(huán)依賴包括構(gòu)造器依賴和setter注入依賴,Spring只能解決單例setter注入依賴(注入時會返回提前暴露的創(chuàng)建中的bean)暗膜,構(gòu)造器依賴無法解決(因為只有實例化之后才能曝光匀奏,實例化前曝光是有風(fēng)險的),對于原型模式学搜,循環(huán)依賴也是無法解決的(因為不使用緩存)
bean什么時候才會提早曝光:單例娃善、創(chuàng)建中、允許循環(huán)依賴
1瑞佩、嘗試從singletionObjects中獲取實例
2聚磺、嘗試從earlySingletionObjects中獲取實例
3、根據(jù)beanName嘗試從singletonFactories中獲取ObjectFactory炬丸,調(diào)用getObject方法創(chuàng)建bean(這里只是實例化了bean)瘫寝,放到earlySingletionObjects中,并從singletonFactories中移除ObjectFactory(互斥操作)這時已經(jīng)可以通過容器的getBean方法獲取到bean


Spring-AOP

1稠炬、靜態(tài)代理矢沿、動態(tài)代理:靜態(tài)代理直接調(diào)用目標(biāo)類方法;動態(tài)代理通過反射調(diào)用目標(biāo)類方法
2酸纲、JDK動態(tài)代理捣鲸、CGLIB動態(tài)代理:JDK是在運行期間創(chuàng)建接口的實現(xiàn)類來完成對目標(biāo)對象的代理;CGLIB采用了非常底層的字節(jié)碼技術(shù)闽坡,其原理是通過字節(jié)碼技術(shù)為一個類創(chuàng)建子類(生成代理類Class的二進制字節(jié)碼栽惶;通過Class.forName加載二進制字節(jié)碼,生成Class對象疾嗅;通過反射機制獲取代理類實例構(gòu)造外厂,并初始化代理類對象),并在子類中采用方法攔截的技術(shù)攔截所有父類方法的調(diào)用代承,順勢織入橫切邏輯
3汁蝶、連接點(方法執(zhí)行處)、切入點(何處織入通知)论悴、通知(處理時機及處理邏輯)掖棉、切面(包括切入點和通知)
4、源碼:
**解析器解析AOP代理注解膀估,生成BeanDefination幔亥,并注冊到BeanDefinitionMap中
**創(chuàng)建自動代理創(chuàng)建器(用來實現(xiàn)AOP)(AnnotationAwareAspectJAutoProxyCreator)
**選擇代理實現(xiàn)方式:默認(rèn)如果目標(biāo)對象有實現(xiàn)接口,則使用JDK動態(tài)代理察纯;否則使用CGLIB代理(無法覆寫final方法帕棉,可以通過proxy-target-class屬性強制使用CGLIB代理)针肥;expose-proxy屬性是為了解決有時候目標(biāo)對象內(nèi)部的自我調(diào)用無法實現(xiàn)切面增強的問題(強制暴露代理,在代碼中可以獲取這個代理進行顯式調(diào)用)
**創(chuàng)建AOP動態(tài)代理:自動代理創(chuàng)建器實現(xiàn)了BeanPostProcessor接口香伴,Spring在目標(biāo)bean初始化完成之后會調(diào)用其postProcessAfterInitialization方法來創(chuàng)建AOP動態(tài)代理慰枕;獲取增強,獲取所有增強中目標(biāo)bean可用的增強即纲;創(chuàng)建代理工廠具帮,根據(jù)配置設(shè)置JDK動態(tài)代理,或者CGLIB代理崇裁,將目標(biāo)bean及其增強添加到代理工廠,通過代理工廠創(chuàng)建代理并返回(將增強組成攔截器鏈束昵,執(zhí)行目標(biāo)方法時拔稳,執(zhí)行攔截器鏈,中間會調(diào)用目標(biāo)方法)


Spring事務(wù)失效的場景

1锹雏、注解@Transactional配置的方法非public權(quán)限修飾(可以開啟 AspectJ 代理模式解決)
2巴比、注解@Transactional所在類非Spring容器管理的bean
3、注解@Transactional所在類中礁遵,注解修飾的方法被類內(nèi)部方法調(diào)用(無事務(wù)方法調(diào)用有事務(wù)方法轻绞,事務(wù)失效,使用代理對象調(diào)用解決佣耐,且要在啟動類上加注解@EnableAspectJAutoProxy(exposeProxy = true)):Spring在掃描Bean的時候會自動為標(biāo)注了@Transactional注解的類生成一個代理對象(proxy)政勃,當(dāng)有注解的方法被調(diào)用的時候,實際上是代理對象調(diào)用的兼砖,代理對象在調(diào)用之前會開啟事務(wù)奸远,執(zhí)行事務(wù)的操作,但是同類中的方法互相調(diào)用讽挟,相當(dāng)于this.B()懒叛,此時的B方法并非是代理對象調(diào)用,而是直接通過原有的Bean直接調(diào)用耽梅,所以注解會失效)
4薛窥、業(yè)務(wù)代碼拋出異常類型非RuntimeException,事務(wù)失效
5眼姐、業(yè)務(wù)代碼中存在異常時诅迷,使用try…catch…語句塊捕獲,而catch語句塊沒有throw new RuntimeExecption異常(最難被排查到問題且容易忽略)
6众旗、注解@Transactional中Propagation屬性值設(shè)置錯誤即Propagation.NOT_SUPPORTED(一般不會設(shè)置此種傳播機制)
7竟贯、mysql關(guān)系型數(shù)據(jù)庫,且存儲引擎是MyISAM而非InnoDB逝钥,則事務(wù)會不起作用(基本開發(fā)中不會遇到)


@Transactional原理

@Transactional是基于Spring AOP的屑那,以@Transactional注解為連接點拱镐,@Transactional注解的切面邏輯類似于@Around


Spring Boot-啟動原理

@SpringBootApplication注解:
1、@SpringBootConfiguration注解:繼承Configuration持际,表示啟動類是IOC容器的配置類
2沃琅、@EnableAutoConfiguration注解:通過@AutoConfigurationPackage注解獲取自動配置包,返回當(dāng)前主類的同級以及子級的中斷自動配置組件蜘欲;開啟springboot的配置功能益眉,借助@Import({EnableAutoConfigurationImportSelector.class})實現(xiàn),將自動配置組件對應(yīng)的bean定義都加載到IoC容器中姥份,通過Spring的SpringFactoriesLoader(Spring工廠加載器)去讀取META-INF/spring.factories中的配置類信息郭脂,通過反射生成一個配置類(里面有許多bean定義),最后將這些bean定義加載到IOC容器中(但是不是所有存在于spring.factories中的配置都進行加載澈歉,而是通過@ConditionalOnClass注解進行判斷條件是否成立(只要導(dǎo)入相應(yīng)的stater展鸡,條件就能成立),如果條件成立則加載配置類埃难,否則不加載該配置類)
https://www.cnblogs.com/xiaopotian/p/11052917.html
3莹弊、@ComponentScan注解:自動掃描并加載符合條件的組件(如@Component和@Repository等)或者bean定義,最終將這些bean加載到IoC容器中涡尘,在beanFactoryProcessor中調(diào)用
run方法:
1忍弛、創(chuàng)建監(jiān)聽器
2、加載配置環(huán)境
3考抄、創(chuàng)建ConfigurableApplicationContext
4细疚、spring.factories加載,bean實例化

image.png


Spring Cloud

分布式事務(wù)

CAP:一致性川梅、可用性惠昔、容錯性
BASE:可用性、容錯性挑势、最終一致性(可能會存在中間狀態(tài)如:處理中)
1镇防、兩階段提交(2PC)(基于數(shù)據(jù)庫,MySQL和Oracle支持):準(zhǔn)備階段(資源鎖定潮饱,執(zhí)行本地事務(wù)来氧,并寫日志undo(修改后數(shù)據(jù))/redo日志(修改前數(shù)據(jù)))、提交/回滾階段香拉,中間由事務(wù)管理器控制全局事務(wù)啦扬,資源鎖需要等到兩個階段結(jié)束才釋放,性能較差凫碌,會出現(xiàn)死鎖問題
2扑毡、seata改進2PC:事務(wù)管理器(事務(wù)發(fā)起服務(wù)引入,負責(zé)發(fā)起全局事務(wù)盛险,發(fā)起全局提交或全局回滾的指令)瞄摊、事務(wù)協(xié)調(diào)器(單獨的服務(wù)勋又,控制,維護全局事務(wù)狀態(tài)换帜,協(xié)調(diào)各分支事務(wù)提交/回滾)楔壤、資源管理器(控制每個分支事務(wù),使用DataSourceProxy連接數(shù)據(jù)庫惯驼,使用ConnectionProxy操作數(shù)據(jù)庫蹲嚣,目的就是在第一階段執(zhí)行本地事務(wù)的同時,寫入undo_log表(保存修改前和修改后的數(shù)據(jù))祟牲,因此第一階段就能進行事務(wù)提交隙畜,并釋放資源;第二階段提交時只需要刪除undo_log表數(shù)據(jù)说贝,回滾時反向執(zhí)行即可)
3议惰、TCC:預(yù)處理Try(業(yè)務(wù)檢查(一致性)及資源預(yù)留(隔離)執(zhí)行)、確認(rèn) Confirm(確認(rèn)提交)狂丝、撤銷Cancel(回滾)换淆;如處理表(中間有狀態(tài)哗总、流水號)几颜、被調(diào)用方保持冪等、并提供查詢接口/回調(diào)
4讯屈、可靠消息最終一致性:本地消息表+消息中間件(通過本地事務(wù)保證數(shù)據(jù)業(yè)務(wù)操作和消息的一致性蛋哭,然后通過定時任務(wù)將消息發(fā)送至消息中間件,待確認(rèn)消息發(fā)送給消費方成功再將消息刪除)
要解決以下問題:
**本地事務(wù)與消息發(fā)送的原子性問題
**事務(wù)參與方接收消息的可靠性(一定能夠接收到消息)
**消息重復(fù)消費的問題(冪等性)
MQ的ack(即消息確認(rèn))機制涮母,消費者監(jiān)聽MQ谆趾,如果消費者接收到消息并且業(yè)務(wù)處理完成后向MQ 發(fā)送ack(即消息確認(rèn)),此時說明消費者正常消費消息完成叛本,MQ將不再向消費者推送消息沪蓬,否則消費者會不斷重試向消費者來發(fā)送消息
5、最大努力通知


zookeeper

zookeeper是一個典型的分布式數(shù)據(jù)一致性解決方案
節(jié)點類型:持久節(jié)點来候、持久順序節(jié)點跷叉、臨時節(jié)點(客戶端會話,非TCP連接营搅,只能作為葉子節(jié)點)云挟、臨時順序節(jié)點
集群:Leader、Follower转质、Observer(不參與Leader選舉园欣、也不參與寫操作的“過半寫成功”策略)
Leader選舉:

分布式鎖

Redis分布式鎖:
1、加鎖:set 命令要用 set key value px milliseconds nx休蟹,替代 setnx + expire 需要分兩次執(zhí)行命令的方式沸枯,保證了原子性日矫;給鎖加上一個過期時間,即使Redis客戶端中間出現(xiàn)異常(來不及調(diào)用lua腳本釋放鎖)也可以保證過期時鎖會自動釋放(但是如果Redis服務(wù)端異常就沒辦法解決)辉饱;超時問題解決:lua腳本+額外線程進行鎖延時
2搬男、解鎖:將lua腳本傳到j(luò)edis.eval()方法里,并使參數(shù)KEYS[1]賦值為lockKey(鎖標(biāo)志)彭沼,ARGV[1]賦值為requestId(Redis客戶端標(biāo)志)缔逛;在執(zhí)行的時候,首先會獲取鎖對應(yīng)的value值姓惑,檢查是否與requestId相等褐奴,如果相等則解鎖(刪除key);比較requestId是為了解決超時問題:如果在加鎖和釋放鎖之間的邏輯執(zhí)行的太長于毙,以至于超出了鎖的超時限制敦冬,就會出現(xiàn)問題,因為這時候鎖過期了唯沮,第二個線程重新持有了這把鎖脖旱,但是緊接著第一個線程執(zhí)行完了業(yè)務(wù)邏輯,就把鎖給釋放了介蛉,第三個線程就會在第二個線程邏輯執(zhí)行完之前拿到了鎖
3萌庆、可重入性:對客戶端的 set 方法進行包裝,使用線程的 Threadlocal 變量存儲當(dāng)前線程持有鎖的計數(shù)币旧,還需要考慮內(nèi)存鎖計數(shù)的過期時間
問題:如果存儲鎖對應(yīng)key的那個節(jié)點掛了的話践险,就可能存在丟失鎖的風(fēng)險,導(dǎo)致出現(xiàn)多個客戶端持有鎖的情況吹菱,這樣就不能實現(xiàn)資源的獨占了(即Redis服務(wù)端出現(xiàn)問題)巍虫,即使是Redis主從也不能解決問題(Redis的主從同步通常是異步的)
解決:
1、Redlock算法:輪流嘗試在每個節(jié)點上創(chuàng)建鎖鳍刷,過期時間較短占遥,一般就幾十毫秒,至少要在大多數(shù)節(jié)點上成功創(chuàng)建鎖输瓜,才說明獲取到鎖瓦胎,客戶端計算創(chuàng)建鎖的時間,如果創(chuàng)建鎖的時間小于超時時間前痘,就是創(chuàng)建成功了凛捏;如果創(chuàng)建鎖失敗了,那么就依次刪除以前創(chuàng)建過的鎖芹缔;如果其他客戶端已經(jīng)創(chuàng)建鎖坯癣,就得不斷輪詢?nèi)L試獲取鎖
2、Redisson:RedissonLock 同樣沒有解決節(jié)點掛掉的時候最欠,存在丟失鎖的風(fēng)險的問題示罗;Redisson 提供了實現(xiàn)了redlock算法的 RedissonRedLock惩猫,RedissonRedLock 真正解決了單點失敗的問題,代價是需要額外的為 RedissonRedLock 搭建Redis環(huán)境
zookeeper分布式鎖:
獲取鎖:客戶端獲取鎖時蚜点,調(diào)用create方法創(chuàng)建臨時順序節(jié)點(Zookeeper會保證所有的客戶端中轧房,最終只有一個客戶端能夠創(chuàng)建成功,沒有獲取到鎖的客戶端需要創(chuàng)建一個節(jié)點去Watch監(jiān)聽鎖節(jié)點)
釋放鎖:當(dāng)前獲取鎖的客戶端宕機/業(yè)務(wù)邏輯執(zhí)行完都會移除臨時順序鎖節(jié)點绍绘,并通知所有Watch節(jié)點去重新嘗試獲取鎖

Redis-基礎(chǔ)數(shù)據(jù)結(jié)構(gòu)

1奶镶、String:類似于ArrayList,字節(jié)數(shù)組陪拘,用途:緩存用戶信息(序列化和反序列化)
2厂镇、List:類似于LinkedList,鏈表+壓縮列表(數(shù)據(jù)量少時左刽,只用壓縮列表)捺信,增刪快,查詢慢欠痴,用途:異步隊列
3迄靠、Hash:類似于HashMap,數(shù)組+鏈表喇辽,漸進式reHash(中間新舊數(shù)據(jù)都會讀)掌挚,用途:緩存用戶信息
4、Set:類似于HashSet茵臭,value值為空疫诽,用途:去重
5舅世、ZSet: 類似于SortedSet 和 HashMap 的結(jié)合體旦委,跳躍列表,既要隨機增刪雏亚,又要排序缨硝,用途:核心企業(yè)/供應(yīng)商列表
6、跳躍列表:每一層是一個單向鏈表罢低,每一層有一個額外的節(jié)點去定位每一層的頭節(jié)點


image.png

Redis-緩存一致性

1查辩、先刪除緩存,再更新數(shù)據(jù)庫(緩存設(shè)置過期時間)
問題:如果兩個并發(fā)操作网持,一個讀操作宜岛,一個寫操作,寫操作刪除緩存功舀,讀操作從緩存讀取數(shù)據(jù)失敗萍倡,從數(shù)據(jù)庫讀取數(shù)據(jù)成功,然后更新緩存辟汰,寫操作更新數(shù)據(jù)庫列敲,無法避免這種情況的緩存一致性
解決:延遲雙刪:寫操作更新數(shù)據(jù)庫成功后阱佛,sleep(睡眠時間不好控制)一段時間,再刪除一次緩存
2戴而、先更新數(shù)據(jù)庫凑术,再刪除緩存(緩存設(shè)置過期時間)(推薦)
原理:更新數(shù)據(jù)庫時,會加鎖所意,其他操作不能操作這條數(shù)據(jù)
問題:寫操作時淮逊,更新數(shù)據(jù)庫成功,刪除緩存失敗扶踊,讀操作仍讀取緩存中的舊數(shù)據(jù)
解決:
**消息隊列:更新數(shù)據(jù)庫壮莹,插入本地消息表,刪除緩存姻檀,消息隊列重試去刪除緩存(需要考慮消息隊列的一些常見問題)
**消息隊列+binlog日志:更新數(shù)據(jù)庫時命满,會插入binlog日志,通過canal讀取binlog日志绣版,推送給消息隊列胶台,消息隊列重試去刪除緩存(與業(yè)務(wù)代碼解耦)
3、為什么是刪除緩存杂抽,而不是更新緩存
**問題1:如果兩個并發(fā)操作诈唬,一個寫操作a,一個寫操作b缩麸,a更新數(shù)據(jù)庫铸磅,釋放鎖,b更新數(shù)據(jù)庫杭朱,釋放鎖阅仔,b更新緩存完成,a更新緩存完成弧械,無法避免這種情況的緩存一致性
**問題2:每次都更新緩存八酒,會導(dǎo)致性能消耗

Redis-緩存穿透、緩存雪崩刃唐、緩存擊穿

緩存穿透
定義:查找一定不存在的數(shù)據(jù)(如惡意攻擊)羞迷,在緩存中根據(jù)key查不到,在數(shù)據(jù)庫中也查詢不到画饥,所以不會存到緩存中衔瓮,每次都要查詢數(shù)據(jù)庫
解決:
1、布隆過濾:將一切可能查詢的key存到map中,請求過來時先去map中查找,不存在直接丟棄
2生逸、即使在數(shù)據(jù)庫中查詢不到腮敌,空值也存到緩存中榨馁,設(shè)置過期時間(浪費空間庄撮,且在有效期內(nèi)可能數(shù)據(jù)不一致)
緩存雪崩
定義:緩存中大量的數(shù)據(jù)同時失效(如服務(wù)掛掉和同時過期)凹嘲,直接去數(shù)據(jù)庫中查詢
解決:
1堂飞、過期時間設(shè)置均勻(避免同時過期)(事前)
2昼接、緩存服務(wù)高可用(主從+哨兵)(事前)
3爽篷、限流:緩存失效時,通過加鎖或者隊列來控制讀數(shù)據(jù)庫寫緩存的線程數(shù)量(如對某個key只允許一個線程查詢數(shù)據(jù)和寫緩存慢睡,其他線程等待)逐工,避免數(shù)據(jù)庫崩掉(事中)
4、Redis 持久化:一旦重啟漂辐,自動從磁盤上加載數(shù)據(jù)泪喊,快速恢復(fù)緩存數(shù)據(jù)(事后)
緩存擊穿
定義:大量請求同時訪問一個或多個熱點key,key如果瞬間失效髓涯,會直接請求數(shù)據(jù)庫袒啼,導(dǎo)致數(shù)據(jù)庫崩掉
解決:
1、若緩存的數(shù)據(jù)是基本不會發(fā)生更新的纬纪,則可嘗試將該熱點數(shù)據(jù)設(shè)置為永不過期
2蚓再、若緩存的數(shù)據(jù)更新不頻繁,且緩存刷新的整個流程耗時較少的情況下包各,則可以采用基于 Redis摘仅、zookeeper 等分布式中間件的分布式互斥鎖,或者本地互斥鎖以保證僅少量的請求能請求數(shù)據(jù)庫并重新構(gòu)建緩存问畅,其余線程則在鎖釋放后能訪問到新緩存
3娃属、若緩存的數(shù)據(jù)更新頻繁或者緩存刷新的流程耗時較長的情況下,可以利用定時任務(wù)在緩存過期前主動的重新構(gòu)建緩存或者延后緩存的過期時間护姆,以保證所有的請求能一直訪問到對應(yīng)的緩存

Redis-持久化

1矾端、RDB全量快照(數(shù)據(jù))直接復(fù)制,快
需要解決的問題:不能影響服務(wù)響應(yīng)签则,如何保證邊持久化邊響應(yīng)
解決:使用Copy On Write機制來實現(xiàn)快照持久化
原理:Redis 在持久化時會產(chǎn)生一個子進程须床,子進程剛剛產(chǎn)生時铐料,和父進程共享內(nèi)存里面的代碼段和數(shù)據(jù)段渐裂,這是 Linux 操作系統(tǒng)的機制,在進程分離的一瞬間钠惩,內(nèi)存的增長幾乎沒有明顯變化柒凉;子進程只做數(shù)據(jù)持久化,不會修改現(xiàn)有的內(nèi)存數(shù)據(jù)結(jié)構(gòu)篓跛,只是對數(shù)據(jù)結(jié)構(gòu)進行遍歷讀取膝捞,然后序列化寫到磁盤中;當(dāng)父進程對其中一個頁面的數(shù)據(jù)進行修改時愧沟,會將被共享的頁面復(fù)制一份分離出來蔬咬,然后對這個復(fù)制的頁面進行修改鲤遥,這時子進程相應(yīng)的頁面是沒有變化的,還是進程產(chǎn)生時那一瞬間的數(shù)據(jù)
2林艘、AOF增量日志(指令)需要指令重放盖奈,慢,一般是先存到磁盤狐援,然后再執(zhí)行指令
需要解決的問題:文件會越來越大钢坦,需要定期進行AOF重寫;服務(wù)宕機啥酱,數(shù)據(jù)丟失的問題
原理:創(chuàng)建子進程對內(nèi)存遍歷轉(zhuǎn)換成指令爹凹,序列化到一個新的AOF日志文件中;序列化完畢后再將操作期間發(fā)生的增量AOF日志追加到這個新的AOF日志文件中镶殷,追加完畢后就立即替代舊的AOF日志文件禾酱,瘦身工作就完成了;AOF日志在內(nèi)存緩存中绘趋,需要異步將數(shù)據(jù)刷回到磁盤宇植,如果機器突然宕機,AOF日志內(nèi)容可能還沒有來得及完全刷到磁盤中埋心,這個時候就會出現(xiàn)日志丟失指郁,因此需要每隔 1s(可配置)左右執(zhí)行一次 fsync 操作(強制刷新到緩存)
3、通常 Redis 的主節(jié)點不會進行持久化操作拷呆,持久化操作主要在從節(jié)點進行闲坎,從節(jié)點是備份節(jié)點,沒有來自客戶端請求的壓力
4茬斧、混合持久化:重啟 Redis 時腰懂,很少使用 RDB 來恢復(fù)內(nèi)存狀態(tài),因為會丟失大量數(shù)據(jù)(重啟期間的數(shù)據(jù)沒有保存)项秉,通常使用 AOF 日志重放(重啟期間的數(shù)據(jù)會追加)绣溜,但是重放 AOF 日志性能相對 RDB 來說要慢很多,這樣在 Redis 實例很大的情況下娄蔼,啟動需要花費很長的時間怖喻;可以將RDB的內(nèi)容和增量的AOF存放在一起,這里的AOF不再是全量的日志岁诉,而是自持久化開始到持久化結(jié)束的這段時間發(fā)生的增量 AOF日志锚沸,通常這部分AOF日志很小,于是在Redis重啟的時候涕癣,開啟AOF哗蜈,先加載RDB的內(nèi)容,然后再重放增量AOF日志就可以完全替代之前的AOF全量文件重放,重啟效率因此大幅得到提升

Redis-過期策略

Redis是單線的距潘,刪除也會占用線程的時間炼列,Redis采用的是定期刪除 + 懶惰刪除策略(定期刪除是集中處理,惰性刪除是零散處理)
定期刪除策略
Redis單線程默認(rèn)會每秒進行十次過期掃描音比,過期掃描不會遍歷過期字典中所有的 key唯鸭,而是采用了一種簡單的貪心策略
1、從過期字典中隨機 20 個 key
2硅确、刪除這 20 個 key 中已經(jīng)過期的 key
3目溉、如果過期的 key 比率超過 1/4,那就重復(fù)步驟 1
4菱农、同時缭付,為了保證過期掃描不會出現(xiàn)循環(huán)過度,導(dǎo)致線程卡死現(xiàn)象循未,算法還增加了掃描時間的上限陷猫,默認(rèn)不會超過 25ms
為什么不掃描所有的過期key?
1的妖、會導(dǎo)致線上讀寫請求出現(xiàn)明顯的卡頓現(xiàn)象(所以要盡量避免大量key同時過期)
2绣檬、即使掃描有 25ms 的時間上限:假如有 101 個客戶端同時將請求發(fā)過來了,然后前 100 個請求的執(zhí)行時間都是25ms嫂粟,那么第 101 個指令需要等待多久才能執(zhí)行娇未?2500ms(因為單線程),這個就是客戶端的卡頓時間
從庫的過期策略
1星虹、從庫不會進行過期掃描零抬,從庫對過期的處理是被動的,主庫在 key 到期時宽涌,會在 AOF 文件里增加一條 del 指令平夜,同步到所有的從庫,從庫通過執(zhí)行這條 del 指令來刪除過期的key
2卸亮、因為指令同步是異步進行的忽妒,所以主庫過期的 key 的 del 指令沒有及時同步到從庫的話,會出現(xiàn)主從數(shù)據(jù)的不一致兼贸,主庫沒有的數(shù)據(jù)在從庫里還存在
懶惰刪除策略
為什么要懶惰刪除段直?
1、刪除指令 del 會直接釋放對象的內(nèi)存寝受,大部分情況下坷牛,這個指令非常快很澄,沒有明顯延遲,不過如果刪除的 key 是一個非常大的對象,比如一個包含了千萬元素的 hash甩苛,那么刪除操作就會導(dǎo)致單線程卡頓
2蹂楣、Redis 內(nèi)部實際上并不是只有一個主線程,它還有幾個異步線程專門用來處理一些耗時的操作讯蒲,可以用異步線程實現(xiàn)懶惰刪除

Redis-內(nèi)存淘汰機制

1痊土、noeviction:當(dāng)內(nèi)存超出最大內(nèi)存,寫入請求會報錯墨林,但是刪除和讀請求可以繼續(xù)(一般不使用赁酝,但是是默認(rèn)的)
2、allkeys-lru:當(dāng)內(nèi)存超出最大內(nèi)存旭等,在所有的key中酌呆,移除最少使用的key,只把Redis當(dāng)作緩存時使用(推薦)
3搔耕、allkeys-random:當(dāng)內(nèi)存超出最大內(nèi)存隙袁,在所有的key中,隨機移除某個key(一般不使用)
4弃榨、volatile-lru:當(dāng)內(nèi)存超出最大內(nèi)存菩收,在設(shè)置了過期時間key的字典中,移除最少使用的key(不會移除沒有設(shè)置過期時間的)鲸睛,把Redis既當(dāng)緩存娜饵,又做持久化的時候使用
5、volatile-random:當(dāng)內(nèi)存超出最大內(nèi)存官辈,在設(shè)置了過期時間key的字典中划咐,隨機移除某個key(不會移除沒有設(shè)置過期時間的)
6、volatile-ttl:當(dāng)內(nèi)存超出最大內(nèi)存钧萍,在設(shè)置了過期時間key的字典中褐缠,優(yōu)先移除剩余時間ttl 最少的(不會移除沒有設(shè)置過期時間的)
LRU算法:
1、實現(xiàn) LRU 算法除了需要 key/value 字典外风瘦,還需要附加一個鏈表队魏,鏈表中的元素按照一定的順序進行排列
2、當(dāng)空間滿的時候万搔,會踢掉鏈表尾部的元素
3胡桨、當(dāng)字典的某個元素被訪問時,它在鏈表中的位置會被移動到表頭瞬雹,所以鏈表的元素排列順序就是元素最近被訪問的時間順序
4昧谊、位于鏈表尾部的元素就是不被重用的元素,所以會被踢掉酗捌;位于表頭的元素就是最近剛剛被人用過的元素呢诬,所以暫時不會被踢(雙向鏈表)
近似 LRU 算法
1涌哲、Redis 使用的是一種近似 LRU 算法,之所以不使用 LRU 算法尚镰,是因為需要消耗大量的額外的內(nèi)存阀圾,需要對現(xiàn)有的數(shù)據(jù)結(jié)構(gòu)進行較大的改造
2、近似LRU 算法則很簡單狗唉,在現(xiàn)有數(shù)據(jù)結(jié)構(gòu)的基礎(chǔ)上使用隨機采樣法+額外字段(最后一次被訪問的時間戳)來淘汰元素初烘,能達到和 LRU 算法非常近似的效果
LFU算法
1、Redis 4.0 里引入了一個新的淘汰策略 —— LFU(最近最少使用)算法
2分俯、LFU 表示按最近的訪問頻率進行淘汰肾筐,它比 LRU 更加精準(zhǔn)地表示了一個 key 被訪問的熱度
3、如果一個 key 長時間不被訪問缸剪,只是剛剛偶然被用戶訪問了一下吗铐,那么在使用 LRU 算法下它是不容易被淘汰的,因為 LRU 算法認(rèn)為當(dāng)前這個 key 是很熱的
4橄登、而 LFU 是需要追蹤最近一段時間的訪問頻率抓歼,如果某個 key 只是偶然被訪問一次是不足以變得很熱的,它需要在近期一段時間內(nèi)被訪問很多次才有機會被認(rèn)為很熱

Redis-哨兵

1拢锹、負責(zé)持續(xù)監(jiān)控主從節(jié)點的健康谣妻,當(dāng)主節(jié)點掛掉時,自動選擇一個最優(yōu)的從節(jié)點切換為主節(jié)點
2卒稳、客戶端來連接主從時蹋半,會首先連接哨兵,通過哨兵來查詢主節(jié)點的地址充坑,然后再去連接主節(jié)點進行數(shù)據(jù)交互
3减江、當(dāng)主節(jié)點發(fā)生故障時,客戶端會重新向哨兵獲取主節(jié)點地址捻爷,哨兵會將最新的主節(jié)點地址告訴客戶端辈灼,無需重啟即可自動完成節(jié)點切換
4、主節(jié)點掛掉了也榄,原先的主從復(fù)制也斷開了巡莹,客戶端和損壞的主節(jié)點也斷開了,從節(jié)點被提升為新的主節(jié)點甜紫,其它從節(jié)點開始和新的主節(jié)點建立復(fù)制關(guān)系降宅,客戶端通過新的主節(jié)點繼續(xù)進行交互
5、哨兵會持續(xù)監(jiān)控已經(jīng)掛掉了主節(jié)點囚霸,待它恢復(fù)后腰根,集群會進行調(diào)整,原先掛掉的主節(jié)點現(xiàn)在變成了從節(jié)點拓型,從現(xiàn)在的主節(jié)點那里建立復(fù)制關(guān)系
6额嘿、哨兵進行主從切換時瘸恼,客戶端如何知道地址變更了 ? 在建立連接的時候進行了主節(jié)點地址變更判斷,查詢主節(jié)點地址岩睁,然后跟內(nèi)存中的主節(jié)點地址進行比對钞脂,如果變更了揣云,就斷開所有連接捕儒,重新使用新地址建立新連接;如果是舊的主節(jié)點掛掉了邓夕,那么所有正在使用的連接都會被關(guān)閉刘莹,然后在重連時就會用上新地址

Redis-應(yīng)用

1、Scan:掃描海量數(shù)據(jù)(有游標(biāo))
2焚刚、HyperLogLog:統(tǒng)計UV
3点弯、布隆過濾器:推薦去重(布隆過濾器能準(zhǔn)確過濾掉那些已經(jīng)看過的內(nèi)容,那些沒有看過的新內(nèi)容矿咕,它也會過濾掉極小一部分 (誤判))抢肛,當(dāng)布隆過濾器說某個值存在時,這個值可能不存在碳柱;當(dāng)它說不存在時捡絮,那就肯定不存在
原理:對key多次無偏hash,每個hash取模數(shù)組長度莲镣,確定位置福稳,將該位置置為1;查詢是否存在時瑞侮,再次對key多次無偏hash的圆,取模,確定位置是否為1半火;位置可以重復(fù)利用越妈,因此會有誤差

HashMap

MySQL-索引

B+樹索引的優(yōu)點:
1、索引按照順序存儲數(shù)據(jù)钮糖,可以用來做ORDER BY和GROUP BY操作
2梅掠、索引中存儲了實際的索引列值,所以某些査詢只使用索引就能夠完成全部査詢(非一級索引的葉子節(jié)點存儲主鍵)
3藐鹤、索引大大減少了服務(wù)器需要掃描的數(shù)據(jù)量
4瓤檐、索引可以幫助服務(wù)器避免排序和臨時表
5、索引可以將隨機I/O變?yōu)轫樞騃/O
B+樹索引的缺點:
1娱节、如果不是按照索引的最左列開始査找挠蛉,則無法使用索引
2、不能跳過索引中的列(只能使用跳過前的列)
3肄满、如果查詢中有某個列的范圍査詢谴古,則其右邊所有列都無法使用索引優(yōu)化査找(但是可以作為值返回)质涛;如果范圍査詢列值的數(shù)量有限,那么可以使用多個等于條件來代替范圍條件
索引策略
1掰担、獨立的列:索引列不能是表達式的一部分汇陆,也不能是函數(shù)的參數(shù),因此要始終將索引列單獨放在比較符號的一側(cè)
2带饱、前綴索引和索引選擇性:前綴越長毡代,選擇性越好
3、聚合(多列)索引:
當(dāng)出現(xiàn)服務(wù)器對多個索引做相交操作時(通常有多個AND條件)或?qū)Χ鄠€索引做聯(lián)合操作時(通常有多個OR條件)勺疼,通常意味著需要一個包含所有相關(guān)列的多列索引教寂,而不是多個獨立的單列索引(需要合并)
多列索引中索引的順序(需要兼顧排序和分組):當(dāng)不需要考慮排序和分組時,將選擇性最高的列放在前面通常是很好的
4执庐、聚簇索引:當(dāng)表有聚簇索引時酪耕,它的數(shù)據(jù)行實際上存放在索引的葉子頁中,因為無法同時把數(shù)據(jù)行存放在兩個不同的地方轨淌,所以一個表只能有一個聚簇索引(覆蓋索引可以模擬多個聚簇索引的情況)
5迂烁、二級索引:訪問需要兩次索引査找,而不是一次递鹉,因為二級索引葉子節(jié)點保存的不是指向行的物理位置的指針盟步,而是行的主鍵值,這意味著通過二級索引查找行梳虽,存儲引擎需要找到二級索引的葉子節(jié)點獲得對應(yīng)的主鍵值址芯,然后根據(jù)這個值去聚簇索引中査找到對應(yīng)的行,這里做了重復(fù)的工作:兩次B+樹査找而不是一次(回表查詢)
6窜觉、普通索引和唯一索引:普通索引在查找到一條記錄后會繼續(xù)查找谷炸,而唯一索引會終止查找
使用索引排序
1、只有當(dāng)索引的列順序和ORDER BY子句的順序完全一致禀挫,并且所有列的排序方向(倒序或正序)都一樣時旬陡,MySQL才能夠使用索引來對結(jié)果做排序
2、如果査詢需要關(guān)聯(lián)多張表语婴,則只有當(dāng)ORDER BY子句引用的字段全部為第一個表時描孟,才能使用索引做排序
3、ORDER BY子句和查找型查詢的限制是一樣的:需要滿足索引的最左前綴的要求砰左;否則MySQL都需要執(zhí)行排序操作匿醒,而無法利用索引排序
有一種情況下ORDER BY子句可以不滿足索引的最左前綴的要求,就是前導(dǎo)列為常量的時候缠导;即如果WHERE子句或者JOIN子句中對這些列指定了常量廉羔,就可以“彌補”索引的不足
B樹與B+樹的區(qū)別
1、B樹可能在非葉子節(jié)點命中返回僻造;B+不可能在非葉子結(jié)點命中
2憋他、B+樹葉子節(jié)點存放所有數(shù)據(jù)孩饼;B+樹葉子節(jié)點之間又是一個鏈表

MySQL-事務(wù)

事務(wù)的特性
1、原子性:一個事務(wù)必須被視為一個不可分割的最小工作單元竹挡,整個事務(wù)中的所有操作要么全部提交成功镀娶,要么全部失敗回滾
2、一致性:數(shù)據(jù)庫總是從一個一致性的狀態(tài)轉(zhuǎn)換到另外一個一致性的狀態(tài)
3揪罕、隔離性:通常來說(涉及到隔離級別)梯码,一個事務(wù)所做的修改在最終提交以前,對其他事務(wù)是不可見的
4耸序、持久性:通常來說(涉及到持久級別)忍些,一旦事務(wù)提交鲁猩,則其所做的修改就會永久保存到數(shù)據(jù)庫中坎怪,此時即使系統(tǒng)崩潰,修改的數(shù)據(jù)也不會丟失
事務(wù)的隔離級別
1廓握、讀未提交:一個事務(wù)可以讀取到其他事務(wù)未提交的數(shù)據(jù)(臟讀)
2搅窿、讀已提交(不可重復(fù)讀):一個事務(wù)只能讀取到其他事務(wù)此刻已提交的數(shù)據(jù)
3、可重復(fù)讀:一個事務(wù)只能讀取到其他事務(wù)在該事務(wù)開啟時已提交的數(shù)據(jù)(快照讀)隙券,但是無法避免幻讀(單指插入男应,兩次讀取的結(jié)果不一致)
可重復(fù)讀是MySQL的默認(rèn)事務(wù)隔離級別,InnoDB通過MVCC(多版本并發(fā)控制)和next-key lock解決了幻讀
4娱仔、串行讀:強制事務(wù)串行執(zhí)行沐飘,解決了幻讀問題,在讀取的每一行數(shù)據(jù)上都加鎖牲迫,所以可能導(dǎo)致大量的超時和鎖爭用的問題
幻讀:
1耐朴、幻讀指的是一個事務(wù)在前后兩次查詢同一個范圍的時候,后一次查詢看到了前一次查詢沒有看到的行(單指插入)盹憎;在可重復(fù)讀隔離級別下筛峭,普通的查詢是快照讀,是不會看到別的事務(wù)插入的數(shù)據(jù)的(MVCC)陪每,幻讀在“當(dāng)前讀”下仍會出現(xiàn)(加鎖讀時影晓,只鎖當(dāng)前滿足條件的行)
2、通過加間隙鎖解決當(dāng)前讀導(dǎo)致的幻讀檩禾,跟間隙鎖存在沖突關(guān)系的挂签,是跟 “往這個間隙中插入一個記錄 ”這個操作,間隙鎖之間都不存在沖突關(guān)系盼产;間隙鎖和行鎖合稱next-key lock饵婆,每個next-key lock是前開后閉區(qū)間
3、間隙鎖是在可重復(fù)讀隔離級別下才會生效的
4辆飘、一個并發(fā)問題:任意鎖住一行啦辐,如果這一行不存在的話就插入谓传,如果存在這一行就更新它的數(shù)據(jù);因為鎖的是間隙鎖芹关,并發(fā)時會鎖競爭续挟,發(fā)生死鎖
MVCC-多版本并發(fā)控制(可重復(fù)讀下)
查詢(同時滿足下面條件的,才作為結(jié)果返回):
a侥衬、査找版本早于當(dāng)前事務(wù)版本的數(shù)據(jù)行(也就是行的系統(tǒng)版本號小于或等于事務(wù)的系統(tǒng)版本號)诗祸,這樣可以確保事務(wù)讀取的行,要么是在事務(wù)開始前已經(jīng)存在的轴总,要么是事務(wù)自身插入或者修改過的
b直颅、行的刪除版本要么未定義,要么大于當(dāng)前事務(wù)版本號怀樟,這可以確保事務(wù)讀取到的行功偿,在事務(wù)開始之前未被刪除
插入:為新插入的每一行保存當(dāng)前事務(wù)版本號作為行版本號
刪除:為刪除的每一行保存當(dāng)前事務(wù)版本號作為行刪除標(biāo)識
修改:插入一行新記錄,保存當(dāng)前事務(wù)版本號作為行版本號往堡,同時保存當(dāng)前事務(wù)版本號到原來的行作為行刪除標(biāo)識
優(yōu)點:保存這兩個額外系統(tǒng)版本號械荷,使大多數(shù)讀操作都可以不用加鎖,這樣設(shè)計使得讀數(shù)據(jù)操作很簡單虑灰,性能很好吨瞎,并且也能保證只會讀取到符合標(biāo)準(zhǔn)的行
缺點:每行記錄都需要額外的存儲空間,需要做更多的行檢査工作穆咐,以及一些額外的維護工作
MySQL沒有完全解決幻讀問題
如:事務(wù)a先查詢(MVCC)颤诀,事務(wù)b插入(next-key),事務(wù)a更新(next-key对湃,會加版本號)崖叫,事務(wù)a查詢(MVCC),兩次查詢的結(jié)果不同
意向鎖(表級鎖):意向鎖是由數(shù)據(jù)庫自己維護的熟尉,一般來說归露,給一行數(shù)據(jù)加上共享鎖之前,數(shù)據(jù)庫會自動在這張表上面加一個意向共享鎖(IS鎖)斤儿;給一行數(shù)據(jù)加上排他鎖之前剧包,數(shù)據(jù)庫會自動在這張表上面加一個意向排他鎖(IX鎖)
意向鎖可以認(rèn)為是共享鎖和互斥鎖在數(shù)據(jù)表上的標(biāo)識,通過意向鎖可以快速判斷表中是否有記錄被上鎖往果,從而避免通過遍歷的方式來查看表中有沒有記錄被上鎖疆液,提升加鎖效率
如要加表級別的互斥鎖,這時候數(shù)據(jù)表里面如果存在行級別的互斥鎖或者共享鎖的陕贮,加鎖就會失敗堕油,此時直接根據(jù)意向鎖就能知道這張表是否有行級別的X鎖或者S鎖

MySQL-查詢性能優(yōu)化

從下面幾點進行優(yōu)化:
1、減少掃描行數(shù)(索引)
2、減少返回的行數(shù)或列數(shù)(limit或避免*)
如:
1掉缺、證件號碼卜录、證件名稱、證件類型(聚合索引眶明,冗余索引艰毒,順序問題,減少回表)
2搜囱、select a.id,a.name from user a inner join (select b.id from user order by a.userId limit 1000,10) b on a.id = b.id
(原始的寫法:select a.id,a.name from user a order by a.userId limit 1000,10)
(使用一級索引丑瞧,減少回表)
join(小表驅(qū)動大表,大表用索引)
1蜀肘、在可以使用被驅(qū)動表的索引(join字段)情況下绊汹,使用join語句,性能比強行拆成多個單表執(zhí)行SQL語句的性能要好扮宠;如果使用join語句的話西乖,需要讓小表(根據(jù)條件查詢出來少的表)做驅(qū)動表
2、在判斷要不要使用join語句時涵卵,就是看explain結(jié)果里面浴栽,Extra字段里面有沒有出現(xiàn)“Block Nested Loop”字樣(出現(xiàn)則不用join)
3、如果用left join的話轿偎,左邊的表一定是驅(qū)動表嗎?不是
4被廓、如果兩個表的join包含多個條件的等值匹配坏晦,是都要寫到on里面呢,還是只把一個條件寫到on里面嫁乘,其他條件寫到where部分昆婿?寫到on里面
5、在MySQL里蜓斧,NULL跟任何值執(zhí)行等值判斷和不等值判斷的結(jié)果仓蛆,都是NULL;select NULL =NULL 的結(jié)果挎春,也是返回 NULL

MySQL-執(zhí)行流程

MySQL執(zhí)行一個査詢的過程:
1看疙、客戶端發(fā)送一條査詢給服務(wù)器
2、服務(wù)器先檢査査詢緩存直奋,如果命中了緩存能庆,則立刻返回存儲在緩存中的結(jié)果,否則進入下一階段
3脚线、服務(wù)器端進行SQL解析搁胆、預(yù)處理,再由優(yōu)化器生成對應(yīng)的執(zhí)行計劃
4、MySQL根據(jù)優(yōu)化器生成的執(zhí)行計劃渠旁,調(diào)用存儲引擎的API來執(zhí)行査詢
5攀例、將結(jié)果返回給客戶端
更新:先找到要更新的數(shù)據(jù),從磁盤讀入內(nèi)存顾腊;在執(zhí)行器中執(zhí)行語句肛度,調(diào)用引擎先把記錄寫到redo log(覆蓋寫,磁盤中投慈,物理日志)里面(原先的記錄會寫到undo log中承耿,用于回滾),并更新內(nèi)存伪煤,此時還未提交事務(wù)加袋,在適當(dāng)?shù)臅r候,再將記錄更新到磁盤抱既;執(zhí)行器寫到binlog(不覆蓋寫职烧,磁盤中,邏輯日志(語句))防泵;引擎將redo log改成提交狀態(tài)蚀之,更新完成,即兩階段提交

ThreadLocal

1捷泞、一個線程對應(yīng)多個ThreadLocal足删,但只有一個ThreadLocalMap(在當(dāng)前線程內(nèi))
2、多個線程可以使用同一個ThreadLocal锁右,但是是隔離的
3失受、ThreadLocalMap是ThreadLocal的內(nèi)部類
4、ThreadLocalMap的key為ThreadLocal(弱引用)咏瑟,value為存儲的值
5拂到、內(nèi)存泄漏:當(dāng)ThreadLocal被回收時,ThreadLocalMap中就可能出現(xiàn)key為null的Entry码泞,沒有任何辦法訪問這些key為null的Entry的value兄旬,如果當(dāng)前線程再遲遲不結(jié)束的話,這些key為null的Entry的value就會一直存在一條強引用鏈:Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value永遠無法回收(沒有調(diào)用remove)余寥,造成內(nèi)存泄漏
6领铐、為什么key使用弱引用:如果key使用強引用,如果當(dāng)前線程再遲遲不結(jié)束的話(如線程池中復(fù)用線程)劈狐,可能會出現(xiàn)整個Entry對象都不會被回收罐孝,也會出現(xiàn)內(nèi)存泄漏問題(更難解決);如果key使用弱引用肥缔,即使沒有手動刪除莲兢,key也會被回收,但是會出現(xiàn)value不會被回收
7、內(nèi)存泄漏的根源是:由于ThreadLocalMap的生命周期跟Thread一樣長改艇,如果沒有手動刪除對應(yīng)key就會導(dǎo)致內(nèi)存泄漏收班,而不是因為弱引用
8、內(nèi)存泄漏解決:使用static修飾ThreadLocal引用(這樣保證ThreadLocal始終保持被引用谒兄,不會被回收)摔桦,但是最后還是要調(diào)用remove方法(ThreadLocal不會被回收,value會回收)


最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末承疲,一起剝皮案震驚了整個濱河市邻耕,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌燕鸽,老刑警劉巖油宜,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件菜皂,死亡現(xiàn)場離奇詭異粥喜,居然都是意外死亡乘凸,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進店門党远,熙熙樓的掌柜王于貴愁眉苦臉地迎上來削解,“玉大人,你說我怎么就攤上這事沟娱》胀裕” “怎么了?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵花沉,是天一觀的道長柳爽。 經(jīng)常有香客問我,道長碱屁,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任蛾找,我火速辦了婚禮娩脾,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘打毛。我一直安慰自己柿赊,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布幻枉。 她就那樣靜靜地躺著碰声,像睡著了一般。 火紅的嫁衣襯著肌膚如雪熬甫。 梳的紋絲不亂的頭發(fā)上胰挑,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天,我揣著相機與錄音,去河邊找鬼瞻颂。 笑死豺谈,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的贡这。 我是一名探鬼主播茬末,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼盖矫!你這毒婦竟也來了丽惭?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤辈双,失蹤者是張志新(化名)和其女友劉穎责掏,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體辐马,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡拷橘,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了喜爷。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片冗疮。...
    茶點故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖檩帐,靈堂內(nèi)的尸體忽然破棺而出术幔,到底是詐尸還是另有隱情,我是刑警寧澤湃密,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布诅挑,位于F島的核電站,受9級特大地震影響泛源,放射性物質(zhì)發(fā)生泄漏拔妥。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一达箍、第九天 我趴在偏房一處隱蔽的房頂上張望没龙。 院中可真熱鬧,春花似錦缎玫、人聲如沸硬纤。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽筝家。三九已至,卻和暖如春邻辉,著一層夾襖步出監(jiān)牢的瞬間溪王,已是汗流浹背腮鞍。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留在扰,地道東北人缕减。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像芒珠,于是被迫代替她去往敵國和親桥狡。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,577評論 2 353

推薦閱讀更多精彩內(nèi)容

  • 邏輯回歸如何解決過擬合問題皱卓?過擬合大部分原因是由于特征過多導(dǎo)致的裹芝,我們可以使用以下兩種方法來解決過擬合的問題。 減...
    b0591d0dc6ba閱讀 4,387評論 0 6
  • Redis 是完全開源免費的娜汁,遵守BSD協(xié)議嫂易,是一個高性能的key-value數(shù)據(jù)庫。 Redis 與其他 key...
    編程小世界閱讀 260評論 0 0
  • 摘要: 本文對面試/筆試過程中經(jīng)常會被問到的一些關(guān)于數(shù)據(jù)庫(MySQL)的問題進行了梳理和總結(jié)掐禁,包括數(shù)據(jù)庫索引怜械、數(shù)...
    Yt_cc閱讀 377評論 0 0
  • 在開發(fā)過程中,遇到的問題和解決方案傅事,當(dāng)做備忘缕允,以后會持續(xù)更新。 1. Attempted to dereferen...
    乜_啊_閱讀 10,977評論 2 7
  • 從三月份找實習(xí)到現(xiàn)在蹭越,面了一些公司障本,掛了不少,但最終還是拿到小米响鹃、百度驾霜、阿里、京東买置、新浪粪糙、CVTE、樂視家的研發(fā)崗...
    時芥藍閱讀 42,239評論 11 349