湖南郴州市seo是做什么工作的
Version : V1.0
- 北科Java面試寶典
- 一、Java基礎(chǔ)面試題【24道】
- 二、JVM虛擬機(jī)面試題【14道】
- 三、集合相關(guān)面試題【17道】
- 四、多線程 【25道】
- 五、IO【5道】
- 六、網(wǎng)絡(luò)編程 【9道】
- 七、MySQL以及SQL面試題【20道】
- 八、常用框架【19道】
- 九、中間件和分布式 【54道】
- 十、設(shè)計(jì)模式面試 【2道】
- 十一、 數(shù)據(jù)結(jié)構(gòu) 【19道】
- 十二、 工具使用 【3道】

北科Java面試寶典
一、Java基礎(chǔ)面試題【24道】
- 聊一聊Java平臺(tái)的理解!
Java 本身是一種面向?qū)ο蟮恼Z(yǔ)言,最顯著的特性有兩個(gè)方面,一是所謂的“書寫一次,到處運(yùn)行”,能夠
非常容易地獲得跨平臺(tái)能力;另外就是垃圾收集,Java 通過垃圾收集器回收分配內(nèi)存,大部分情況下,
程序員不需要自己操心內(nèi)存的分配和回收。 一次編譯、到處運(yùn)行”說的是Java語(yǔ)言跨平臺(tái)的特性,Java的
跨平臺(tái)特性與Java虛擬機(jī)的存在密不可分,可在不同的環(huán)境中運(yùn)行。比如說Windows平臺(tái)和Linux平臺(tái)
都有相應(yīng)的JDK,安裝好JDK后也就有了Java語(yǔ)言的運(yùn)行環(huán)境。其實(shí)Java語(yǔ)言本身與其他的編程語(yǔ)言沒有
特別大的差異,并不是說Java語(yǔ)言可以跨平臺(tái),而是在不同的平臺(tái)都有可以讓Java語(yǔ)言運(yùn)行的環(huán)境而
已,所以才有了Java一次編譯,到處運(yùn)行這樣的效果。 Java具有三大特性: 封裝,繼承,多態(tài).利用這三大特
性增加代碼的復(fù)用率和靈活度. - String、StringBuffer、StringBuilder的區(qū)別!
String的典型的不可變類型,內(nèi)部使用final關(guān)鍵字修飾,所以每次進(jìn)行字符串操作(拼接,截取等)都
會(huì)產(chǎn)生新的對(duì)象!在開發(fā)中,會(huì)使用到大量的字符串操作!所以字符串的臨時(shí)對(duì)象,會(huì)對(duì)程序操作較大
的性能開銷!產(chǎn)生了大量的內(nèi)存浪費(fèi)!
StringBuilder比StringBuffer速度快, StringBuffer比String速度快
StringBuffer線程安全 - int和Integer的區(qū)別!
int是基本數(shù)據(jù)類型,是Java的8個(gè)原始數(shù)據(jù)類型之一,直接存數(shù)值。
Integer是int對(duì)應(yīng)的包裝類,在拆箱和裝箱中java 可以根據(jù)上下文,自動(dòng)進(jìn)行轉(zhuǎn)換,極大地簡(jiǎn)化了相關(guān)
編程。
int是基本類型,Integer是對(duì)象,用一個(gè)引用指向這個(gè)對(duì)象。由于Integer是一個(gè)對(duì)象,在JVM中對(duì)象需
要一定的數(shù)據(jù)結(jié)構(gòu)進(jìn)行描述,相比int而言,其占用的內(nèi)存更大一些。 - == 和 equals 的區(qū)別是什么?
是直接比較的兩個(gè)對(duì)象的堆內(nèi)存地址,如果相等,則說明這兩個(gè)引用實(shí)際是指向同一個(gè)對(duì)象地址的。
因此基本數(shù)據(jù)類型和String常量是可以直接通過來直接比較的。
對(duì)于引用對(duì)象對(duì)比是否相等使用equals方法,equals方法比較的是對(duì)象中的值是否相等, 但是要從寫
hashcode和equals - 聊一聊JDK1.8版本的新特性!
拉姆達(dá)表達(dá)式
函數(shù)式接口,
方法引用
新的日期類LocalDate
引入了stream類,支持鏈?zhǔn)骄幊?/li> - final關(guān)鍵字的作用?
final關(guān)鍵字可以用來修飾變量、方法和類。
被final修飾的類該類成為最終類,無法被繼承。
被final修飾的方法將成為最終方法,無法被子類重寫。但是,該方法仍然可以被繼承。
final修飾類中的屬性或者變量
無論屬性是基本類型還是引用類型,final所起的作用都是變量里面存放的“值”不能變。
對(duì)于一個(gè)final變量,如果是基本數(shù)據(jù)類型的變量,則其數(shù)值一旦在初始化之后便不能更改;如果是引用
類型的變量,則在對(duì)其初始化之后便不能再讓其指向另一個(gè)對(duì)象。 - 什么是內(nèi)存泄漏和內(nèi)存溢出!
內(nèi)存泄漏(memoryleak),是指應(yīng)用程序在申請(qǐng)內(nèi)存后,無法釋放已經(jīng)申請(qǐng)的內(nèi)存空間,一次內(nèi)存泄漏危
害可以忽略,但如果任其發(fā)展最終會(huì)導(dǎo)致內(nèi)存溢出(outofmemory)。如讀取 文件后流要進(jìn)行及時(shí)的
關(guān)閉以及對(duì)數(shù)據(jù)庫(kù)連接的釋放。
內(nèi)存溢出(outofmemory)是指應(yīng)用程序在申請(qǐng)內(nèi)存時(shí),沒有足夠的內(nèi)存空間供其使用。如我們?cè)陧?xiàng)目中
對(duì)于大批量數(shù)據(jù)的導(dǎo)入,采用分批量提交的方式。 - 抽象類和接口的區(qū)別!
抽象類 : 抽象類必須用 abstract 修飾,子類必須實(shí)現(xiàn)抽象類中的抽象方法,如果有未實(shí)現(xiàn)的,那么子類
也必須用 abstract 修飾。抽象類默認(rèn)的權(quán)限修飾符為 public,可以定義為 public 或 procted,如果定
義為 private,那么子類則無法繼承。抽象類不能創(chuàng)建對(duì)象
接口和抽象類的區(qū)別 : 抽象類只能繼承一次,但是可以實(shí)現(xiàn)多個(gè)接口
接口和抽象類必須實(shí)現(xiàn)其中所有的方法,抽象類中如果有未實(shí)現(xiàn)的抽象方法,那么子類也需要定義為抽
象類。抽象類中可以有非抽象的方法
接口中的變量必須用 public static final 修飾,并且需要給出初始值。所以實(shí)現(xiàn)類不能重新定義,也不能
改變其值。
接口中的方法默認(rèn)是 public abstract,也只能是這個(gè)類型。不能是 static,接口中的方法也不允許子類
覆寫,抽象類中允許有static 的方法 - Error和Execption的區(qū)別?
Exception 和 Error 都是繼承了 Throwable 類,在 Java 中只有 Throwable 類型的實(shí)例才可以被拋出
(throw)或者捕獲(catch),它是異常處理機(jī)制的基本組成類型。
Exception 和 Error 體現(xiàn)了 Java 平臺(tái)設(shè)計(jì)者對(duì)不同異常情況的分類。Exception 是程序正常運(yùn)行中,可
以預(yù)料的意外情況,可能并且應(yīng)該被捕獲,進(jìn)行相應(yīng)處理。
Error 是指在正常情況下,不大可能出現(xiàn)的情況,絕大部分的 Error 都會(huì)導(dǎo)致程序(比如 JVM自身)處于
非正常的、不可恢復(fù)狀態(tài)。既然是非正常情況,所以不便于也不需要捕獲,常見的比如
OutOfMemoryError 之類,都是 Error 的子類。
Exception 又分為可檢查(checked)異常和不檢查(unchecked)異常,可檢查異常 - 說一說常見的Execption和解決方案!
數(shù)組越界異常 : Java.lang.ArrayIndexOutofBoundsException
產(chǎn)生的原因:訪問了不存在的索引
解決的辦法:索引0到數(shù)組長(zhǎng)度-1的范圍內(nèi)取值
空指針異常 : Java.lang.NullPointerException
產(chǎn)生的原因:對(duì)象沒有創(chuàng)建就訪問了元素或者方法或者屬性
解決的辦法: 先找出出現(xiàn)的所有引用類型,判斷哪個(gè)對(duì)象是沒有new的元素或者方法或者屬性,如
果沒有就創(chuàng)建該對(duì)象
沒有這樣的元素異常 : Java.util.NoSuchElementException
產(chǎn)生的原因:在迭代器迭代的時(shí)候沒有下一個(gè)元素了
解決的辦法:在迭代器之前做相對(duì)應(yīng)得判斷,如果沒有元素了就不迭代輸出了
并發(fā)修改異常 : Java.util.ConcurrentModificationException
產(chǎn)生的原因:在迭代器迭代的同時(shí)使用集合修改元素
解決的辦法:使用普通for循環(huán)來遍歷 , 使用toArray來遍歷 , 使用ListIterator來遍歷
類型轉(zhuǎn)換異常 : Java.lang.ClassCastException
產(chǎn)生的原因:在向下轉(zhuǎn)型的過程中,沒有轉(zhuǎn)換成真實(shí)的類型
解決的方法:在向下轉(zhuǎn)型之前使用instanceof關(guān)鍵字對(duì)所有子類做逐一判斷
算法出錯(cuò)異常 : Java.lang.ArithmeticException
產(chǎn)生的原因:除數(shù)不能為零
解決的辦法:改變除數(shù)的結(jié)果再進(jìn)行測(cè)試
沒有序列化異常 : Java.io.NotSerialzableException
產(chǎn)生的原因:沒有實(shí)現(xiàn)serializable接口
解決的辦法:對(duì)需要的寫入到文件的類實(shí)現(xiàn)serializable接口,表示允許該類的該類寫入到文件 - 重寫和重載區(qū)別
重寫: 子類繼承父類, 在子類中存在和父類中一模一樣的方法, 從新編寫方法中的實(shí)現(xiàn)業(yè)務(wù)代碼.
重載: 在同一個(gè)類中存在多個(gè)方法名相同, 傳參個(gè)數(shù),順序和類型不同, 返回值可以相同也可以不同的方法. - 什么是反射, 反射的好處?
Java 反射,就是在運(yùn)行狀態(tài)中:
獲取任意類的名稱、package信息、所有屬性、方法、注解、類型、類加載器等
獲取任意對(duì)象的屬性,并且能改變對(duì)象的屬性
調(diào)用任意對(duì)象的方法
判斷任意一個(gè)對(duì)象所屬的類
實(shí)例化任意一個(gè)類的對(duì)象
Java 的動(dòng)態(tài)就體現(xiàn)在這。通過反射我們可以實(shí)現(xiàn)動(dòng)態(tài)裝配,降低代碼的耦合度;動(dòng)態(tài)代理等。反射的過
度使用會(huì)嚴(yán)重消耗系統(tǒng)資源。 - 事務(wù)控制在哪一層? 可否控制在dao層? 為什么?
事務(wù)必須控制在service層, 不可以在dao層.
因?yàn)閐ao層需要按照單一職責(zé)原則設(shè)計(jì), 一個(gè)類對(duì)應(yīng)一張表, 類中最好都是單表增刪改查, 增加代碼復(fù)
用率而事務(wù)具有隔離性, service會(huì)調(diào)用多個(gè)dao方法組裝業(yè)務(wù), 如果事務(wù)控制在dao層就會(huì)被分隔成
多個(gè)事務(wù), 無法整體控制
所以事務(wù)必須控制在service層, 保證一套業(yè)務(wù)操作要么全成功, 要么全失敗. - Cookie和session的區(qū)別和聯(lián)系?
Cookie是客戶端瀏覽器用來保存數(shù)據(jù)的一塊空間, cookie沒有session安全, cookie中保存的數(shù)據(jù)量
有大小限制
Session是服務(wù)器端用來保存數(shù)據(jù)的一塊空間, session比較安全
Cookie和session聯(lián)系:
當(dāng)用戶第一次發(fā)送請(qǐng)求訪問網(wǎng)站的時(shí)候, cookie中沒有信息, 這個(gè)時(shí)候服務(wù)器就會(huì)生成一個(gè)session
對(duì)象, 這個(gè)session對(duì)象有個(gè)唯一id叫做sessionID, 這個(gè)id會(huì)被保存到用戶的瀏覽器cookie中.
當(dāng)用戶再次訪問服務(wù)器的時(shí)候, cookie中就會(huì)有sessionID, 會(huì)被帶到服務(wù)器, 服務(wù)器會(huì)根據(jù)這個(gè)id找
到對(duì)應(yīng)的session對(duì)象使用.
如果服務(wù)器沒有找到對(duì)應(yīng)的session對(duì)象則會(huì)新生成一個(gè)session對(duì)象, 然后將新的session對(duì)象的id
再次寫入到cookie中保存. - —個(gè)線程兩次調(diào)用start。方法會(huì)出現(xiàn)什么情況?
Java的線程是不允許啟動(dòng)兩次的,第二次調(diào)用必然會(huì)拋出legalThreadStateException,這是一種運(yùn)行時(shí)
異常,多次調(diào)用 start 被認(rèn)為是編程錯(cuò)誤。 - 談?wù)刦inal、finally、 finalize有什么不同!
final 可以用來修飾類、方法、變量,分別有不同的意義,final 修飾的 class 代表不可以繼承擴(kuò)展,final
的變量是不可以修改的,而 final 的方法也是不可以重寫的(override)。
finally 則是 Java 保證重點(diǎn)代碼一定要被執(zhí)行的一種機(jī)制。我們可以使用 try-finally 或者 trycatch-finally
來進(jìn)行類似關(guān)閉 JDBC 連接、保證 unlock 鎖等動(dòng)作。
finalize 是基礎(chǔ)類 java.lang.Object 的一個(gè)方法,它的設(shè)計(jì)目的是保證對(duì)象在被垃圾收集前完成特定資源
的回收。finalize 機(jī)制現(xiàn)在已經(jīng)不推薦使用,并且在 JDK 9 開始被標(biāo)記為deprecated。 - 強(qiáng)引用、軟引用、弱引用、幻象引用有什么區(qū)別!
所謂強(qiáng)引用(“Strong” Reference) :
就是我們最常見的普通對(duì)象引用,只要還有強(qiáng)引用指向一個(gè)對(duì)象,就能表明對(duì)象還“活著”,垃圾收
集器不會(huì)碰這種對(duì)象。對(duì)于一個(gè)普通的對(duì)象,如果沒有其他的引用關(guān)系,只要超過了引用的作用域
或者顯式地將相(強(qiáng))引用賦值為 null,就是可以被垃圾收集的了,當(dāng)然具體回收時(shí)機(jī)還是要看垃
圾收集策略。
軟引用(SoftReference) :
是一種相對(duì)強(qiáng)引用弱化一些的引用,可以讓對(duì)象豁免一些垃圾收集,只有當(dāng) JVM 認(rèn)為內(nèi)存不足時(shí),
才會(huì)去試圖回收軟引用指向的對(duì)象。JVM 會(huì)確保在拋出OutOfMemoryError 之前,清理軟引用指
向的對(duì)象。軟引用通常用來實(shí)現(xiàn)內(nèi)存敏感的緩存,如果還有空閑內(nèi)存,就可以暫時(shí)保留緩存,當(dāng)內(nèi)
存不足時(shí)清理掉,這樣就保證了使用緩存的同時(shí),不會(huì)耗盡內(nèi)存。
弱引用(WeakReference):
并不能使對(duì)象豁免垃圾收集,僅僅是提供一種訪問在弱引用狀態(tài)下對(duì)象的途徑。這就可以用來構(gòu)建
一種沒有特定約束的關(guān)系,比如,維護(hù)一種非強(qiáng)制性的映射關(guān)系,如果試圖獲取時(shí)對(duì)象還在,就使
用它,否則重現(xiàn)實(shí)例化。它同樣是很多緩存實(shí)現(xiàn)的選擇。
幻象引用 :
有時(shí)候也翻譯成虛引用,你不能通過它訪問對(duì)象?;孟笠脙H僅是提供了一種確保對(duì)象被 finalize
以后,做某些事情的機(jī)制,比如,通常用來做所謂的 Post-Mortem 清理機(jī)制,我在專欄上一講中
介紹的 Java 平臺(tái)自身 Cleaner 機(jī)制等,也有人利用幻象引用監(jiān)控對(duì)象的創(chuàng)建和銷毀。 - 父子類靜態(tài)代碼塊, 非靜態(tài)代碼塊, 構(gòu)造方法執(zhí)行順序
父類 - 靜態(tài)代碼塊
子類 - 靜態(tài)代碼塊
父類 - 非靜態(tài)代碼
父類 - 構(gòu)造函數(shù)
子類 - 非靜態(tài)代碼
子類 - 構(gòu)造函數(shù) - 如何實(shí)現(xiàn)對(duì)象克隆
實(shí)現(xiàn) Cloneable 接口并重寫 Object 類中的 clone()方法;
實(shí)現(xiàn) Serializable 接口,通過對(duì)象的序列化和反序列化實(shí)現(xiàn)克隆,可以實(shí)現(xiàn)真正的深度克隆 - 什么是 java 序列化,如何實(shí)現(xiàn) java 序列化?
序列化就是一種用來處理對(duì)象流的機(jī)制,所謂對(duì)象流也就是將對(duì)象的內(nèi)容進(jìn)行流化??梢詫?duì)流化后的對(duì)
象進(jìn)行讀
寫操作,也可將流化后的對(duì)象傳輸于網(wǎng)絡(luò)之間。序列化是為了解決在對(duì)對(duì)象流進(jìn)行讀寫操作時(shí)所引發(fā)的
問題。
序 列 化 的 實(shí) 現(xiàn) : 將 需 要 被 序 列 化 的 類 實(shí) 現(xiàn) Serializable 接 口 - 深拷貝和淺拷貝區(qū)別是什么?
淺拷貝只是復(fù)制了對(duì)象的引用地址,兩個(gè)對(duì)象指向同一個(gè)內(nèi)存地址,所以修改其中任意的值,另一個(gè)值
都會(huì)隨之變化,這就是淺拷貝
深拷貝是將對(duì)象及值復(fù)制過來,兩個(gè)對(duì)象修改其中任意的值另一個(gè)值不會(huì)改變,這就是深拷貝 - jsp 和 servlet 有什么區(qū)別?
jsp經(jīng)編譯后就變成了Servlet.(JSP的本質(zhì)就是Servlet,JVM只能識(shí)別Java的類,不能識(shí)別JSP的代碼,
Web容器將JSP的代碼編譯成JVM能夠識(shí)別的Java類)
jsp更擅長(zhǎng)表現(xiàn)于頁(yè)面顯示,servlet更擅長(zhǎng)于邏輯控制。 - 說一下 jsp 的 4 種作用域?
JSP中的四種作用域包括page、request、session和application:
page : 代表與一個(gè)頁(yè)面相關(guān)的對(duì)象和屬性。
request : 代表與Web客戶機(jī)發(fā)出的一個(gè)請(qǐng)求相關(guān)的對(duì)象和屬性。一個(gè)請(qǐng)求可能跨越多個(gè)頁(yè)面,涉
及多個(gè)Web組件;需要在頁(yè)面顯示的臨時(shí)數(shù)據(jù)可以置于此作用域。
session : 代表與某個(gè)用戶與服務(wù)器建立的一次會(huì)話相關(guān)的對(duì)象和屬性。跟某個(gè)用戶相關(guān)的數(shù)據(jù)應(yīng)該
放在用戶自己的session中。
application : 代表與整個(gè)Web應(yīng)用程序相關(guān)的對(duì)象和屬性,它實(shí)質(zhì)上是跨越整個(gè)Web應(yīng)用程序,包
括多個(gè)頁(yè)面、請(qǐng)求和會(huì)話的一個(gè)全局作用域。 - 請(qǐng)求轉(zhuǎn)發(fā)(forward)和重定向(redirect)的區(qū)別?
請(qǐng)求轉(zhuǎn)發(fā)forward比重定向redirect快
請(qǐng)求轉(zhuǎn)發(fā)的url地址只能是本網(wǎng)站內(nèi)部地址, 重定向的url地址可以是外部網(wǎng)站地址
請(qǐng)求轉(zhuǎn)發(fā)瀏覽器url不發(fā)生變化, 重定向?yàn)g覽器url地址發(fā)生改變.
請(qǐng)求轉(zhuǎn)發(fā)request域中的數(shù)據(jù)可以帶到轉(zhuǎn)發(fā)后的方法中, 重定向request域中的數(shù)據(jù)無法帶到重定向后的
方法中.
二、JVM虛擬機(jī)面試題【14道】
- JDK1.7、1.8的JVM內(nèi)存模型,哪些區(qū)域可能發(fā)生OOM(out of memory)內(nèi)存溢出!
Out of memory: 堆內(nèi)存溢出
JDK 1.7:有永久代,但已經(jīng)把字符串常量池、靜態(tài)變量,存放在堆上。逐漸的減少永久代的使用。
JDK 1.8:無永久代,運(yùn)行時(shí)常量池、類常量池,都保存在元空間。但字符串常量池仍然存放在堆上。
JVM 內(nèi)存區(qū)域劃分圖 :
內(nèi)存溢出通俗的講就是內(nèi)存不夠用了,并且 GC 通過垃圾回收也無法提供更多的內(nèi)存。實(shí)際上除了程序
計(jì)數(shù)器,其他區(qū)域都有可能發(fā)生 OOM, 簡(jiǎn)單總結(jié)如下:
【1】堆內(nèi)存不足是最常見的 OOM 原因之一,拋出錯(cuò)誤信息
java.lang.OutOfMemoryError:Java heap space
原因也不盡相同,可能是內(nèi)存泄漏,也有可能是堆的大小設(shè)置不合理。
【2】對(duì)于虛擬機(jī)棧和本地方法棧,導(dǎo)致 OOM 一般為對(duì)方法自身不斷的遞歸調(diào)用,且 沒有結(jié)束點(diǎn),導(dǎo)
致不斷的壓棧操作。類似這種情況,JVM 實(shí)際會(huì)拋出 StackOverFlowError , 但是如果 JVM 試圖去拓展棧
空間的時(shí)候,就會(huì)拋出 OOM.
【3】對(duì)于老版的 JDK, 因?yàn)橛谰么笮∈怯邢薜?#xff0c;并且 JVM 對(duì)老年代的內(nèi)存回收非常不積極,所以當(dāng)我
們添加新的對(duì)象,老年代發(fā)生 OOM 的情況也非常常見。
【4】隨著元數(shù)據(jù)區(qū)的引入,方法區(qū)內(nèi)存已經(jīng)不再那么窘迫,所以相應(yīng)的 OOM 有所改 觀,出現(xiàn) OOM,
異常信息則變成了:“java.lang.OutOfMemoryError: Metaspace”。 - 請(qǐng)介紹類加載過程和雙親委派模型!
類加載過程 : 加載 -> 連接 -> 驗(yàn)證 -> 準(zhǔn)備 -> 解析 -> 初始化
雙親委派模型: 先找父類加載器, 讓父類加載器加載, 如果父類加載器無法加載就讓子類加載器加載.這個(gè)
加載過程叫做雙親委派模型或者雙親委派機(jī)制. - 談一談堆和棧的區(qū)別!
棧: 存儲(chǔ)局部變量, 基本類型數(shù)據(jù), 動(dòng)態(tài)鏈接(堆中對(duì)象的內(nèi)存地址)
堆: 凡是new出來的對(duì)象都在堆中, 也就是存儲(chǔ)數(shù)組和對(duì)象,垃圾回收器不定期到堆中收取垃圾. - 說一說jvm常見垃圾回收器和特點(diǎn)!
按照內(nèi)存空間來劃分,年輕代的垃圾回收器有:
Serial:是一個(gè)串行的垃圾回收器
ParNew:是Serial的并行版本
Parallel Scavenge:也是一個(gè)并行的垃圾回收器,區(qū)別在于它注重吞吐量
年輕代的三個(gè)垃圾回收器采用的垃圾回收算法都是復(fù)制算法,所以在進(jìn)行垃圾回收時(shí)都會(huì)暫停所有的用
戶線程;
然后是老年代的垃圾回收器:
Serial Old:是Serial的老年代版本,串行,采用的是標(biāo)記-整理算法,同樣會(huì)STW
Parallel Old:是Parallel Scavenge的老年代版本,并行,采用的是標(biāo)記-整理算法,會(huì)STW,注重
吞吐量
CMS:注重低延遲,采用的是標(biāo)記-清除算法,分為四個(gè)階段:初始標(biāo)記、并發(fā)標(biāo)記、重新標(biāo)記、
并發(fā)清除;在初始化和重新標(biāo)記階段中是并行的,會(huì)STW,其余兩個(gè)階段都是并發(fā)執(zhí)行與用戶線程
同時(shí)執(zhí)行;由于采用標(biāo)記清理算法,會(huì)產(chǎn)生空間碎片
最后是整堆收集器:G1收集器,G1收集器的特點(diǎn)有:能夠獨(dú)立管理整個(gè)堆空間、可利用多CPU、多核的
硬件優(yōu)勢(shì)縮短STW的時(shí)間、采用分代收集算法不會(huì)產(chǎn)生空間碎片 - Java創(chuàng)建對(duì)象的過程!
對(duì)象的創(chuàng)建過程一般是從new指令開始的,JVM首先會(huì)對(duì)符號(hào)引用進(jìn)行解析,解析完畢后JVM會(huì)為對(duì)象在
堆中分配內(nèi)存,之后,JVM會(huì)將該內(nèi)存進(jìn)行零值初始化。最后,JVM會(huì)調(diào)用對(duì)象的構(gòu)造函數(shù)。此時(shí),一般
會(huì)有一個(gè)引用指向這個(gè)對(duì)象,將對(duì)象的地址值賦值給變量。在Java對(duì)象初始化過程中,主要涉及三種執(zhí)
行對(duì)象初始化的結(jié)構(gòu),分別是 實(shí)例變量初始化、實(shí)例代碼塊初始化 以及 構(gòu)造函數(shù)初始化。 - Java中垃圾回收機(jī)制
垃圾回收機(jī)制用到finalize。當(dāng)程序創(chuàng)建對(duì)象、數(shù)組等引用類型實(shí)體時(shí),系統(tǒng)都會(huì)在堆內(nèi)存中為之分
配一塊內(nèi)存區(qū),對(duì)象就保存在這塊內(nèi)存中,當(dāng)這塊內(nèi)存不再被任何引用變量引用時(shí),這塊內(nèi)存就會(huì)
變成垃圾,等待垃圾回收機(jī)制進(jìn)行回收。
分析對(duì)象是否為垃圾的方法 : 可達(dá)性分析法, 引用計(jì)數(shù)法兩種
強(qiáng)制垃圾回收 : 當(dāng)一個(gè)對(duì)象失去引用后,系統(tǒng)何時(shí)調(diào)用它的finalize ()方法對(duì)它進(jìn)行資源清理,何時(shí)
它會(huì)變成不可達(dá)狀態(tài),系統(tǒng)何時(shí)回收它所占有的內(nèi)存。對(duì)于系統(tǒng)程序完全透明。程序只能控制一個(gè)
對(duì)象任何不再被任何引用變量引用,絕不能控制它何時(shí)被回收。強(qiáng)制只是建議系統(tǒng)立即進(jìn)行垃圾回
收,系統(tǒng)完全有可能并不立即進(jìn)行垃圾回收,垃圾回收機(jī)制也不會(huì)對(duì)程序的建議置之不理:垃圾回收
機(jī)制會(huì)在收到通知后,盡快進(jìn)行垃圾回收。
強(qiáng)制垃圾回收的兩個(gè)方法 : 調(diào)用System類的gc()靜態(tài)方法System.gc(), 調(diào)用Runtime對(duì)象的gc()實(shí)例
方法:Runtime.getRuntime().gc()方法
垃圾回收基本算法有四種 : 引用計(jì)數(shù)法, 標(biāo)記清除法, 標(biāo)記壓縮法, 復(fù)制算法
垃圾回收復(fù)合算法 – 分代收集算法:
當(dāng)前虛擬機(jī)的垃圾收集都采用分代收集算法,這種算法就是根據(jù)具體的情況選擇具體的垃圾回收算
法。一般將java 堆分為新生代和老年代,這樣我們就可以根據(jù)各個(gè)年代的特點(diǎn)選擇合適的垃圾收集
算法。
比如在新生代中,每次收集都會(huì)有大量對(duì)象死去,所以可以選擇復(fù)制算法,只需要付出少量對(duì)象的
復(fù)制成本就可以完成每次垃圾收集。而老年代的對(duì)象存活幾率是比較高的,而且沒有額外的空間對(duì)
它進(jìn)行分配擔(dān)保,所以我們必須選擇“標(biāo)記-清除”或“標(biāo)記-壓縮”算法進(jìn)行垃圾收集。 - Java中垃圾回收算法
(1)標(biāo)記-清除算法:使用可達(dá)性分析算法標(biāo)記內(nèi)存中的垃圾對(duì)象,并直接清理,容易造成空間碎片
(2)標(biāo)記-壓縮算法:在標(biāo)記-清除算法的基礎(chǔ)上,將存活的對(duì)象整理在一起保證空間的連續(xù)性
(3)復(fù)制算法:將內(nèi)存空間劃分成等大的兩塊區(qū)域,from和to只用其中的一塊區(qū)域,收集垃圾時(shí)將存
活的對(duì)象復(fù)制到另一塊區(qū)域中,并清空使用的區(qū)域;解決了空間碎片的問題,但是空間的利用率低
(4)復(fù)合算法 - 分代收集算法:是對(duì)前三種算法的整合,將內(nèi)存空間分為老年代和新生代,新生代中存
儲(chǔ)一些生命周期短的對(duì)象,使用復(fù)制算法;而老年代中對(duì)象的生命周期長(zhǎng),則使用標(biāo)記-清除或標(biāo)記-整
理算法。 - YoungGC和FullGC觸發(fā)時(shí)機(jī)?
Young GC :Young GC其實(shí)一般就是在新生代的Eden區(qū)域滿了之后就會(huì)觸發(fā),采用復(fù)制算法來回
收新生代的垃圾。
Full GC : 調(diào)用System.gc()時(shí)。系統(tǒng)建議執(zhí)行Full GC,但是不必然執(zhí)行
觸發(fā)時(shí)機(jī) :
老年代空間不足
方法區(qū)空間不足
進(jìn)入老年代的平均大小大于老年代的可用內(nèi)存
由Eden區(qū),幸存者0區(qū)向幸存者1區(qū)復(fù)制時(shí),對(duì)象大小大于1區(qū)可用內(nèi)存,則把該對(duì)象轉(zhuǎn)存到
老年代,且老年代的可用內(nèi)存大小小于該對(duì)象大小。
注意:full GC是開發(fā)或調(diào)優(yōu)中盡量要避免的,這樣STW會(huì)短一些 - FullGC觸發(fā)可能產(chǎn)生什么問題
fullgc的時(shí)候除gc線程外的所有用戶線程處于暫停狀態(tài),也就是不會(huì)有響應(yīng)了。一般fullgc速度很快,毫秒級(jí)
的,用戶無感知。除非內(nèi)存特別大上百G的,或者fullgc也無法收集到足夠內(nèi)存導(dǎo)致一直fullgc,應(yīng)用的外在表
現(xiàn)就是程序卡死了. - jvm調(diào)優(yōu)工具和性能調(diào)優(yōu)思路?
Jvm中調(diào)優(yōu)使用的分析命令如下, 先試用下面命令分析jvm中的問題 :
Jps : 用于查詢正在運(yùn)行的JVM進(jìn)程。 直接獲取進(jìn)程的pid
Jstat : 可以實(shí)時(shí)顯示本地或遠(yuǎn)程JVM進(jìn)程中裝載,內(nèi)存,垃圾信息,JIT編譯等數(shù)據(jù)
Jinfo : 用于查詢當(dāng)前運(yùn)行著的JVM屬性和參數(shù)的值
Jmap : 用于顯示當(dāng)前java堆和永生代的詳細(xì)信息
Jhat : 用于分析使用jmap生成的dump文件,是JDK自帶的工具
Jstack: 用于生成當(dāng)JVM所有線程快照,線程快照是虛擬機(jī)每一條線程正在執(zhí)行的方法,目的是定位
線程出現(xiàn)長(zhǎng)時(shí)間停頓的原因。
下面是JVM常見的調(diào)優(yōu)參數(shù), 經(jīng)過上面命令分析定位問題后使用下面參數(shù)優(yōu)化:
-Xmx 指定java程序的最大堆內(nèi)存
-Xms 指定最小堆內(nèi)存
-Xmn 設(shè)置年輕代大小 , 整個(gè)堆大小=年輕代大小+年老代大小。所以增大年輕代后,會(huì)減小年老代
大小。此值對(duì)系統(tǒng)影響較大,Sun官方推薦為整個(gè)堆的3/8
-Xss 指定線程的最大??臻g,此參數(shù)決定了java函數(shù)調(diào)用的深度,值越大說明調(diào)用深度越深,若值
太小則容易棧溢出錯(cuò)誤(StackOverflowError)
-XX: PermSize 指定方法區(qū)(永久區(qū))的初始值默認(rèn)是物理內(nèi)存的1/64。
-XX:MetaspaceSize指定元數(shù)據(jù)區(qū)大小, 在Java8中,永久區(qū)被移除,代之的是元數(shù)據(jù)區(qū)
-XX:NewRatio=n 年老代與年輕代的比值,-XX:NewRatio=2,表示年老代和年輕代的比值為2:1
-XX:SurvivorRatio=n Eden區(qū)與Survivor區(qū)的大小比值,-XX:SurvivorRatio=8表示Eden區(qū)與
Survivor區(qū)的大小比值是8:1:1,因?yàn)镾urvivor區(qū)有兩個(gè)(from,to); - Jvm內(nèi)存模型?
注意 : 大多數(shù)面試官混淆了內(nèi)存模型和內(nèi)存結(jié)構(gòu)的區(qū)別, 如果面試官這么問, 可以咨詢下面試官是否問的
是jvm內(nèi)存的結(jié)構(gòu)都包含哪些東西, 還是問JMM(Java memory model)內(nèi)存模型原理
Jvm內(nèi)存結(jié)構(gòu)由9塊組成:
類加載器, 垃圾回收器, 執(zhí)行引擎, 方法區(qū), 堆, 棧, 直接內(nèi)存, 本地方法棧, 程序計(jì)數(shù)器(pc寄存器)
JMM(java memory model)java內(nèi)存模型:
JMM規(guī)定了所有變量(除了方法參數(shù)和本地變量,包括實(shí)例變量和靜態(tài)變量)都放在主內(nèi)存中。每
個(gè)線程都有自己的工作內(nèi)存,工作內(nèi)存保存了該線程使用的主內(nèi)存的變量副本,所有的操作都在工
作內(nèi)存中進(jìn)行,線程不能直接操作主內(nèi)存。線程之間通過將數(shù)據(jù)刷回主內(nèi)存的方式進(jìn)行通信。
JMM定義了原子性,可見性和有序性。
原子性:一個(gè)操作不可分割,不可中斷,不可被其他線程干擾。JMM提供moniterenter和
monitereixt倆個(gè)字節(jié)碼指令保證代碼塊的原子性
可見性:當(dāng)一個(gè)變量被修改后,其他線程能夠立即看到修改的結(jié)果
有序性:禁止指令重排序 - GC如何識(shí)別垃圾?
引用計(jì)數(shù)算法(已被淘汰的算法)
給對(duì)象中添加一個(gè)引用計(jì)數(shù)器,每當(dāng)有一個(gè)地方引用它時(shí),計(jì)數(shù)器值就加1;當(dāng)引用失效時(shí),計(jì)數(shù)器值就
減1;任何時(shí)刻計(jì)數(shù)器為0的對(duì)象就是不可能再被使用的。
可達(dá)性分析算法
目前主流的編程語(yǔ)言(java,C#等)的主流實(shí)現(xiàn)中,都是稱通過可達(dá)性分析(Reachability Analysis)來判
定對(duì)象是否存活的。這個(gè)算法的基本思路就是通過一系列的稱為“GC Roots”的對(duì)象作為起始點(diǎn),從這
些節(jié)點(diǎn)開始向下搜索,搜索所走過的路徑稱為引用鏈(Reference Chain),當(dāng)一個(gè)對(duì)象到GC Roots沒有
任何引用鏈相連, 就是從GC Roots到這個(gè)對(duì)象不可達(dá)時(shí),則證明此對(duì)象是不可用的。
分配流程 - 詳細(xì)介紹一下 CMS 垃圾回收器?
CMS 是英文 Concurrent Mark-Sweep 的簡(jiǎn)稱,是以犧牲吞吐量為代價(jià)來獲得最短回收停頓時(shí)間的垃圾
回收器。對(duì)于要求服務(wù)器響應(yīng)速度的應(yīng)用上,這種垃圾回收器非常適合。
在啟動(dòng) JVM 的參數(shù)加上“-XX:+UseConcMarkSweepGC”來指定使用 CMS 垃圾回收器。
CMS 使用的是標(biāo)記-清除的算法實(shí)現(xiàn)的,所以在 gc 的時(shí)候會(huì)產(chǎn)生大量的內(nèi)存碎片,當(dāng)剩余內(nèi)存不能滿足
程序運(yùn)行要求時(shí),系統(tǒng)將會(huì)出現(xiàn) Concurrent Mode Failure,臨時(shí) CMS 會(huì)采用 Serial Old 回收器進(jìn)行垃
圾清除,此時(shí)的性能將會(huì)被降低。
CMS 工作機(jī)制相比其他的垃圾收集器來說更復(fù)雜,整個(gè)過程分為以下 4 個(gè)階段:
(1)初始標(biāo)記
只是標(biāo)記一下 GC Roots 能直接關(guān)聯(lián)的對(duì)象,速度很快,仍然需要暫停所有的工作線程。
(2)并發(fā)標(biāo)記
進(jìn)行 GC Roots 跟蹤的過程,和用戶線程一起工作,不需要暫停工作線程。
(3)重新標(biāo)記
為了修正在并發(fā)標(biāo)記期間,因用戶程序繼續(xù)運(yùn)行而導(dǎo)致標(biāo)記產(chǎn)生變動(dòng)的那一部分對(duì)象的標(biāo)記記錄,
仍然需要暫停所有的工作線程。
(4)并發(fā)清除
清除 GC Roots 不可達(dá)對(duì)象,和用戶線程一起工作,不需要暫停工作線程。由于耗時(shí)最長(zhǎng)的并發(fā)標(biāo)
記和并發(fā)清除過程中,垃圾收集線程可以和用戶現(xiàn)在一起并發(fā)工作, 所以總體上來看CMS 收集器的內(nèi)存
回收和用戶線程是一起并發(fā)地執(zhí)行。 - Java創(chuàng)建對(duì)象的內(nèi)存分配流程?
三、集合相關(guān)面試題【17道】
- 聊一聊Java中容器體系結(jié)構(gòu)!
Collection:集合接口
List:是Collection的子接口,用來存儲(chǔ)有序的數(shù)據(jù)集合
ArrayList:是List集合的實(shí)現(xiàn)類,底層采用動(dòng)態(tài)數(shù)組進(jìn)行存儲(chǔ)數(shù)據(jù),默認(rèn)是空數(shù)組,如果存值則
擴(kuò)容至10,如果不夠則以1.5倍進(jìn)行擴(kuò)容。
LinkedList:是List集合的實(shí)現(xiàn)類,底層采用雙向鏈表結(jié)構(gòu)進(jìn)行存儲(chǔ)數(shù)據(jù),增刪數(shù)據(jù)比較方便,
速度快。
Vector :Vector類實(shí)現(xiàn)了可擴(kuò)展的對(duì)象數(shù)組, 像數(shù)組一樣,它包含可以使用整數(shù)索引訪問的組
件。但是,Vector的大小可以根據(jù)需要增長(zhǎng)或縮小,以適應(yīng)在創(chuàng)建Vector之后添加和刪除。
【注】Vector是同步的(線程安全)。 如果不需要線程安全的實(shí)現(xiàn),建議使用ArrayList代替
Vector.
Set: 是Collection的子接口,用來存儲(chǔ)無序的數(shù)據(jù)集合
HashSet:底層采用哈希表(HashMap)的方式存儲(chǔ)數(shù)據(jù),數(shù)據(jù)無序且唯一
TreeSet:采用有序二叉樹進(jìn)行存儲(chǔ)數(shù)據(jù),遵循了自然排序。
Map: 與Collection并列,用來存儲(chǔ)具有映射關(guān)系(key-value)的數(shù)據(jù)
HashMap:哈希表結(jié)構(gòu)存儲(chǔ)數(shù)據(jù),key值可以為null
TreeMap:紅黑樹算法的實(shí)現(xiàn)
HashTable:哈希表實(shí)現(xiàn),key值不可以為null
Properties:HashTable的子類,鍵值對(duì)存儲(chǔ)數(shù)據(jù)均為String類型,主要用來操作以.properties
結(jié)尾的配置文件。
【特點(diǎn)】存儲(chǔ)數(shù)據(jù)是以key,value對(duì)進(jìn)行存儲(chǔ),其中key值是以Set集合形式存儲(chǔ),唯一且不可重復(fù)。
value是以Collection形式存儲(chǔ),可以重復(fù)。 - List、Set和Map的區(qū)別,以及各自的優(yōu)缺點(diǎn)
List :
可以允許重復(fù)的對(duì)象。可以插入多個(gè)null元素。是一個(gè)有序容器,保持了每個(gè)元素的插入順
序,輸出的順序就是插入的順序。
常用的實(shí)現(xiàn)類有 ArrayList、LinkedList 和 Vector。ArrayList 最為流行,它提供了使用索引的
隨意訪問,而 LinkedList 則對(duì)于經(jīng)常需要從 List 中添加或刪除元素的場(chǎng)合更為合適。
Set :
不允許重復(fù)對(duì)象。無序容器,你無法保證每個(gè)元素的存儲(chǔ)順序,TreeSet通過 Comparator 或
者 Comparable 維護(hù)了一個(gè)排序順序。只允許一個(gè) null 元素。
Set 接口最流行的幾個(gè)實(shí)現(xiàn)類是 HashSet、LinkedHashSet 以及 TreeSet。最流行的是基于
HashMap 實(shí)現(xiàn)的 HashSet;TreeSet 還實(shí)現(xiàn)了 SortedSet 接口,因此 TreeSet 是一個(gè)根據(jù)其
compare() 和 compareTo() 的定義進(jìn)行排序的有序容器。而且可以重復(fù)。
Map :
Map不是collection的子接口或者實(shí)現(xiàn)類。Map是一個(gè)接口。
Map 的 每個(gè) Entry 都持有兩個(gè)對(duì)象,也就是一個(gè)鍵一個(gè)值,Map 可能會(huì)持有相同的值對(duì)象但
鍵對(duì)象必須是唯一的。
TreeMap 也通過 Comparator 或者 Comparable 維護(hù)了一個(gè)排序順序。
Map 里你可以擁有隨意個(gè) null 值但最多只能有一個(gè) null 鍵。
Map 接口最流行的幾個(gè)實(shí)現(xiàn)類是 HashMap、LinkedHashMap、Hashtable 和 TreeMap。
(HashMap、TreeMap最常用) - ArrayList,LinkedList,Vector的區(qū)別!
ArrayList和Vector是基于數(shù)組實(shí)現(xiàn)的。 ArrayList線程不安全,速度快, Vector線程安全,速度慢, 因?yàn)?br /> 底層數(shù)組實(shí)現(xiàn)所以查詢快, 修改快, 添加和刪除慢,
LinkedList是基于雙向鏈表實(shí)現(xiàn)的, 線程不安全, 添加, 刪除快, 查詢和修改慢.
ArrayList和Vector都是使用Object的數(shù)組形式來存儲(chǔ)的,當(dāng)向這兩種類型中增加元素的時(shí)候,若容
量不夠,需要進(jìn)行擴(kuò)容。ArrayList擴(kuò)容后的容量是之前的1.5倍,然后把之前的數(shù)據(jù)拷貝到新建的
數(shù)組中去。而Vector默認(rèn)情況下擴(kuò)容后的容量是之前的2倍。
Vector可以設(shè)置容量增量,而ArrayList不可以。 - TreeSet如何保證對(duì)象的有序性!
TreeSet是有序不可重復(fù)集,具有以下特點(diǎn):
數(shù)據(jù)會(huì)按自然排序(可以理解為從小到大排序)
不可存儲(chǔ)null
數(shù)據(jù)不可重復(fù)
非線程安全
TreeSet底層依賴于TreeMap,TreeMap的數(shù)據(jù)結(jié)構(gòu)是二叉樹(紅黑樹),由底層數(shù)據(jù)結(jié)構(gòu)決定了
TreeSet中元素有序且唯一。
我們使用TreeSet時(shí),如果添加對(duì)象的話,我們就必須去實(shí)現(xiàn)Comparable接口,當(dāng)然也可以實(shí)現(xiàn)
Comparator接口來保證對(duì)象的有序性。 - HashTable、HashMap、TreeMap的區(qū)別!
Hashtable、HashMap、TreeMap都實(shí)現(xiàn)了Map接口,使用鍵值對(duì)的形式存儲(chǔ)數(shù)據(jù)和操作數(shù)據(jù)。
Hashtable是java早期提供的,方法是同步的(加了synchronized)。key和value都不能是null
值。
HashMap的方法不是同步的,支持key和value為null的情況。行為上基本和Hashtable一致。由于
Hashtable是同步的,性能開銷比較大,一般不推薦使用Hashtable。通常會(huì)選擇使用HashMap。
HashMap進(jìn)行put和get操作,基本上可以達(dá)到常數(shù)時(shí)間的性能
TreeMap是基于紅黑樹的一種提供順序訪問的Map,和HashMap不同,它的get或put操作的時(shí)間
復(fù)雜度是O(log(n))。具體的順序由指定的Comparator來決定,或者根據(jù)鍵key的具體順序來決定。
關(guān)系圖: - HashMap的底層數(shù)據(jù)存儲(chǔ)結(jié)構(gòu)
JDK1.7采用 : 數(shù)組 + 鏈表結(jié)構(gòu)
JDK1.8采用 : 數(shù)組 + 鏈表 + 紅黑樹
紅黑樹:jdk1.8最重要的就是引入了紅黑樹的設(shè)計(jì),當(dāng)hash表的單一鏈表長(zhǎng)度超過 8 個(gè)的時(shí)候,鏈表結(jié)
構(gòu)就會(huì)轉(zhuǎn)為紅黑樹結(jié)構(gòu)。
HashMap的底層數(shù)據(jù)存儲(chǔ)結(jié)構(gòu)為哈希表加紅黑樹。實(shí)現(xiàn)過程如下:
調(diào)用HashMap的無參構(gòu)造方法,加載因子loadFactor賦值0.75,table數(shù)組是空。
當(dāng)添加第一個(gè)元素時(shí),創(chuàng)建長(zhǎng)度16的數(shù)組,threshold=12。
當(dāng)‘鏈表長(zhǎng)度大于等于8時(shí),并且數(shù)組長(zhǎng)度大于等于64時(shí),鏈表調(diào)整紅黑樹
當(dāng)紅黑樹的節(jié)點(diǎn)個(gè)數(shù)小于6時(shí),調(diào)整為鏈表
當(dāng)HashMap的容量超出閾值時(shí),擴(kuò)容為原來大小的2倍,減少元素的移動(dòng),提高效率。 - HashMap的擴(kuò)容機(jī)制
為什么會(huì)需要擴(kuò)容:
當(dāng)它在初始化時(shí)會(huì)創(chuàng)建一個(gè)容量為16的數(shù)組,當(dāng)元素?cái)?shù)量超過閾值(當(dāng)前容量X加載因子(通
常為0.75)=擴(kuò)容閾值)時(shí),會(huì)將數(shù)組大小擴(kuò)展為當(dāng)前容量的兩倍。
但是HashMap的容量是有上限的。如果hashmap的數(shù)組當(dāng)前容量達(dá)到了1073741824,則該
數(shù)組不會(huì)再增長(zhǎng)。且閾值將被設(shè)置為Integer.MAX_VALUE(2^31-1)即永遠(yuǎn)不會(huì)超出閾值。
Hashmap的擴(kuò)容機(jī)制在JDK8中,容量變化通常有以下集中情況:
無參構(gòu)造:實(shí)例化的hashmap默認(rèn)內(nèi)部數(shù)組是null,也就是無實(shí)例化。第一次調(diào)用put方法
時(shí),會(huì)開始第一次初始化擴(kuò)容,長(zhǎng)度為16.
有參構(gòu)造:用于指定容量。會(huì)根據(jù)指定的正整數(shù)找到不小于指定容量的2的冪數(shù),將這個(gè)數(shù)設(shè)
置賦值給閾值。第一次調(diào)用put方法時(shí),會(huì)將閾值賦值給容量,然后讓 閾值=容量X負(fù)載因子。
因此并不是手動(dòng)指定了容量就一定不會(huì)觸發(fā)擴(kuò)容,超過閾值后一樣擴(kuò)容。
如果不是第一次擴(kuò)容,則容量變?yōu)樵瓉淼?倍,閾值也變?yōu)樵瓉淼?倍。但負(fù)載因子不變。
補(bǔ)充:
首次put時(shí)會(huì)以擴(kuò)容的方式初始化,然后存入數(shù)據(jù),之后判斷是否需要擴(kuò)容
如果不是首次put,則不咋需要初始化,會(huì)直接存入數(shù)據(jù),然后判斷是否需要擴(kuò)容。
元素遷移:
在JDK8中,由于數(shù)組的容量是以2的冪次方擴(kuò)容的,那么一個(gè)數(shù)組在擴(kuò)容時(shí),一個(gè)元素要么在
原位置要么在原長(zhǎng)度+原位置的位置。
數(shù)組長(zhǎng)度變?yōu)樵瓉淼?倍表現(xiàn)在二進(jìn)制上多了一個(gè)高位參與數(shù)組下標(biāo)確定。此時(shí),一個(gè)元素通
過hash轉(zhuǎn)換坐標(biāo)的方法計(jì)算后會(huì)出現(xiàn)一個(gè)現(xiàn)象:
最高位是0,則坐標(biāo)不變,最高位是1則坐標(biāo)變?yōu)?0000+原坐標(biāo),即原長(zhǎng)度+原坐標(biāo)。 - HashMap鏈表和紅黑樹轉(zhuǎn)化時(shí)機(jī)!
HashMap中維護(hù)有一個(gè)Node類型的數(shù)組table,當(dāng)新添加的元素的hash值(hashCode & (length - 1))所
指向的位置已有元素時(shí)就會(huì)被掛在該位置的最后面而形成鏈表。當(dāng)在插入一個(gè)元素后某一位置的鏈表長(zhǎng)
度大于等于樹化的閾值(TREEIFY_THRESHOLD = 8)時(shí),會(huì)先去檢查table的size,如果此時(shí)table數(shù)組
的長(zhǎng)度小于最小樹化容量(MIN_TREEIFY_CAPACITY = 64),會(huì)先對(duì)數(shù)組進(jìn)行擴(kuò)容,擴(kuò)容大小為原來的
二倍。如果此時(shí)數(shù)組的長(zhǎng)度已經(jīng)大于64,則會(huì)將鏈表轉(zhuǎn)化為紅黑樹。在擴(kuò)容時(shí),如果發(fā)現(xiàn)發(fā)現(xiàn)某處已經(jīng)
變?yōu)榧t黑樹的樹上的節(jié)點(diǎn)數(shù)量總和小于等于紅黑樹轉(zhuǎn)化為鏈表的閾值(UNTREEIFY_THRESHOLD =
6),則會(huì)將此處的紅黑樹轉(zhuǎn)化為鏈表。
簡(jiǎn)而言之,當(dāng)鏈表長(zhǎng)度超過8且哈希表容量大于64,會(huì)將鏈表轉(zhuǎn)化為紅黑樹。而當(dāng)紅黑樹的大小小于6,
則由樹轉(zhuǎn)化為鏈表。 - 什么是Hash碰撞以及常見的解決方案
Hash碰撞 : 對(duì)象Hash的前提是實(shí)現(xiàn)equals()和hashCode()兩個(gè)方法,那么HashCode()的作用就是保證
對(duì)象返回唯一hash值,但當(dāng)兩個(gè)不同對(duì)象計(jì)算值一樣時(shí),這就發(fā)生了碰撞沖突。
解決方案:
開放地址法(再散列法)開放地執(zhí)法有一個(gè)公式:Hi=(H(key)+di) MOD m i=1,2,…,k(k<=m-1)
其中,m為哈希表的表長(zhǎng)。di 是產(chǎn)生沖突的時(shí)候的增量序列。如果di值可能為1,2,3,…m-1,稱線性
探測(cè)再散列。
如果di取1,則每次沖突之后,向后移動(dòng)1個(gè)位置.如果di取值可能為1,-1,2,-2,4,-4,9,-9,16,-16,…kk,-
kk(k<=m/2),稱二次探測(cè)再散列。如果di取值可能為偽隨機(jī)數(shù)列。稱偽隨機(jī)探測(cè)再散列。
再哈希法Rehash當(dāng)發(fā)生沖突時(shí),使用第二個(gè)、第三個(gè)、哈希函數(shù)計(jì)算地址,直到無沖突時(shí)。
缺點(diǎn):計(jì)算時(shí)間增加。
比如上面第一次按照姓首字母進(jìn)行哈希,如果產(chǎn)生沖突可以按照姓字母首字母第二位進(jìn)行哈希,再
沖突,第三位,直到不沖突為止.這種方法不易產(chǎn)生聚集,但增加了計(jì)算時(shí)間。
鏈地址法(拉鏈法)將所有關(guān)鍵字為同義詞的記錄存儲(chǔ)在同一線性鏈表中.基本思想:將所有哈希地
址為i的元素構(gòu)成一個(gè)稱為同義詞
鏈的單鏈表,并將單鏈表的頭指針存在哈希表的第i個(gè)單元中,因而查找、插入和刪除主要在同義詞
鏈中進(jìn)行。鏈地址法適用于經(jīng)常進(jìn)行插入和刪除的情況。 - HashMap在多線程操作中會(huì)出現(xiàn)什么問題!
會(huì)出現(xiàn)線程安全問題, 可以使用加鎖解決, 但是速度慢, 所以建議使用juc包下的ConcurrentHashMap替代
HashMap, 速度快且線程安全. - 如何保證集合是線程安全的?
第一: 使用JUC包下的ConcurrentHashMap, 線程安全, 并且速度也快
第二: 選用synchronize或者Lock加鎖解決, 速度慢. - 聊一聊JUC包下的并發(fā)工具
CountDownLatch 閉鎖,使用時(shí)需要傳入一個(gè)int類型的參數(shù),一個(gè)線程等待一組線程執(zhí)行完畢后再恢復(fù)
執(zhí)行;await() 等待其他線程都執(zhí)行完畢,通過計(jì)數(shù)器來判斷等待的線程是否全部執(zhí)行完畢。計(jì)數(shù)器:
countDown方法,被等待線程執(zhí)行完畢后將計(jì)數(shù)器值-1,當(dāng)CountDownLatch的值減為0時(shí)無法恢復(fù),
這就是叫做閉鎖的原因。
CyclicBarrier 循環(huán)柵欄, 一組線程 同時(shí)到達(dá)臨界點(diǎn)后再同時(shí)恢復(fù)執(zhí)行(先到達(dá)臨界點(diǎn)的線程會(huì)阻塞,直到
所有線程都到達(dá)臨界點(diǎn)),當(dāng)多個(gè)線程同時(shí)到達(dá)臨界點(diǎn)時(shí),隨機(jī)挑一個(gè)線程執(zhí)行barrierAction后再同時(shí)恢
復(fù)執(zhí)行。await():調(diào)用該方法時(shí)表示線程已經(jīng)到達(dá)屏障,隨即阻塞。模擬:多線程向磁盤寫入數(shù)據(jù),計(jì)
數(shù)器的值可以恢復(fù)。
SemaPhore-信號(hào)量:SemaPhore是synchronized或Lock的加強(qiáng)版,作用是控制線程的并發(fā)數(shù)量。作
用:用來控制同時(shí)訪問特定資源的線程數(shù)量,通過協(xié)調(diào)保證合理的使用公共資源。acquire():嘗試占用一
個(gè)信號(hào)量,失敗的線程會(huì)阻塞,直達(dá)有新的信號(hào)量,再恢復(fù)執(zhí)行release():釋放一個(gè)信號(hào)量;acquire(n):
嘗試占用n信號(hào)量,失敗的線程會(huì)阻塞,直達(dá)有新的信號(hào)量,再恢復(fù)執(zhí)行,release(n):釋放n信號(hào)量。
Exchanger 線程交換器,Exchange類似于一個(gè)交換器,Exchange類允許在兩個(gè)線程之間定義同步點(diǎn)。
當(dāng)兩個(gè)線程都到達(dá)同步點(diǎn)時(shí),他們交換數(shù)據(jù),因此第一個(gè)線程的數(shù)據(jù)進(jìn)入到第二個(gè)線程中,第二個(gè)線程
的數(shù)據(jù)進(jìn)入到第一個(gè)線程中。 - ConcurrentHashMap如何保證線程安全
底層采用CAS(內(nèi)存比較交換技術(shù)) + volatile + synchronize實(shí)現(xiàn), 初始化為無鎖狀態(tài)也就是使用CAS +
volatile解決安全問題, 但是會(huì)涉及ABA問題. 但是幾率很小, 如果涉及ABA問題, 底層自動(dòng)切換成使用
synchronize實(shí)現(xiàn). - ConcurrentHashMap在1.7和1.8的底層實(shí)現(xiàn)
JDK1.7底層采用 : 數(shù)組 + 鏈表,采用Segement保證安全
JDK1.8底層采用: 數(shù)據(jù) + 鏈表 + 紅黑樹,采用CAS+synchronized代碼塊保證安全 - ConcurrentLinkedQueue和LinkedBlockingQueue有什么區(qū)別
首先二者都是線程安全的得隊(duì)列,都可以用于生產(chǎn)與消費(fèi)模型的場(chǎng)景。
LinkedBlockingQueue是阻塞隊(duì)列,其好處是:多線程操作共同的隊(duì)列時(shí)不需要額外的同步,由于具有
插入與移除的雙重阻塞功能,對(duì)插入與移除進(jìn)行阻塞,隊(duì)列會(huì)自動(dòng)平衡負(fù)載,從而減少生產(chǎn)與消費(fèi)的處
理速度差距。
由于LinkedBlockingQueue有阻塞功能,其阻塞是基于鎖機(jī)制實(shí)現(xiàn)的,當(dāng)有多個(gè)線程消費(fèi)時(shí)候,隊(duì)列為
空時(shí)消費(fèi)線程被阻塞,有元素時(shí)需要再喚醒消費(fèi)線程,隊(duì)列元素可能時(shí)有時(shí)無,導(dǎo)致用戶態(tài)與內(nèi)核態(tài)切
換頻繁,消耗系統(tǒng)資源。從此方面來講,LinkedBlockingQueue更適用于多線程插入,單線程取出,即
多個(gè)生產(chǎn)者與單個(gè)消費(fèi)者。
ConcurrentLinkedQueue非阻塞隊(duì)列,采用 CAS+自旋操作,解決多線程之間的競(jìng)爭(zhēng),多寫操作增加沖
突幾率,增加自選次數(shù),并不適合多寫入的場(chǎng)景。當(dāng)許多線程共享訪問一個(gè)公共集合時(shí),
ConcurrentLinkedQueue 是一個(gè)恰當(dāng)?shù)倪x擇。從此方面來講,ConcurrentLinkedQueue更適用于單線
程插入,多線程取出,即單個(gè)生產(chǎn)者與多個(gè)消費(fèi)者。
總之,對(duì)于幾個(gè)線程生產(chǎn)與幾個(gè)線程消費(fèi),二者并沒有嚴(yán)格的規(guī)定, 只有誰(shuí)更適合。 - 說一下 HashSet 的實(shí)現(xiàn)原理
HashSet底層使用了哈希表來支持的,特點(diǎn):存儲(chǔ)快, 底層由HashMap實(shí)現(xiàn)
往HashSet添加元素的時(shí)候,HashSet會(huì)先調(diào)用元素的hashCode方法得到元素的哈希值 ,然后通過元
素 的哈希值經(jīng)過移位等運(yùn)算,就可以算出該元素在哈希表中的存儲(chǔ)位置。
如果算出的元素存儲(chǔ)的位置目前沒有任何元素存儲(chǔ),那么該元素可以直接存儲(chǔ)在該位置上
如果算出的元素的存儲(chǔ)位置目前已經(jīng)存在有其他的元素了,那么還會(huì)調(diào)用該元素的equals方法與該位置
的元素再比較一次,如果equals方法返回的是true,那么該位置上的元素視為重復(fù)元素,不允許添加,
如果返回的是false,則允許添加 - HashSet和TreeSet有什么區(qū)別?
HashSet是由一個(gè)hash表來實(shí)現(xiàn)的,因此,它的元素是無序的。add(),remove(),contains()方法的時(shí)
間復(fù)雜度是O(1)。
TreeSet是由一個(gè)樹形的結(jié)構(gòu)來實(shí)現(xiàn)的,它里面的元素是有序的。因此,add(),remove(),contains()方
法的時(shí)間復(fù)雜度是O(logn)。
四、多線程 【25道】
- 聊一聊并行和并發(fā)的區(qū)別?
并發(fā)是指同一時(shí)間一起發(fā)生的.
例如: 一個(gè)處理器同時(shí)處理多個(gè)任務(wù)叫并發(fā)處理, 同一時(shí)間好多個(gè)請(qǐng)求一起訪問你的網(wǎng)站叫做并發(fā)請(qǐng)
求等
并行是指多個(gè)任務(wù)在同一時(shí)間一起運(yùn)行.
例如: 多個(gè)處理器或者是多核的處理器同時(shí)處理多個(gè)不同的任務(wù), 這叫并行執(zhí)行. - 線程和進(jìn)程的區(qū)別?
進(jìn)程是執(zhí)行中的一個(gè)程序, 而一個(gè)進(jìn)程中執(zhí)行的一個(gè)任務(wù)即為一個(gè)線程
一個(gè)線程只屬于一個(gè)進(jìn)程, 但一個(gè)進(jìn)程能包含多個(gè)線程
線程無地址空間, 它包括在進(jìn)程的地址空間中
線程的開銷比進(jìn)程小 - 說一說什么是原子性、可見性、有序性?
原子性:提供互斥訪問,同一時(shí)刻只能有一個(gè)線程對(duì)數(shù)據(jù)進(jìn)行操作(Atomic、CAS算法、
synchronized、Lock)
可見性:一個(gè)主內(nèi)存的線程如果進(jìn)行了修改,可以及時(shí)被其他線程觀察到(synchronized、volatile)
有序性:如果兩個(gè)線程不能從 happens-before原則 觀察出來,那么就不能觀察他們的有序性,虛擬機(jī)
可以隨意的對(duì)他們進(jìn)行重排序,導(dǎo)致其觀察結(jié)果雜亂無序(happens-before原則) - Java中實(shí)現(xiàn)多線程的方式
通過擴(kuò)展Thread類來創(chuàng)建多線程:
定義Thread類的子類,并重寫該類的run方法,該run方法的方法體就代表了線程要完成的任務(wù)。
通過實(shí)現(xiàn)Runnable接口來創(chuàng)建多線程
定義runnable接口的實(shí)現(xiàn)類,并重寫該接口的run()方法,該run()方法的方法體同樣是該線程的線
程執(zhí)行體
通過線程池實(shí)現(xiàn)
定義線程池實(shí)例, 使用的時(shí)候從線程池中獲取線程使用.
通過Callable和Future創(chuàng)建線程
創(chuàng)建Callable接口的實(shí)現(xiàn)類,并實(shí)現(xiàn)call()方法,該call()方法將作為線程執(zhí)行體,并且有返回值
創(chuàng)建Callable實(shí)現(xiàn)類的實(shí)例,使用FutureTask類來包裝Callable對(duì)象,該FutureTask對(duì)象封裝了該
Callable對(duì)象的call()方法的返回值。
使用FutureTask對(duì)象作為Thread對(duì)象的target創(chuàng)建并啟動(dòng)新線程。
調(diào)用FutureTask對(duì)象的get()方法來獲得子線程執(zhí)行結(jié)束后的返回值 - Java中提供的線程池種類,及其特點(diǎn)!
五種線程池
Single Thread Executor : 這是一個(gè)單線程的Executor,它創(chuàng)建單個(gè)工作線程來執(zhí)行任務(wù),如果這
個(gè)線程異常結(jié)束,會(huì)創(chuàng)建一個(gè)新的來替代它;它的特點(diǎn)是能確保依照任務(wù)在隊(duì)列中的順序來串行執(zhí)
行
代碼: Executors.newSingleThreadExecutor()
Cached Thread Pool : 創(chuàng)建一個(gè)可緩存的線程池,如果線程池的規(guī)模超過了處理需求,將自動(dòng)回收
空閑線程,而當(dāng)需求增加時(shí),則可以自動(dòng)添加新線程,線程池的規(guī)模不存在任何限制。
代碼:Executors.newCachedThreadPool()
Fixed Thread Pool : 擁有固定線程數(shù)的線程池,如果沒有任務(wù)執(zhí)行,那么線程會(huì)一直等待, 代
碼: Executors.newFixedThreadPool(4) 在構(gòu)造函數(shù)中的參數(shù)4是線程池的大小,你可以隨意設(shè)
置,也可以和cpu的核數(shù)量保持一致,獲取cpu的核數(shù)量
代碼 : int cpuNums = Runtime.getRuntime().availableProcessors();
Scheduled Thread Pool : 創(chuàng)建一個(gè)固定長(zhǎng)度的線程池,而且以延遲或定時(shí)的方式來執(zhí)行任務(wù),類
似于Timer。
代碼:Executors.newScheduledThreadPool() - 線程池的工作原理以及重要參數(shù)含義
原理:
線程池會(huì)初始化一定數(shù)量線程, 每次使用線程從線程池中拿一個(gè)使用, 省去了創(chuàng)建線程的開銷和時(shí)間,
使用完畢, 放回線程池中, 不銷毀, 省去了銷毀的開銷和時(shí)間.
重要參數(shù):
corePoolSize:線程池核心線程數(shù)量
maximumPoolSize:線程池最大線程數(shù)量
keepAliverTime:當(dāng)活躍線程數(shù)大于核心線程數(shù)時(shí),空閑的多余線程最大存活時(shí)間
unit:存活時(shí)間的單位
workQueue:存放任務(wù)的隊(duì)列
threadFactory:創(chuàng)建線程的工廠
handler:拒絕策略 - 線程池的阻塞隊(duì)列有哪些?
ArrayBlockingQueue : 數(shù)組有界阻塞隊(duì)列FIFO, 按照阻塞的先后順序訪問隊(duì)列,默認(rèn)情況下不保證線程
公平的訪問隊(duì)列, 如果要保證公平性,會(huì)降低一定的吞吐量
LinkedBlockingQueue : 鏈表有界阻塞隊(duì)列,默認(rèn)最大長(zhǎng)度為Integer.MAX_VALUE。此隊(duì)列按照先進(jìn)先
出的原則對(duì)元素進(jìn)行排序SynchronousQueue : 不存儲(chǔ)元素的阻塞隊(duì)列
DelayedWorkQueue : 延遲獲取元素隊(duì)列,指定時(shí)間后獲取,為無界阻塞隊(duì)列。 - 線程池的拒絕策略有哪些?
拒絕策略核心接口:
java.util.concurrent.RejectedExecutionHandler
實(shí)現(xiàn):
CallerRunsPolicy : java.util.concurrent.ThreadPoolExecutor.CallerRunsPolicy
當(dāng)有新任務(wù)提交后,如果線程池沒被關(guān)閉且沒有能力執(zhí)行,則把這個(gè)任務(wù)交于提交任務(wù)的線程
執(zhí)行,也就是誰(shuí)提交任務(wù),誰(shuí)就負(fù)責(zé)執(zhí)行任務(wù)。
AbortPolicy : java.util.concurrent.ThreadPoolExecutor.AbortPolicy
拒絕策略在拒絕任務(wù)時(shí),會(huì)直接拋出一個(gè)類型為 RejectedExecutionException 的
RuntimeException,讓你感知到任務(wù)被拒絕了,于是你便可以根據(jù)業(yè)務(wù)邏輯選擇重試或者放
棄提交等策略。
DiscardPolicy : java.util.concurrent.ThreadPoolExecutor.DiscardPolicy
當(dāng)新任務(wù)被提交后直接被丟棄掉,也不會(huì)給你任何的通知,相對(duì)而言存在一定的風(fēng)險(xiǎn),因?yàn)槲?br /> 們提交的時(shí)候根本不知道這個(gè)任務(wù)會(huì)被丟棄,可能造成數(shù)據(jù)丟失。
DiscardOldestPolicy : java.util.concurrent.ThreadPoolExecutor.DiscardOldestPolicy
如果線程池沒被關(guān)閉且沒有能力執(zhí)行,則會(huì)丟棄任務(wù)隊(duì)列中的頭結(jié)點(diǎn),通常是存活時(shí)間最長(zhǎng)的
任務(wù),這種策略與第二種不同之處在于它丟棄的不是最新提交的,而是隊(duì)列中存活時(shí)間最長(zhǎng)
的,這樣就可以騰出空間給新提交的任務(wù),但同理它也存在一定的數(shù)據(jù)丟失風(fēng)險(xiǎn)。 - synchronized和reentranlock鎖的區(qū)別?
Synchronized屬于重量級(jí)鎖, JDK早期版本使用了線程的狀態(tài)變化來實(shí)現(xiàn), JDK1.8中使用了自適應(yīng)自旋鎖
實(shí)現(xiàn). 總體來說安全, 但是效率慢
Reentranlock是JUC包下的鎖, 底層使用CAS + Volatile關(guān)鍵字實(shí)現(xiàn). 效率高. - synchronized底層如何實(shí)現(xiàn)?什么是鎖升級(jí)、降級(jí)!
jdk 中做了優(yōu)化,提供了三種不同的 Monitor實(shí)現(xiàn),分別是:
偏斜鎖 (Biased Locking)
輕量級(jí)鎖
重量級(jí)鎖
所謂鎖的升級(jí),降級(jí),實(shí)際上是 JVM 對(duì) synchronized優(yōu)化的一種策略,JVM 會(huì)檢測(cè)不同的競(jìng)爭(zhēng)狀態(tài),
然后自動(dòng)切換到合適的鎖實(shí)現(xiàn),這種切換就是鎖的升級(jí),降級(jí)。
當(dāng)沒有出現(xiàn)鎖的競(jìng)爭(zhēng)時(shí),默認(rèn)使用的是偏斜鎖。JVM 會(huì)利用 CAS 實(shí)現(xiàn).
如果有另一個(gè)線程試圖鎖定某個(gè)以被加持偏斜鎖的對(duì)象時(shí),JVM 就需要撤銷偏斜鎖,并切換到輕量級(jí)鎖
實(shí)現(xiàn)。如果獲取成功,就使用輕量級(jí)鎖,否者,進(jìn)一步升級(jí)到重量級(jí)鎖 - reentranlock的底層實(shí)現(xiàn)原理
reentranlock 是基于AQS(AbstractQueueSynchronizer) 采用FIFO的隊(duì)列表示排隊(duì)等待的線程.
AQS底層采用CAS(內(nèi)存比較交換技術(shù)) + Volatile關(guān)鍵字實(shí)現(xiàn). - volatile關(guān)鍵字的特點(diǎn)
保證線程之間的可見性,當(dāng)一個(gè)線程對(duì)共享的變量進(jìn)行了修改,其他的線程能夠通過此關(guān)鍵字發(fā)現(xiàn)
這個(gè)修改
禁止指令重排序,編譯器在編譯的過程中會(huì)對(duì)程序進(jìn)行優(yōu)化,在保證結(jié)果不變的前提下,調(diào)整指令
執(zhí)行的順序,提高執(zhí)行效率,如果加了volatile關(guān)鍵字,則會(huì)禁止指令重排序。
不能保證原子性 - Java為什么會(huì)指令重排
java中源代碼文件會(huì)被編譯成.class的字節(jié)碼文件, 字節(jié)碼指定在執(zhí)行之前, jvm底層有內(nèi)置的對(duì)字節(jié)碼的
優(yōu)化策略, 也就是指令重排機(jī)制, 會(huì)調(diào)整指令執(zhí)行順序. 目的是加快執(zhí)行速度. - 悲觀鎖和樂觀鎖的區(qū)別
悲觀鎖 : 無論讀還是更改對(duì)象都要加鎖, 所以慢, 但是安全.
樂觀鎖 : 讀不加鎖, 更改對(duì)象加鎖, 所以快, 但是沒有悲觀鎖安全 - 什么是CAS, 以及它的ABA問題, 如何解決ABA問題
CAS的含義 :
CAS是compare and swap的縮寫,即我們所說的比較交換。
CAS 操作包含三個(gè)操作數(shù) —— 內(nèi)存位置(V)、預(yù)期原值(A)和新值(B)。如果內(nèi)存地址里面的值
和A的值是一樣的,那么就將內(nèi)存里面的值更新成B。CAS是通過無限循環(huán)來獲取數(shù)據(jù)的,若果在第
一輪循環(huán)中,a線程獲取地址里面的值被b線程修改了,那么a線程需要自旋,到下次循環(huán)才有可能
機(jī)會(huì)執(zhí)行。
CAS的ABA問題 :
CAS容易造成ABA問題。一個(gè)線程a將數(shù)值改成了b,接著又改成了a,此時(shí)CAS認(rèn)為是沒有變化,其
實(shí)是已經(jīng)變化過了,而這個(gè)問題的解決方案可以使用版本號(hào)標(biāo)識(shí),每操作一次version加1。在
java5中,已經(jīng)提供了AtomicStampedReference來解決問題。
CAS造成CPU利用率增加。之前說過了CAS里面是一個(gè)循環(huán)判斷的過程,如果線程一直沒有獲取到
狀態(tài),cpu資源會(huì)一直被占用。 - Atomic變量如何保證的原子性
它底層是使用CAS + volatile關(guān)鍵字來保證原子性操作,從而達(dá)到線程安全的目的. - ThreadLocal是什么,有什么作用
ThreadLocal是一個(gè)本地線程副本變量工具類。主要用于將私有線程和該線程存放的副本對(duì)象做一個(gè)映
射,各個(gè)線程之間的變量互不干擾,在高并發(fā)場(chǎng)景下可以實(shí)現(xiàn)無狀態(tài)的調(diào)用,特別適用于各個(gè)線程依賴
不通 的變量值完成操作的場(chǎng)景。
簡(jiǎn)單說ThreadLocal就是一種以空間換時(shí)間的做法,在每個(gè)Thread里面維護(hù)了一個(gè)以開地址法實(shí)現(xiàn)的
ThreadLocal.ThreadLocalMap,把數(shù)據(jù)進(jìn)行隔離,數(shù)據(jù)不共享,自然就沒有線程安全的問題了。 - ThreadLocal的內(nèi)存泄漏問題
ThreadLocalMap 中的 key 是一個(gè) ThreadLocal 對(duì)象,且是一個(gè)弱引用,而 value 卻是一個(gè)強(qiáng)引
用。
存在一種情況,可能導(dǎo)致內(nèi)存泄漏。如果在某一時(shí)刻,將 ThreadLocal 實(shí)例設(shè)置為 null ,即
ThreadLocal 沒有強(qiáng)引用了,如果發(fā)生 GC 時(shí),由于 ThreadLocal 實(shí)例只存在弱引用,所以被回收
了,但是 value 仍然存在一個(gè)當(dāng)前線程連接過來的強(qiáng)引用,其不會(huì)被回收,只有等到線程結(jié)束死亡或
者手動(dòng)清空 value 或者等到另一個(gè) ThreadLocal 對(duì)象進(jìn)行 get 或 set 操作時(shí)剛好觸發(fā)
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
// 。。。。
}
ThreadLocal的內(nèi)存泄漏問題
expungeStaleEntry 函數(shù)并且剛好能夠檢查到本 ThreadLocal 對(duì)象 key 為空(概率太小),這樣才
不會(huì)發(fā)生內(nèi)存泄漏。否則, value 始終有引用指向它,它也不會(huì)被 GC 回收,那么就會(huì)導(dǎo)致內(nèi)存泄漏。
雖然發(fā)生內(nèi)存泄漏的概率比較小,但是為了保險(xiǎn)起見,也建議在使用完 ThreadLocal 對(duì)象后調(diào)用一下
remove 方法清理一下值。
作者:guozhchun - jdk1.8對(duì)鎖進(jìn)行了哪些優(yōu)化
LongAdder 類似automicLong, 但是提供了“熱點(diǎn)分離”。過程如下:如果并發(fā)不激烈,則與
automicLong 一樣,cas賦值。如果出現(xiàn)并發(fā)操作,則使用數(shù)組,數(shù)組的各元素之和為真實(shí)value值,讓
操作分散在數(shù)組各個(gè)元素上,把并發(fā)操作壓力分散,一遇到并發(fā)就擴(kuò)容數(shù)組,最后達(dá)到高效率。一般cas
如果遇到高并發(fā),可能一直賦值失敗導(dǎo)致不斷循環(huán),熱點(diǎn)分離可以解決這個(gè)問題
stampedLock 改進(jìn)讀寫鎖,讀不阻塞寫。
completableFuture 對(duì)Future進(jìn)行增強(qiáng),支持函數(shù)式編程的流式調(diào)用 - 死鎖發(fā)生的情況和如何避免死鎖
死鎖就是多個(gè)線程同時(shí)等待其他線程釋放鎖,導(dǎo)致被無限期阻塞的現(xiàn)象。
發(fā)生死鎖的四個(gè)必要條件:
互斥條件
不可搶占條件
占有且申請(qǐng)條件
循環(huán)等待條件
避免死鎖:
盡量避免一個(gè)線程同時(shí)獲取多個(gè)鎖
盡量避免一個(gè)線程在鎖內(nèi)部占用多個(gè)資源,盡量保證每個(gè)鎖只占用一個(gè)資源
順序加鎖
加鎖時(shí)限
死鎖檢驗(yàn) - 介紹鎖的升級(jí)過程
鎖狀態(tài)一種有四種 : 從級(jí)別由低到高依次是:無鎖、偏向鎖,輕量級(jí)鎖,重量級(jí)鎖,
鎖狀態(tài)只能升級(jí),不能降級(jí)
升級(jí)過程 :
無鎖狀態(tài) - > 當(dāng)一個(gè)線程訪問同步代碼塊時(shí)升級(jí)成偏向鎖 - > 偏向鎖 -> 有鎖競(jìng)爭(zhēng)時(shí)升級(jí)成輕量級(jí)鎖 - >
輕量級(jí)鎖 - > 自旋N次失敗, 鎖膨脹, 升級(jí)成重量級(jí)鎖 - > 重量級(jí)鎖 - 介紹鎖的降級(jí)過程
鎖降級(jí)
鎖降級(jí)發(fā)生在讀寫鎖中,寫鎖降級(jí)讀鎖的過程
讀寫鎖 : ReentrantReadWriteLock
讀寫鎖,既可以獲取讀鎖,也可以獲取寫鎖
寫鎖是獨(dú)占鎖,所謂獨(dú)占即為獨(dú)自占有,別的線程既不能獲取到該鎖的寫鎖,也不能獲取到對(duì)
應(yīng)的讀鎖。
讀鎖是共享鎖,所謂共享即是所有線程都可以共同持有該讀鎖
鎖降級(jí)過程 :
鎖降級(jí)指的是寫鎖降級(jí)為讀鎖的過程,他的過程是持有寫鎖,獲取讀鎖,然后釋放寫鎖 - 怎么解決多線程的大量訪問時(shí)的數(shù)據(jù)同步
可以加鎖解決, 至于鎖可以使用synchronized重量級(jí)鎖, 也可以使用Lock輕量級(jí)鎖
還可以使用線程安全的對(duì)象作為多線程共用的數(shù)據(jù)操作對(duì)象, 比如ConcurrentHashMap, 或者Atomic原
子操作類等
速度快慢則是, 線程安全操作對(duì)象ConcurrentHashMap或者原子操作類比輕量級(jí)鎖快, 輕量級(jí)鎖比重量
級(jí)鎖要快. - 線程的 run()和 start()有什么區(qū)別
每個(gè)線程都是通過某個(gè)特定Thread對(duì)象所對(duì)應(yīng)的run()方法來完成其操作的,方法run()稱為線程體。也
就是run方法中是線程具體要執(zhí)行的任務(wù)或者業(yè)務(wù).
Start方法是啟動(dòng)線程, 調(diào)用線程類的Start方法線程開始運(yùn)行. 這時(shí)無需等待run方法體代碼執(zhí)行完畢,可
以直接繼續(xù)執(zhí)行下面的代碼, 真正實(shí)現(xiàn)了多線程運(yùn)行 - JDK1.6對(duì)Synchronized底層做了哪些優(yōu)化
Synchronized底層早期JDK版本是采用線程的狀態(tài)轉(zhuǎn)換實(shí)現(xiàn)的, 主要使用了線程的阻塞和喚醒來實(shí)現(xiàn).
JDK1.6中使用了自適應(yīng)自旋鎖實(shí)現(xiàn), 還加入的鎖消除, 鎖粗化等優(yōu)化策略
自適應(yīng)自旋鎖 : JDK 1.6引入了更加聰明的自旋鎖,即自適應(yīng)自旋鎖。所謂自適應(yīng)就意味著自旋的次數(shù)不
再是固定的,它是由前一次在同一個(gè)鎖上的自旋時(shí)間及鎖的擁有者的狀態(tài)來決定。
鎖消除 : 為了保證數(shù)據(jù)的完整性,我們?cè)谶M(jìn)行操作時(shí)需要對(duì)這部分操作進(jìn)行同步控制,但是在有些情況
下,JVM檢測(cè)到不可能存在共享數(shù)據(jù)競(jìng)爭(zhēng),這是JVM會(huì)對(duì)這些同步鎖進(jìn)行鎖消除
鎖粗化 : 就是將多個(gè)連續(xù)的加鎖、解鎖操作連接在一起,擴(kuò)展成一個(gè)范圍更大的鎖
五、IO【5道】
- Java提供了哪些IO方式?
傳統(tǒng)BIO同步阻塞IO, 這里面又分為字節(jié)流和字符流
JDK1.4引入NIO, 同步非阻塞IO, 速度比傳統(tǒng)BIO快, 并且更節(jié)省資源
JDK1.7引入NIO2也叫做AIO, 異步非阻塞IO, 基于事件和回調(diào)機(jī)制實(shí)現(xiàn), 速度更快 - NIO, BIO, AIO區(qū)別?
NIO:在JDK1.4以前,Java的IO模型一直是BIO,從JDK1.4開始,JDK引入的新的IO模型NIO,它是同步
非阻塞的。而服務(wù)器的實(shí)現(xiàn)模式是多個(gè)請(qǐng)求一個(gè)線程,即請(qǐng)求會(huì)注冊(cè)到多路復(fù)用器Selecter上,多路復(fù)
用器輪詢到連接有IO請(qǐng)求時(shí)才啟動(dòng)一個(gè)線程處理
BIO:同步阻塞,服務(wù)器的實(shí)現(xiàn)模式是一個(gè)連接一個(gè)線程,這樣的模式很明顯的一個(gè)缺陷是:優(yōu)于客戶
端連接數(shù)與服務(wù)器線程數(shù)成正比關(guān)系,可能造成不必要的線程開銷,嚴(yán)重的還會(huì)導(dǎo)致服務(wù)器內(nèi)存溢出,
當(dāng)然,這種情況可以通過線程池改善,但并不能從本質(zhì)上消除這個(gè)弊端
AIO:JDK1.7發(fā)布了NIO2.0,這就是真正意義上的異步非阻塞,服務(wù)器的實(shí)現(xiàn)模式為多個(gè)有效請(qǐng)求一個(gè)
線程,客戶端的IO請(qǐng)求都是由OS完成再通知服務(wù)器應(yīng)用去啟動(dòng)線程處理(回調(diào)) - 有哪些緩沖流?如何實(shí)現(xiàn)緩沖功能!
緩沖流也叫高效流,是對(duì)四個(gè)基本的FileXxx流的增強(qiáng),按照數(shù)據(jù)類型分類:
字節(jié)緩沖流 :BufferedInputStream,BufferedOutputStream
字符緩沖流:BufferedReader,BufferedWriter
基本原理:
是在創(chuàng)建流對(duì)象的時(shí)候,會(huì)創(chuàng)建一個(gè)內(nèi)置默認(rèn)大小的緩沖區(qū)數(shù)組,減少系統(tǒng)IO次數(shù),從而提高讀寫
效率
字節(jié)緩沖流
public BufferedInputStream(InputStream in) :創(chuàng)建一個(gè) 新的緩沖輸入流。
public BufferedOutputStream(OutputStream out) : 創(chuàng)建一個(gè)新的緩沖輸出流。 - 實(shí)現(xiàn)文件拷貝的幾種方式!
通過字節(jié)流實(shí)現(xiàn)文件拷貝
通過字符流實(shí)現(xiàn)文件拷貝
通過字節(jié)緩沖流實(shí)現(xiàn)文件拷貝
通過字符緩沖流實(shí)現(xiàn)文件拷貝
通過JAVA NIO非直接緩沖區(qū)拷貝文件
通過JAVA NIO直接緩沖區(qū)拷貝文件
通過JAVA NIO通道傳輸拷貝文件 - 什么Java序列化,如何實(shí)現(xiàn)序列化!
序列化:就是一種用來處理對(duì)象流的機(jī)制,所謂對(duì)象流也就是將對(duì)象的內(nèi)容進(jìn)行流化??梢詫?duì)流化后的
對(duì)象進(jìn)行讀寫操作,也可將流化后的對(duì)象傳輸于網(wǎng)絡(luò)之間。序列化是為了解決在對(duì)對(duì)象流進(jìn)行讀寫操作
時(shí)所引發(fā)的問題。
序列化的實(shí)現(xiàn):將需要被序列化的類實(shí)現(xiàn)Serializable接口,該接口沒有需要實(shí)現(xiàn)的方法,implements
Serializable只是為了標(biāo)注該對(duì)象是可被序列化的,然后使用一個(gè)輸出流(如:FileOutputStream)來構(gòu)造
一個(gè)ObjectOutputStream(對(duì)象流)對(duì)象,接著,使用ObjectOutputStream對(duì)象的writeObject(Object
obj)方法就可以將參數(shù)為obj的對(duì)象寫出(即保存其狀態(tài)),要恢復(fù)的話則用輸入流。
六、網(wǎng)絡(luò)編程 【9道】
- http協(xié)議和RPC協(xié)議區(qū)別
RPC是遠(yuǎn)程過程調(diào)用, 是JDK底層定義的規(guī)范, 它的實(shí)現(xiàn)既可以使用TCP也可以使用http, 底層可以使用二
進(jìn)制傳輸, 效率高.
Http是協(xié)議, http協(xié)議底層位于傳輸層是tcp協(xié)議. 通用性好可以傳輸json數(shù)據(jù), 效率稍微慢一些 - OSI網(wǎng)絡(luò)模型七層都有哪些
OSI參考模型分為7層,分別是物理層,數(shù)據(jù)鏈路層,網(wǎng)絡(luò)層,傳輸層,會(huì)話層,表示層和應(yīng)用層。 - 傳輸層上的TCP和UDP區(qū)別
TCP與UDP的特點(diǎn):
UDP:無連接、不可靠、傳輸速度快. 適合傳輸語(yǔ)音, 視頻等
TCP:面向連接、可靠、傳輸速度沒有UDP快, 例如: http, smtp等協(xié)議底層就是tcp - http協(xié)議1.0和1.1版本區(qū)別
主要有4個(gè)方面:緩存處理,帶寬優(yōu)化和網(wǎng)絡(luò)連接使用,Host請(qǐng)求頭的區(qū)別,長(zhǎng)連接
● 緩存處理:HTTP1.1則引入了更多的緩存控制策略
● 長(zhǎng)連接:HTTP1.1默認(rèn)使用長(zhǎng)連接,可有效減少TCP的三次握手開銷,在一個(gè)TCP連接上可以傳送多個(gè)
HTTP請(qǐng)求和響應(yīng),減少了建立和關(guān)閉連接的消耗和延遲
● Host請(qǐng)求頭的區(qū)別:HTTP1.0是沒有host域的,HTTP1.1才支持這個(gè)參數(shù)
● 帶寬優(yōu)化和網(wǎng)絡(luò)連接使用:在1.1后,現(xiàn)在只會(huì)發(fā)送head信息,果服務(wù)器認(rèn)為客戶端有權(quán)限請(qǐng)求服務(wù)
器,則返回100,當(dāng)接收到100后才會(huì)將剩下的信息發(fā)送到服務(wù)器 - http是短連接還是長(zhǎng)連接, 如何實(shí)現(xiàn)的
HTTP協(xié)議與TCP/IP協(xié)議的關(guān)系
HTTP的長(zhǎng)連接和短連接本質(zhì)上是TCP長(zhǎng)連接和短連接。HTTP屬于應(yīng)用層協(xié)議,在傳輸層使用TCP
協(xié)議,在網(wǎng)絡(luò)層使用IP協(xié)議。IP協(xié)議主要解決網(wǎng)絡(luò)路由和尋址問題,TCP協(xié)議主要解決如何在IP層
之上可靠的傳遞數(shù)據(jù)包,使在網(wǎng)絡(luò)上的另一端收到發(fā)端發(fā)出的所有包,并且順序與發(fā)出順序一致。
TCP有可靠,面向連接的特點(diǎn)。
如何理解HTTP協(xié)議是無狀態(tài)的
HTTP協(xié)議是無狀態(tài)的,指的是協(xié)議對(duì)于事務(wù)處理沒有記憶能力,服務(wù)器不知道客戶端是什么狀
態(tài)。也就是說,打開一個(gè)服務(wù)器上的網(wǎng)頁(yè)和你之前打開這個(gè)服務(wù)器上的網(wǎng)頁(yè)之間沒有任何聯(lián)系。
HTTP是一個(gè)無狀態(tài)的面向連接的協(xié)議,無狀態(tài)不代表HTTP不能保持TCP連接,更不能代表HTTP使
用的是UDP協(xié)議(無連接)。
什么是長(zhǎng)連接、短連接?
在HTTP/1.0中,默認(rèn)使用的是短連接。也就是說,瀏覽器和服務(wù)器每進(jìn)行一次HTTP操作,就建立
一次連接,但任務(wù)結(jié)束就中斷連接。如果客戶端瀏覽器訪問的某個(gè)HTML或其他類型的 Web頁(yè)中包
含有其他的Web資源,如JavaScript文件、圖像文件、CSS文件等;當(dāng)瀏覽器每遇到這樣一個(gè)Web
資源,就會(huì)建立一個(gè)HTTP會(huì)話。
但從 HTTP/1.1起,默認(rèn)使用長(zhǎng)連接,用以保持連接特性。使用長(zhǎng)連接的HTTP協(xié)議,會(huì)在響應(yīng)頭有
加入這行代碼:Connection:keep-alive
在使用長(zhǎng)連接的情況下,當(dāng)一個(gè)網(wǎng)頁(yè)打開完成后,客戶端和服務(wù)器之間用于傳輸HTTP數(shù)據(jù)的 TCP
連接不會(huì)關(guān)閉,如果客戶端再次訪問這個(gè)服務(wù)器上的網(wǎng)頁(yè),會(huì)繼續(xù)使用這一條已經(jīng)建立的連接。
Keep-Alive不會(huì)永久保持連接,它有一個(gè)保持時(shí)間,可以在不同的服務(wù)器軟件(如Apache)中設(shè)
定這個(gè)時(shí)間。實(shí)現(xiàn)長(zhǎng)連接要客戶端和服務(wù)端都支持長(zhǎng)連接。
HTTP協(xié)議的長(zhǎng)連接和短連接,實(shí)質(zhì)上是TCP協(xié)議的長(zhǎng)連接和短連接。
TCP連接
當(dāng)網(wǎng)絡(luò)通信時(shí)采用TCP協(xié)議時(shí),在真正的讀寫操作之前,server與client之間必須建立一個(gè)連
接,當(dāng)讀寫操作完成后,雙方不再需要這個(gè)連接 時(shí)它們可以釋放這個(gè)連接,連接的建立是需
要三次握手的,而釋放則需要4次握手,所以說每個(gè)連接的建立都是需要資源消耗和時(shí)間消耗
三次握手建立連接 , 四次揮手關(guān)閉連接
短連接的操作步驟是:
建立連接——數(shù)據(jù)傳輸——關(guān)閉連接…建立連接——數(shù)據(jù)傳輸——關(guān)閉連接
長(zhǎng)連接的操作步驟是:
建立連接——數(shù)據(jù)傳輸…(保持連接)…數(shù)據(jù)傳輸——關(guān)閉連接 - 簡(jiǎn)述tcp三次握手四次揮手過程
先向HTTP服務(wù)器發(fā)起TCP的確認(rèn)請(qǐng)求(三次握手)
客戶端 --> SYN --> 服務(wù)器
服務(wù)器 --> SYN+ACK —>客戶端
客戶端 --> ACK --> 服務(wù)器
客戶端要和服務(wù)器斷開TCP連接(四次揮手)
客戶端 --> FIN +ACK —> 服務(wù)器
服務(wù)器 --> FIN —> 客戶端
服務(wù)器 --> ACK --> 客戶端
客戶端 --> ACK —> 服務(wù)器 - http有多少類響應(yīng)碼?分別什么含義
響應(yīng)碼由三位十進(jìn)制數(shù)字組成,它們出現(xiàn)在由HTTP服務(wù)器發(fā)送的響應(yīng)的第一行。
響應(yīng)碼分五種類型,由它們的第一位數(shù)字表示:
1xx:信息,請(qǐng)求收到,繼續(xù)處理
2xx:成功,行為被成功地接受、理解和采納
3xx:重定向,為了完成請(qǐng)求,必須進(jìn)一步執(zhí)行的動(dòng)作
4xx:客戶端錯(cuò)誤,請(qǐng)求包含語(yǔ)法錯(cuò)誤或者請(qǐng)求無法實(shí)現(xiàn)
5xx:服務(wù)器錯(cuò)誤,服務(wù)器不能實(shí)現(xiàn)一種明顯無效的請(qǐng)求 - tomcat的實(shí)現(xiàn)原理!tomcat如何進(jìn)行優(yōu)化?
tomcat是一個(gè)基于JAVA的WEB容器,其實(shí)現(xiàn)了JAVA EE中的 Servlet 與 jsp 規(guī)范,與Nginx Apache 服務(wù)
器不同在于一般用于動(dòng)態(tài)請(qǐng)求處理。在架構(gòu)設(shè)計(jì)上采用面向組件的方式設(shè)計(jì)。即整體功能是通過組件的
方式拼裝完成。另外每個(gè)組件都可以被替換以保證靈活性。
實(shí)現(xiàn)原理:
Tomcat是運(yùn)行在JVM中的一個(gè)進(jìn)程。它定義為“中間件”,顧名思義是一個(gè)在Java項(xiàng)目與JVM之間的中間容
器。
Web項(xiàng)目的本質(zhì),是一大堆的資源文件和方法。Web項(xiàng)目沒有入口方法(即main方法),這意味著
Web項(xiàng)目中的方法不會(huì)自動(dòng)運(yùn)行起來。Web項(xiàng)目部署進(jìn)Tomcat的webapp中的目的是很明確的,那就是
希望Tomcat去調(diào)用寫好的方法去為客戶端返回需要的資源和數(shù)據(jù)。
Tomcat可以運(yùn)行起來,并調(diào)用寫好的方法。那么,Tomcat有一個(gè)main方法。對(duì)于Tomcat而言,它并
不知道用戶會(huì)有什么樣的方法,這些都只是在項(xiàng)目被部署進(jìn)webapp下后才確定的。由此,可知Tomcat
用到了Java的反射來實(shí)現(xiàn)類的動(dòng)態(tài)加載、實(shí)例化、獲取方法、調(diào)用方法。但是部署到Tomcat的中的Web
項(xiàng)目必須是按照規(guī)定好的接口來進(jìn)行編寫,以便進(jìn)行調(diào)用。
優(yōu)化:
修改內(nèi)存的相關(guān)配置
修改TOMCAT_HOME/bin/catalina.sh,在其中加入,也可以放在 CLASSPATH=下面, 加一些
對(duì)內(nèi)存調(diào)優(yōu)的參數(shù)
優(yōu)化連接器Connector
Connector是連接器,負(fù)責(zé)接收客戶的請(qǐng)求,以及向客戶端回送響應(yīng)的消息。所以 Connector
的優(yōu)化是重要部分。默認(rèn)情況下 Tomcat只支持200線程訪問,超過這個(gè)數(shù)量的連接將被等待
甚至超時(shí)放棄,所以我們需要提高這方面的處理能力。
在TOMCAT_HOME/conf/server.xml添加最大線程數(shù)量和最小空閑線程數(shù),請(qǐng)求的數(shù)量
配置線程池
Executor代表了一個(gè)線程池,可以在Tomcat組件之間共享。使用線程池的好處在于減少了創(chuàng)
建銷毀線程的相關(guān)消耗,而且可以提高線程的使用效率。 - get 和 post 請(qǐng)求有哪些區(qū)別?
GET在瀏覽器回退時(shí)是無害的,而POST會(huì)再次提交請(qǐng)求。
GET請(qǐng)求會(huì)被瀏覽器主動(dòng)cache,而POST不會(huì),除非手動(dòng)設(shè)置。
GET請(qǐng)求只能進(jìn)行url編碼,而POST支持多種編碼方式。
GET請(qǐng)求參數(shù)會(huì)被完整保留在瀏覽器歷史記錄里,而POST中的參數(shù)不會(huì)被保留。
GET請(qǐng)求在URL中傳送的參數(shù)是有長(zhǎng)度限制的,而POST沒有。
參數(shù)的數(shù)據(jù)類型,GET只接受ASCII字符,而POST沒有限制。
GET比POST更不安全,因?yàn)閰?shù)直接暴露在URL上,所以不能用來傳遞敏感信息。
GET參數(shù)通過URL傳遞,POST放在Request body中
七、MySQL以及SQL面試題【20道】
- 說一說什么是數(shù)據(jù)庫(kù)事務(wù)!
數(shù)據(jù)庫(kù)事務(wù)就是在一套業(yè)務(wù)操作的多條sql語(yǔ)句執(zhí)行中要么全成功, 要么全失敗. 保證了數(shù)據(jù)的一致性.
事務(wù)的四個(gè)屬性:原子性,一致性,隔離性,持久性。
原子性:在事務(wù)中進(jìn)行的修改,要么全部執(zhí)行,要么全不執(zhí)行。如果在事務(wù)完成之前系統(tǒng)出現(xiàn)故
障,SQLServer會(huì)撤銷在事務(wù)中的修改。
一致性:為了事務(wù)在查詢和修改時(shí)數(shù)據(jù)不發(fā)生沖突。
隔離性:隔離性是一種用于控制數(shù)據(jù)訪問的機(jī)制,能夠確保事務(wù)只能訪問處于期望的一致性級(jí)別下
的數(shù)據(jù)。SQLServer使用鎖對(duì)各個(gè)事務(wù)之間正在修改和查詢的數(shù)據(jù)進(jìn)行隔離。
持久性:在將數(shù)據(jù)修改寫入到磁盤之前,總是先把這些修改寫入到事務(wù)日志中。這樣子,即使數(shù)據(jù)
還沒有寫入到磁盤中,也可以認(rèn)為事務(wù)是持久化的。這是如果系統(tǒng)重新啟動(dòng),SQL Server也會(huì)檢查
數(shù)據(jù)庫(kù)日志,進(jìn)行恢復(fù)處理。
隔離級(jí)別:讀未提交、讀已提交、可重復(fù)讀、串行化 - 事務(wù)并發(fā)產(chǎn)生的問題和隔離級(jí)別!
事務(wù)并發(fā)產(chǎn)生的問題:
臟讀:一個(gè)事務(wù)讀取另一個(gè)事務(wù)的未提交數(shù)據(jù)! 真錯(cuò)誤!read UNCOMMITTED;
不可重復(fù)讀:一個(gè)事務(wù)讀取;另一個(gè)事務(wù)的提交的修改數(shù)據(jù)!read committed;
虛讀()幻讀:一個(gè)事務(wù)讀取了另一個(gè)數(shù)的提交的插入數(shù)據(jù)!repeatable read
隔離級(jí)別:讀未提交、讀已提交、可重復(fù)讀、串行化
隔離級(jí)別選擇:
數(shù)據(jù)庫(kù)隔離級(jí)別越高!數(shù)據(jù)越安全,性能越低!
數(shù)據(jù)庫(kù)隔離級(jí)別越低!數(shù)據(jù)越不安全,性能越高
建議:設(shè)置為第二個(gè)隔離級(jí)別 read committed; - Spring中事務(wù)的傳播特性有哪些?
七種傳播特性:
propagation_requierd:如果當(dāng)前沒有事務(wù),就新建一個(gè)事務(wù),如果已存在一個(gè)事務(wù)中,加入到這個(gè)事
務(wù)中,這是最常見的選擇。
propagation_supports:支持當(dāng)前事務(wù),如果沒有當(dāng)前事務(wù),就以非事務(wù)方法執(zhí)行。
propagation_mandatory:使用當(dāng)前事務(wù),如果沒有當(dāng)前事務(wù),就拋出異常。
propagation_required_new:新建事務(wù),如果當(dāng)前存在事務(wù),把當(dāng)前事務(wù)掛起。
propagation_not_supported:以非事務(wù)方式執(zhí)行操作,如果當(dāng)前存在事務(wù),就把當(dāng)前事務(wù)掛起。
propagation_never:以非事務(wù)方式執(zhí)行操作,如果當(dāng)前事務(wù)存在則拋出異常。
propagation_nested:如果有事務(wù)在運(yùn)行,當(dāng)前的方法就應(yīng)該在這個(gè)事務(wù)的嵌套事務(wù)內(nèi)運(yùn)行,否則就啟
動(dòng)一個(gè)新的事務(wù),并在它自己的事務(wù)內(nèi)運(yùn)行。
Spring 默認(rèn)的事務(wù)傳播行為是 PROPAGATION_REQUIRED - MySQL的內(nèi)部有哪幾部分組成?
連接器
MYSQL擁有非常多的客戶端比如:navicat,jdbc,SQLyog等客戶端,這些客戶端都需要向
MYSQL通信都必須要建立連接,這個(gè)建立連接的工作就是由連接器完成的
查詢緩存(5.8版本開始取消了這個(gè))
連接建立之后,你就可以執(zhí)行select語(yǔ)句了 這個(gè)時(shí)候首先會(huì)去查詢下緩存,緩存的存儲(chǔ)形式類似于
key-value,key保存的是sql語(yǔ)句value 保存的是結(jié)果集,如果查詢不在緩存中就會(huì)執(zhí)行sql語(yǔ)句執(zhí)
行完成后把執(zhí)行結(jié)果放入緩存中,如果是能在緩存中查詢到那么直接返回
詞法分析器
解析sql語(yǔ)句中的詞法, 語(yǔ)法
優(yōu)化器
對(duì)解析后生成的執(zhí)行計(jì)劃進(jìn)行自動(dòng)優(yōu)化, 提升查詢效率
執(zhí)行器
執(zhí)行sql語(yǔ)句分析后生成的執(zhí)行計(jì)劃, 執(zhí)行后返回結(jié)果 - MySQL如何實(shí)現(xiàn)樂觀鎖和悲觀鎖
①樂觀鎖實(shí)現(xiàn):版本號(hào)控制及時(shí)間戳控制。
版本號(hào)控制:表中加一個(gè) version 字段;當(dāng)讀取數(shù)據(jù)時(shí),連同這個(gè) version 字段一起讀出;數(shù)據(jù)每
更新一次就將此值加一;當(dāng)提交更新時(shí),判斷數(shù)據(jù)庫(kù)表中對(duì)應(yīng)記錄的當(dāng)前版本號(hào)是否與之前取出來
的版本號(hào)一致,如果一致則可以直接更新,如果不一致則表示是過期數(shù)據(jù)需要重試或者做其它操
作。
時(shí)間戳控制:其原理和版本號(hào)控制差不多,也是在表中添加一個(gè) timestamp 的時(shí)間戳字段,然后
提交更新時(shí)判斷數(shù)據(jù)庫(kù)中對(duì)應(yīng)記錄的當(dāng)前時(shí)間戳是否與之前取出來的時(shí)間戳一致,一致就更新,不
一致就重試。
②悲觀鎖實(shí)現(xiàn):必須關(guān)閉mysql數(shù)據(jù)庫(kù)的自動(dòng)提交屬性,開啟事務(wù),再進(jìn)行數(shù)據(jù)庫(kù)操作,最后提交事
務(wù)。 - MySQL常用的存儲(chǔ)引擎及區(qū)別
InnoDB默認(rèn)的存儲(chǔ)引擎, 不需要任何配置, 默認(rèn)使用的就是它, 支持事務(wù), 支持主外鍵連接, 速度一般
MyISAM不支持事務(wù), 速度快, 以讀寫插入為主的應(yīng)用程序,比如博客系統(tǒng)、新聞門戶網(wǎng)站。
Memory存儲(chǔ)的數(shù)據(jù)都放在內(nèi)存中, 服務(wù)器斷電, 數(shù)據(jù)丟失, 速度最快, 現(xiàn)基本被redis取代. - 說一說對(duì)MySQL索引的理解
什么是數(shù)據(jù)庫(kù)索引?
數(shù)據(jù)庫(kù)索引,是數(shù)據(jù)庫(kù)管理系統(tǒng)中一個(gè)排序的數(shù)據(jù)結(jié)構(gòu),索引實(shí)現(xiàn)通常使用B樹及變種的B+樹。在
數(shù)據(jù)之外,數(shù)據(jù)庫(kù)系統(tǒng)還維護(hù)著滿足特定查找算法的數(shù)據(jù)結(jié)構(gòu),這些數(shù)據(jù)結(jié)構(gòu)以某種方式引用(指
向)數(shù)據(jù),這樣就可以在這些數(shù)據(jù)結(jié)構(gòu)上實(shí)現(xiàn)高級(jí)查找算法。這種數(shù)據(jù)結(jié)構(gòu)就是索引。
索引的作用?
協(xié)助快速查詢、更新數(shù)據(jù)庫(kù)表中數(shù)據(jù)。
副作用:
增加了數(shù)據(jù)庫(kù)的存儲(chǔ)空間插入和修改數(shù)據(jù)時(shí)要花費(fèi)較多的時(shí)間(因?yàn)樗饕矔?huì)隨之變動(dòng)) - MySQL中什么字段適合添加索引
表的主鍵、外鍵必須有索引;
經(jīng)常與其他表進(jìn)行連接的表,在連接字段上應(yīng)該建立索引;
經(jīng)常出現(xiàn)在Where子句中的字段,特別是大表的字段,應(yīng)該建立索引;
索引應(yīng)該建在選擇性高的字段上;
索引應(yīng)該建在小字段上,對(duì)于大的文本字段甚至超長(zhǎng)字段,不要建索引;
復(fù)合索引的建立需要進(jìn)行仔細(xì)分析;盡量考慮用單字段索引代替: - MySQL中常見的索引類型
普通索引:是最基本的索引,它沒有任何限制
唯一索引:與前面的普通索引類似,不同的就是:索引列的值必須唯一,但允許有空值。如果是組合索
引,則列值的組合必須唯一
主鍵索引:是一種特殊的唯一索引,一個(gè)表只能有一個(gè)主鍵,不允許有空值。一般是在建表的時(shí)候同時(shí)
創(chuàng)建主鍵索引
組合索引:指多個(gè)字段上創(chuàng)建的索引,只有在查詢條件中使用了創(chuàng)建索引時(shí)的第一個(gè)字段,索引才會(huì)被
使用。使用組合索引時(shí)遵循最左前綴集合 - MySQL中索引失效的場(chǎng)景!
WHERE字句的查詢條件里有不等于號(hào)(WHERE column!=…),MYSQL將無法使用索引,類似地,如果
WHERE字句的查詢條件里使用了函數(shù)(如:WHERE DAY(column)=…),MYSQL將無法使用索引
在JOIN操作中(需要從多個(gè)數(shù)據(jù)表提取數(shù)據(jù)時(shí)),MYSQL只有在主鍵和外鍵的數(shù)據(jù)類型相同時(shí)才能使用
索引,否則即使建立了索引也不會(huì)使用
如果WHERE子句的查詢條件里使用了比較操作符LIKE和REGEXP,MYSQL只有在搜索模板的第一個(gè)字符
不是通配符的情況下才能使用索引。比如說,如果查詢條件是LIKE ‘a(chǎn)bc%’,MYSQL將使用索引;如果條
件是LIKE ‘%abc’,MYSQL將不使用索引。 - 說一說Hash和B+樹結(jié)構(gòu)索引區(qū)別?
hash索引 : 等值查詢效率高,不能排序,不能進(jìn)行范圍查詢;Hash索引僅僅能滿足"=",“IN"和”<=>"查
詢,不能使用范圍查詢
B+樹索引 : 數(shù)據(jù)有序,支持范圍查詢, 查詢效率沒有hash索引高. - B+比B樹索引的區(qū)別?
B樹,每個(gè)節(jié)點(diǎn)都儲(chǔ)存key和dta,所有節(jié)點(diǎn)組成這棵樹,并且葉子節(jié)點(diǎn)指針為null,葉子節(jié)點(diǎn)不包含任何
關(guān)鍵字的信息。
B+樹,所有葉子節(jié)點(diǎn)中包含了全部關(guān)鍵字的信息,及指向含有這些關(guān)鍵字記錄的指針,且葉子節(jié)點(diǎn)本身
依關(guān)鍵字的大小自小爾達(dá)的順序鏈接,所有的非終端節(jié)點(diǎn)可以看成是索引部分,節(jié)點(diǎn)中僅含有其子樹根
節(jié)點(diǎn)中最大的(或最小的)關(guān)鍵字。 - 聚集(集中)索引和非聚集(稀疏)索引的區(qū)別
【聚集索引】:也稱 Clustered Index。是指關(guān)系表記錄的物理順序與索引的邏輯順序相同。由于一張表
只能按照一種物理順序存放,一張表最多也只能存在一個(gè)聚集索引。與非聚集索引相比,聚集索引有著
更快的檢索速度。
MySQL 里只有 INNODB 表支持聚集索引,INNODB 表數(shù)據(jù)本身就是聚集索引,也就是常說 IOT,索引
組織表。非葉子節(jié)點(diǎn)按照主鍵順序存放,葉子節(jié)點(diǎn)存放主鍵以及對(duì)應(yīng)的行記錄。所以對(duì) INNODB 表進(jìn)行
全表順序掃描會(huì)非???。
【非聚集索引】:也叫 Secondary Index。指的是非葉子節(jié)點(diǎn)按照索引的鍵值順序存放,葉子節(jié)點(diǎn)存放
索引鍵值以及對(duì)應(yīng)的主鍵鍵值。MySQL 里除了 INNODB 表主鍵外,其他的都是二級(jí)索引。MYISAM,
memory 等引擎的表索引都是非聚集索引。簡(jiǎn)單點(diǎn)說,就是索引與行數(shù)據(jù)分開存儲(chǔ)。一張表可以有多個(gè)
二級(jí)索引。 - 什么是索引倒排!
倒排索引源于實(shí)際應(yīng)用中需要根據(jù)屬性的值來查找記錄。這種索引表中的每一項(xiàng)都包括一個(gè)屬性值和具
有該屬性值的各記錄的地址。由于不是由記錄來確定屬性值,而是由屬性值來確定記錄的位置,因而稱
為倒排索引(inverted index)。帶有倒排索引的文件我們稱為倒排索引文件,簡(jiǎn)稱倒排文件(inverted
file)。 - 如何快速的復(fù)制一張表!
將表結(jié)構(gòu)和數(shù)據(jù)導(dǎo)出成sql腳本文件, 可以根據(jù)要求修改腳本中的表名字防止重名, 然后再導(dǎo)入到mysql中
在庫(kù)中創(chuàng)建新表后, 使用insert into 表名(字段名) select 字段名 from 表名 - SQL語(yǔ)句優(yōu)化的方案?
第一, 開啟慢查詢?nèi)罩?讓系統(tǒng)運(yùn)行一段時(shí)間, 打開慢查詢?nèi)罩菊业骄唧w需要優(yōu)化的sql語(yǔ)句
第二, 使用explain執(zhí)行計(jì)劃分析sql語(yǔ)句中問題出現(xiàn)的哪里
第三, 根據(jù)sql語(yǔ)句編寫規(guī)范, 進(jìn)行優(yōu)化, 例如: 不要使用like模糊查詢, 因?yàn)樗饕龝?huì)失效; 盡量避免使用
select*, 因?yàn)闀?huì)返回?zé)o用字段; 條件中不要使用or關(guān)鍵字, 因?yàn)闀?huì)全表掃描等. - 組合索引的最左原則?
MySQL 的聯(lián)合索引會(huì)首先根據(jù)聯(lián)合索引中最左邊的、也就是第一個(gè)字段進(jìn)行排序,在第一個(gè)字段排序的
基礎(chǔ)上,再對(duì)聯(lián)合索引中后面的第二個(gè)字段進(jìn)行排序,依此類推。聯(lián)合索引當(dāng)遇到范圍查詢(>、<、
between、like)就會(huì)停止匹配,建立索引時(shí)匹配度高的字段在前,匹配度低的字段在后
舉例 : a = 1 and b = 2 and c > 3 and d = 4 如果建立(a,b,c,d)順序的索引,d是用不到索引的,如果建立
(a,b,d,c)的索引則都可以用到,a,b,d的順序可以任意調(diào)整。
=和in可以亂序,比如a = 1 and b = 2 and c = 3 建立(a,b,c)索引可以任意順序,mysql的查詢優(yōu)化器會(huì)幫
你優(yōu)化成索引可以識(shí)別的形式 - 什么是行鎖, 表鎖, 頁(yè)鎖?
行鎖 : 開銷大,加鎖慢;會(huì)出現(xiàn)死鎖;鎖定粒度小,發(fā)生鎖沖突的概率低
行鎖就是針對(duì)數(shù)據(jù)表中行記錄的鎖。這很好理解,比如事務(wù)A更新了一行,而這時(shí)候事務(wù)B也要更新
同一行,則必須等事務(wù)A的操作完成后才能進(jìn)行更新。
表鎖 : 開銷小,加鎖快;不會(huì)出現(xiàn)死鎖;鎖定粒度大,發(fā)生鎖沖突概率高
表鎖的語(yǔ)法是 lock tables … read/write??梢杂胾nlock tables主動(dòng)釋放鎖,也可以在客戶端斷開
的時(shí)候自動(dòng)釋放。需要注意,lock tables語(yǔ)法除了會(huì)限制別的線程的讀寫外,也限定了本線程接下
來的操作對(duì)象。
頁(yè)級(jí)鎖
頁(yè)級(jí)鎖是MySQL中鎖定粒度介于行級(jí)鎖和表級(jí)鎖中間的一種鎖。表級(jí)鎖速度快,但沖突多,行級(jí)沖
突少,但速度慢。所以取了折衷的頁(yè)級(jí),一次鎖定相鄰的一組記錄。
特點(diǎn):開銷和加鎖時(shí)間界于表鎖和行鎖之間;會(huì)出現(xiàn)死鎖;鎖定粒度界于表鎖和行鎖之間,并發(fā)度
一般 - 數(shù)據(jù)庫(kù)的三范式是什么
第一范式:1NF 是對(duì)屬性的原子性約束,強(qiáng)調(diào)的引擎是列的原子性,即數(shù)據(jù)庫(kù)表的每一列都是不可分割
的原子數(shù)據(jù)項(xiàng)。
第二范式:2NF 是對(duì)記錄的惟一性約束,要求實(shí)體的屬性完全依賴于主關(guān)鍵字。所謂完全依賴是指不能
存在僅依賴主關(guān)鍵字一部分的屬性。
第三范式:3NF 是對(duì)字段冗余性的約束,任何非主屬性不依賴于其它非主屬性。即任何字段不能由其他
字段派生出來, 它要求字段沒有冗余。 - 什么情況下設(shè)置了索引但無法使用
以“%”開頭的 LIKE 語(yǔ)句,模糊匹配
OR 語(yǔ)句前后沒有同時(shí)使用索引
數(shù)據(jù)類型出現(xiàn)隱式轉(zhuǎn)化(如 varchar 不加單引號(hào)的話可能會(huì)自動(dòng)轉(zhuǎn)換為 int 型)
八、常用框架【19道】
- MyBatis$和#的區(qū)別
#{ }可以防止Sql 注入,它會(huì)將所有傳入的參數(shù)作為一個(gè)字符串來處理。
Mybatis在處理#{}時(shí),會(huì)將SQL語(yǔ)句中的#{}替換為?號(hào),調(diào)用PrepaerdStatement的set方法來賦值。
$ {} 則將傳入的參數(shù)拼接到Sql上去執(zhí)行,一般用于表名和字段名參數(shù),$ 所對(duì)應(yīng)的參數(shù)應(yīng)該由服務(wù)器端
提供,前端可以用參數(shù)進(jìn)行選擇,避免 Sql 注入的風(fēng)險(xiǎn)
Mybatis在處理 時(shí),就是把 {}時(shí),就是把 時(shí),就是把{}換成變量的值。 - MyBatis中一級(jí)緩存和二級(jí)緩存的區(qū)別
一級(jí)緩存 :
一級(jí)緩存是基于sqlsession默認(rèn)開啟的,在操作數(shù)據(jù)庫(kù)時(shí)需要構(gòu)造SqlSession對(duì)象,在對(duì)象中有一
個(gè)HashMap用于存儲(chǔ)緩存數(shù)據(jù)。不同的SqlSession之間的緩存數(shù)據(jù)區(qū)域是互相不影響的。
一級(jí)緩存作用是sqlsession范圍的,在同一個(gè)sqlsession中執(zhí)行兩次相同的sql時(shí),第一次得到的數(shù)
據(jù)會(huì)緩存放在內(nèi)存中,第二次不再去數(shù)據(jù)庫(kù)獲取,而是直接在緩存中獲取,提高效率。
如果執(zhí)行了增刪改并提交到數(shù)據(jù)庫(kù),mybatis是會(huì)把sqlsession中的一級(jí)緩存清空的,這樣是為了
數(shù)據(jù)的準(zhǔn)確性,避免臟讀現(xiàn)象。
二級(jí)緩存 :
二級(jí)緩存是基于mapper的namespace作用域,但多個(gè)sqlsession操作同一個(gè)namespace下的sql
時(shí),并且傳入的參數(shù)也相同,執(zhí)行相同的sql語(yǔ)句,第一次執(zhí)行完畢后會(huì)將數(shù)據(jù)緩存,這就是二級(jí)
緩存。
二級(jí)緩存同樣是使用HashMap進(jìn)行數(shù)據(jù)存儲(chǔ)。相比一級(jí)緩存SqlSession,二級(jí)緩存的范圍更大,
多個(gè)Sqlsession可以共用二級(jí)緩存,二級(jí)緩存它是可以跨越多個(gè)sqlsession的。 - MyBatis和數(shù)據(jù)庫(kù)交互的原理
詳細(xì)流程如下:
加載mybatis全局配置文件(數(shù)據(jù)源、mapper映射文件等),解析配置文件,MyBatis基于XML配置文
件生成Configuration,和一個(gè)個(gè)MappedStatement(包括了參數(shù)映射配置、動(dòng)態(tài)SQL語(yǔ)句、結(jié)果映射
配置),其對(duì)應(yīng)著<select | update | delete | insert>標(biāo)簽項(xiàng)。
SqlSessionFactoryBuilder通過Configuration對(duì)象生成SqlSessionFactory,用來開啟SqlSession。
SqlSession對(duì)象完成和數(shù)據(jù)庫(kù)的交互, 通過Executor執(zhí)行器將MappedStatement對(duì)象進(jìn)行解析,最后通
過JDBC執(zhí)行sql。
借助MappedStatement中的結(jié)果映射關(guān)系,將返回結(jié)果轉(zhuǎn)化成HashMap、JavaBean等存儲(chǔ)結(jié)構(gòu)并返
回。 - Spring的IOC是什么
SpringIOC是控制反轉(zhuǎn), 負(fù)責(zé)創(chuàng)建對(duì)象,管理對(duì)象,裝配對(duì)象,配置對(duì)象,并且管理對(duì)象的整個(gè)生命周期,
也就是以前創(chuàng)建對(duì)象需要new, 那么在使用了spring的IOC后, 所有這樣的工作都交給spring進(jìn)行管理. - Spring的常用注解
@Autowired 用于自動(dòng)注入bean
@Resource 根據(jù)名字注入bean
@Controller 用于聲明控制層類
@Repository 用于聲明Dao持久層類
@Service 用于聲明Service業(yè)務(wù)層類
@Component 用于聲明一個(gè)普通JavaBean
@Configuration 用于聲明初始化類相當(dāng)于spring核心配置文件的標(biāo)簽
@Bean 聲明一個(gè)配置, 相當(dāng)于spring核心配置文件的標(biāo)簽 - Spring的IOC中創(chuàng)建對(duì)象的整體流程
通過ClassPathXmlApplicationContext對(duì)象加載spring核心配置文件創(chuàng)建ApplicationContext對(duì)象
通過applicationContext對(duì)象的getBean(beanName)方法創(chuàng)建所需類對(duì)象
轉(zhuǎn)換beanName,因?yàn)閭魅氲膮?shù)可能是別名,也可能是FactoryBean,所以需要解析
如果是beanName,不需要處理
如果是 “&beanName”,則去掉&
如果是別名,則轉(zhuǎn)換為beanName - Spring的Bean的三級(jí)緩存
一級(jí)緩存singletonObjects是完整的bean,它可以被外界任意使用,并且不會(huì)有歧義。
二級(jí)緩存earlySingletonObjects是不完整的bean,沒有完成初始化,它與singletonObjects的分離主要
是職責(zé)的分離以及邊界劃分,可以試想一個(gè)Map緩存里既有完整可使用的bean,也有不完整的,只能持
有引用的bean,在復(fù)雜度很高的架構(gòu)中,很容易出現(xiàn)歧義,并帶來一些不可預(yù)知的錯(cuò)誤。
三級(jí)緩存singletonFactories,其職責(zé)就是包裝一個(gè)bean,有回調(diào)邏輯,所以它的作用非常清晰,并且
只能處于第三層。
在實(shí)際使用中,要獲取一個(gè)bean,先從一級(jí)緩存一直查找到三級(jí)緩存,緩存bean的時(shí)候是從三級(jí)到一
級(jí)的順序保存,并且緩存bean的過程中,三個(gè)緩存都是互斥的,只會(huì)保持bean在一個(gè)緩存中,而且,
最終都會(huì)在一級(jí)緩存中。 - Spring如何處理循環(huán)依賴
構(gòu)造器參數(shù)循環(huán)依賴 :
通過構(gòu)造器注入構(gòu)成的循環(huán)依賴,此依賴是無法解決的,只能拋出BeanCurrentlyIn
CreationException異常表示循環(huán)依賴。
如在創(chuàng)建TestA類時(shí),構(gòu)造器需要TestB類,那將去創(chuàng)建TestB,在創(chuàng)建TestB類時(shí)又發(fā)現(xiàn)需要TestC
類,則又去創(chuàng)建TestC,最終在創(chuàng)建TestC時(shí)發(fā)現(xiàn)又需要TestA,從而形成一個(gè)環(huán),沒辦法創(chuàng)建。
Spring容器會(huì)將每一個(gè)正在創(chuàng)建的Bean 標(biāo)識(shí)符放在一個(gè)“當(dāng)前創(chuàng)建Bean池”中,Bean標(biāo)識(shí)符在創(chuàng)建
過程中將一直保持在這個(gè)池中,因此如果在創(chuàng)建Bean過程中發(fā)現(xiàn)自己已經(jīng)在“當(dāng)前創(chuàng)建Bean池”里
時(shí)將拋出BeanCurrentlyInCreationException異常表示循環(huán)依賴;而對(duì)于創(chuàng)建完畢的Bean將從“當(dāng)
前創(chuàng)建Bean池”中清除掉。
setter方式單例,默認(rèn)方式 :
Spring先是用構(gòu)造實(shí)例化Bean對(duì)象 ,此時(shí)Spring會(huì)將這個(gè)實(shí)例化結(jié)束的對(duì)象放到一個(gè)Map中,并
且Spring提供了獲取這個(gè)未設(shè)置屬性的實(shí)例化對(duì)象引用的方法。當(dāng)Spring實(shí)例化了StudentA、
StudentB、StudentC后,緊接著會(huì)去設(shè)置對(duì)象的屬性,此時(shí)StudentA依賴StudentB,就會(huì)去Map
中取出存在里面的單例StudentB對(duì)象,以此類推,不會(huì)出來循環(huán)的問題。
prototype作用域bean的循環(huán)依賴 :
這種循環(huán)依賴同樣無法解決,因?yàn)閟pring不會(huì)緩存prototype作用域的bean,而spring中循環(huán)依賴
的解決方式正是通過緩存來實(shí)現(xiàn)的。
Spring解決這個(gè)問題主要靠巧妙的三層緩存,Spring首先從singleTonObjects(一級(jí)緩存)中嘗試
獲取,如果獲取不到并且對(duì)象在創(chuàng)建中,則嘗試從earlySingleTonObjects(二級(jí)緩存)中獲取,
如果還是獲取不到并且允許從singleTonFactories通過getObject獲取,則通過
singleTonFactory.getObject()(三級(jí)緩存)獲取。
如果獲取到了則移除相對(duì)應(yīng)的singleTonFactory,將singleTonObject放入到
earlySingleTonObjects,其實(shí)就是將三級(jí)緩存提升到二級(jí)緩存,這個(gè)就是緩存升級(jí)。Spring在進(jìn)行
對(duì)象創(chuàng)建的時(shí)候,會(huì)依次從一級(jí)、二級(jí)、三級(jí)緩存中尋找對(duì)象,如果找到直接返回。
拓展:
一級(jí)緩存:singletonObjects,存放完全實(shí)例化屬性賦值完成的單例對(duì)象的cache,直接可以使
用。
二級(jí)緩存:earlySingletonObjects,存放提前曝光的單例對(duì)象的cache,尚未進(jìn)行屬性封裝的
Bean。
三級(jí)緩存:singletonFactories,存放單例對(duì)象工廠的cache。 - @Autowired和@Resource的區(qū)別
@Autowired是自動(dòng)注入, 一個(gè)接口只有一個(gè)實(shí)現(xiàn)類的時(shí)候使用
@Resource是根據(jù)bean的名字注入, 一個(gè)接口有多個(gè)實(shí)現(xiàn)類的時(shí)候使用, @Resource(實(shí)現(xiàn)類bean名字)
根據(jù)實(shí)現(xiàn)類的名字注入指定實(shí)現(xiàn)類. - SpringMVC的執(zhí)行流程
- Spring和SpringMVC的父子工廠關(guān)系
Spring容器是父容器,包含的對(duì)象有dao,service等
Springmvc是子容器,包括controller
子容器可以訪問父容器的對(duì)象
父容器不能訪問子容器的對(duì)象 - 闡述SpringBoot啟動(dòng)類中注解
@SpringBootAplication:這個(gè)注解是啟動(dòng)springboot,從源碼我們會(huì)發(fā)現(xiàn)這個(gè)注解被
@Configuration、@EnableAutoConfiguration、 @ComponentScan三個(gè)注解修飾。換言之,
springboot提供了統(tǒng)一的注解來替代這三個(gè)注解。
@EnableEurekaClient:這個(gè)注解是必須的,表示注冊(cè)到某個(gè)Eureka服務(wù)(注冊(cè)中心)中,相當(dāng)于給這個(gè)
服務(wù)加了一個(gè)通行證,通行證的具體的內(nèi)容需要在application.yml中配置。一般配置里面會(huì)有:
eureka、client、serviceurl、defaultZone:http://,代表注冊(cè)到上面的注冊(cè)中心,這個(gè)注冊(cè)中心里面
的服務(wù)包括所有的接口都可以通過協(xié)商對(duì)方暴露的接口之后直接按照規(guī)則進(jìn)行調(diào)用
@MapperScan:這個(gè)注解是用來掃描到dao的范圍的。如果使用的是mybatis的話,需要通過配置文件
來之指定Mapper和主要的配置文件的位置。
@EnableRedisHttpSession:這個(gè)注解是用來獲取session中緩存的內(nèi)容, 還需要配合配置文件來完
成。 - SpringBoot如何實(shí)現(xiàn)的自動(dòng)裝配
Spring Boot 通過@EnableAutoConfiguration開啟自動(dòng)裝配,通過 SpringFactoriesLoader 最終加載
META-INF/spring.factories中的自動(dòng)配置類實(shí)現(xiàn)自動(dòng)裝配,自動(dòng)配置類其實(shí)就是通過@Conditional按需
加載的配置類,想要其生效必須引入spring-boot-starter-xxx包實(shí)現(xiàn)起步依賴 - Spring事務(wù)的傳播行為?
事務(wù)傳播行為(propagation behavior)指的就是當(dāng)一個(gè)事務(wù)方法被另一個(gè)事務(wù)方法調(diào)用時(shí),這個(gè)事務(wù)
方法應(yīng)該如何進(jìn)行。是為自己開啟一個(gè)新事務(wù)運(yùn)行,還是由原來的事務(wù)執(zhí)行, 這是由傳播行為決定的 - Spring中AOP底層實(shí)現(xiàn)原理
AOP底層實(shí)現(xiàn)使用了動(dòng)態(tài)代理
在spring中有兩種實(shí)現(xiàn)方式, 一種是JDK動(dòng)態(tài)代理, 一種是CJlib動(dòng)態(tài)代理
JDK動(dòng)態(tài)代理針對(duì)實(shí)現(xiàn)了接口的類使用
CJlib動(dòng)態(tài)代理針對(duì)只有類的時(shí)候使用 - Spring中ApplicationContext對(duì)象和BeanFactory對(duì)象區(qū)別?
這兩個(gè)對(duì)象都可以創(chuàng)建JavaBean對(duì)象
ApplicationContext對(duì)象 : 這個(gè)對(duì)象底層是通過BeanFactory對(duì)象實(shí)現(xiàn)的, 但是ApplicationContext創(chuàng)建
JavaBean的時(shí)機(jī)是調(diào)用了這個(gè)對(duì)象的getBean()方法, 就會(huì)立即創(chuàng)建JavaBean
BeanFactory對(duì)象 : 它創(chuàng)建JavaBean的時(shí)機(jī)是調(diào)用getBean方法不進(jìn)行創(chuàng)建JavaBean而是等到用到了
JavaBean的時(shí)候才去創(chuàng)建. - Spring中的注入方式有哪些
常用的注入方式主要有三種:
構(gòu)造方法注入 : 構(gòu)造方法注入會(huì)有不支持的情況發(fā)生,因?yàn)樵谡{(diào)用構(gòu)造方法中必須傳入正確的構(gòu)造
參數(shù),否則報(bào)錯(cuò)。
setter注入 : 注入支持大部分的依賴注入
基于注解的注入 : 可以在類中使用@Autowired或是@Resource注解進(jìn)行注入.
Spring中Bean的生命周期 - Spring 支持幾種 bean 的作用域
支持如下5種作用域:
singleton(默認(rèn)的):單例模式,在整個(gè)Spring IoC容器中,使用singleton定義的Bean將只有一個(gè)實(shí)
例
prototype:原型模式,每次通過容器的getBean方法獲取prototype定義的Bean時(shí),都將產(chǎn)生一
個(gè)新的Bean實(shí)例
request:對(duì)于每次HTTP請(qǐng)求,使用request定義的Bean都將產(chǎn)生一個(gè)新實(shí)例,即每次HTTP請(qǐng)求
將會(huì)產(chǎn)生不同的Bean實(shí)例。只有在Web應(yīng)用中使用Spring時(shí),該作用域才有效
session:對(duì)于每次HTTP Session,使用session定義的Bean豆?jié){產(chǎn)生一個(gè)新實(shí)例。同樣只有在
Web應(yīng)用中使用Spring時(shí),該作用域才有效
globalsession:每個(gè)全局的HTTP Session,使用session定義的Bean都將產(chǎn)生一個(gè)新實(shí)例。典型
情況下,僅在使用portlet context的時(shí)候有效。同樣只有在Web應(yīng)用中使用Spring時(shí),該作用域才
有效 - Spring中Bean的生命周期
九、中間件和分布式 【54道】
- Nginx的應(yīng)用場(chǎng)景有哪些?
負(fù)載均衡, 反向代理, 靜態(tài)資源服務(wù)器 - Nginx負(fù)載均衡的策略有哪些?
輪詢(默認(rèn)), 指定權(quán)重, IP hash - Nginx的進(jìn)程模型 ?
nginx模型有兩種進(jìn)程,master進(jìn)程(相當(dāng)于管理進(jìn)程)和worker進(jìn)程(實(shí)際工作進(jìn)程)。
master進(jìn)程主要用來管理worker進(jìn)程,管理包含:接收來自外界的信號(hào),向各worker進(jìn)程發(fā)送信號(hào),
監(jiān)控worker進(jìn)程的運(yùn)行狀態(tài),當(dāng)worker進(jìn)程退出后(異常情況下),會(huì)自動(dòng)重新啟動(dòng)新的worker進(jìn)程。
而基本的網(wǎng)絡(luò)事件,則是放在worker進(jìn)程中來處理了。多個(gè)worker進(jìn)程之間是對(duì)等的,他們同等競(jìng)爭(zhēng)來
自客戶端的請(qǐng)求,各進(jìn)程互相之間是獨(dú)立的。一個(gè)請(qǐng)求,只可能在一個(gè)worker進(jìn)程中處理,一個(gè)worker
進(jìn)程,不可能處理其它進(jìn)程的請(qǐng)求。worker進(jìn)程的個(gè)數(shù)是可以設(shè)置的,一般我們會(huì)設(shè)置與機(jī)器cpu核數(shù)
一致,這里面的原因與nginx的進(jìn)程模型以及事件處理模型是分不開的。 - Nginx常用命令!
啟動(dòng) ./nginx
關(guān)閉 ./nginx -s stop
查看nginx進(jìn)程 ps -ef | grep nginx
重新加載nginx ./nginx -s reload - Nginx的優(yōu)化方案!
nginx 進(jìn)程數(shù),建議按照cpu 數(shù)目來指定,一般為它的倍數(shù) (如,2個(gè)四核的cpu計(jì)為8)。
為每個(gè)進(jìn)程分配cpu,上例中將8 個(gè)進(jìn)程分配到8 個(gè)cpu,當(dāng)然可以寫多個(gè),或者將一個(gè)進(jìn)程分配到多個(gè)
cpu。
配置每個(gè)進(jìn)程允許的最多連接數(shù), 理論上每臺(tái)nginx 服務(wù)器的最大連接數(shù)為 : worker進(jìn)程數(shù) * worker連
接數(shù)。 - Redis的應(yīng)用場(chǎng)景!
熱點(diǎn)數(shù)據(jù)的緩存, 限時(shí)業(yè)務(wù)的運(yùn)用, 計(jì)數(shù)器相關(guān)問題, 排行榜相關(guān)問題, 分布式鎖, 點(diǎn)贊、好友等相互關(guān)系的
存儲(chǔ)等 - Redis存儲(chǔ)數(shù)據(jù)的結(jié)構(gòu)!
Redis的Key-Value格式中Key只能是String類型
Redis的Value類型有5種:
String, List, Set(無序Set), ZSet(有序Set), Hash類型(Map) - Redis持久化策略!
redis的持久化策策略有2種:默認(rèn)是RDB
RDB(數(shù)據(jù)快照模式),定期存儲(chǔ),保存的是數(shù)據(jù)本身,操作磁盤頻率低, 速度快, 服務(wù)器突然斷電數(shù)據(jù)
可能丟失一部分.
AOF(追加模式),每次修改數(shù)據(jù)時(shí),同步到硬盤(寫操作日志),保存的是數(shù)據(jù)的變更記錄, 頻繁操作磁
盤, 速度慢, 但是數(shù)據(jù)可靠不容易丟失. - Redis集群架構(gòu)!
哨兵模式:
需要三臺(tái)服務(wù)器, 一臺(tái)哨兵服務(wù)器, 一臺(tái)主機(jī), 一臺(tái)備機(jī)
哨兵使用心跳檢測(cè)技術(shù)每隔一段時(shí)間向主機(jī)發(fā)送ping命令, 主機(jī)返回pong命令, 認(rèn)為主機(jī)存活, 如果主機(jī)
不返回pong命令, 認(rèn)為主機(jī)宕機(jī)
哨兵服務(wù)器就會(huì)通知備機(jī)進(jìn)行切換, 備機(jī)充當(dāng)主機(jī), 備機(jī)會(huì)最后ping一次主機(jī), 如果主機(jī)返回pong命令不
進(jìn)行切換, 如果主機(jī)沒有返回pong命令則認(rèn)為主機(jī)確實(shí)宕機(jī), 備機(jī)切換成主機(jī)替代主機(jī)工作 - Redis的淘汰策略
volatile-lru:從設(shè)置過期時(shí)間的數(shù)據(jù)集中挑選出最近最少使用的數(shù)據(jù)淘汰。
volatile-ttl:淘汰機(jī)制采用LRU,策略基本上與volatile-lru相似,從設(shè)置過期時(shí)間的數(shù)據(jù)集中挑選將要過
期的數(shù)據(jù)淘汰,ttl值越大越優(yōu)先被淘汰。
volatile-random:隨機(jī)淘汰, 從已設(shè)置過期時(shí)間的數(shù)據(jù)集中任意選擇數(shù)據(jù)淘汰。
allkeys-lru:從所有數(shù)據(jù)集中挑選最近最少使用的數(shù)據(jù)淘汰,該策略要淘汰的key面向的是全體key集
合,而非過期的key集合。
allkeys-random:從所有數(shù)據(jù)集中選擇任意數(shù)據(jù)淘汰。 - Redis緩存的擊穿,穿透,雪崩,傾斜問題!
緩存擊穿:
就是某一個(gè)熱點(diǎn)數(shù)據(jù),緩存中某一時(shí)刻失效了,因而大量并發(fā)請(qǐng)求打到數(shù)據(jù)庫(kù)上,就像被擊穿了一
樣。說白了,就是某個(gè)數(shù)據(jù),數(shù)據(jù)庫(kù)有,但是緩存中沒有。那么,緩存擊穿,就會(huì)使得因?yàn)檫@一個(gè)
熱點(diǎn)數(shù)據(jù),將大量并發(fā)請(qǐng)求打擊到數(shù)據(jù)庫(kù)上,從而導(dǎo)致數(shù)據(jù)庫(kù)被打垮。
緩存擊穿解決方案:
去掉熱點(diǎn)數(shù)據(jù)的生存時(shí)間
熱點(diǎn)數(shù)據(jù)訪問到數(shù)據(jù)庫(kù)的時(shí)候加個(gè)鎖, 這樣同一時(shí)間只能有一個(gè)訪問熱點(diǎn)數(shù)據(jù)的請(qǐng)求訪問數(shù)據(jù)庫(kù), 防
止數(shù)據(jù)庫(kù)宕機(jī).
緩存穿透 :
緩存穿透與擊穿的區(qū)別就是,
擊穿:redis中沒有數(shù)據(jù), 數(shù)據(jù)庫(kù)里“有”數(shù)據(jù);
穿透:redis中沒有數(shù)據(jù), 數(shù)據(jù)庫(kù)里也“沒”數(shù)據(jù)。
緩存穿透解決方案:
用戶查詢會(huì)有查詢參數(shù), 使用查詢參數(shù)作為key, value值設(shè)置為null, 存入redis并設(shè)置生存時(shí)間, 如果
這樣的請(qǐng)求多了, redis抗壓,但是不會(huì)造成數(shù)據(jù)庫(kù)宕機(jī).
使用布隆過濾器, 直接過濾這樣的查詢請(qǐng)求
緩存雪崩 : 指的是大面積的 key 同時(shí)過期,導(dǎo)致大量并發(fā)打到我們的數(shù)據(jù)庫(kù)。
雪崩解決方案 : 將key的過期時(shí)間設(shè)置為隨機(jī), 這樣不會(huì)在同一時(shí)間大量過期, 不會(huì)在同一時(shí)間造成
大量數(shù)據(jù)庫(kù)訪問, 防止數(shù)據(jù)庫(kù)宕機(jī) - Redis的緩存和數(shù)據(jù)庫(kù)的雙寫一致性!
數(shù)據(jù)庫(kù)和redis同步數(shù)據(jù)流程 : 寫數(shù)據(jù)庫(kù), 刪除redis對(duì)應(yīng)緩存數(shù)據(jù), 再將數(shù)據(jù)庫(kù)最新數(shù)據(jù)寫入redis中
將不一致分為三種情況:
數(shù)據(jù)庫(kù)有數(shù)據(jù),緩存沒有數(shù)據(jù);
數(shù)據(jù)庫(kù)有數(shù)據(jù),緩存也有數(shù)據(jù),數(shù)據(jù)不相等;
數(shù)據(jù)庫(kù)沒有數(shù)據(jù),緩存有數(shù)據(jù)。
解決方案大概有以下幾種:
對(duì)刪除緩存進(jìn)行重試,數(shù)據(jù)的一致性要求越高,我越是重試得快。
定期全量更新,簡(jiǎn)單地說,就是我定期把緩存全部清掉,然后再全量加載。
給所有的緩存一個(gè)失效期 - Redis如何實(shí)現(xiàn)的分布式鎖!
獲取鎖:
使用setnx命令向redis中存入一個(gè)鍵值對(duì)并設(shè)置過期時(shí)間
setnx命令在存入之前會(huì)進(jìn)行判斷, 如果redis中存在這個(gè)鍵值對(duì), 則無法存入并返回狀態(tài)0, 相當(dāng)于鎖
被占用, 無法獲取鎖可以進(jìn)入等待或再一次嘗試, 如果redis中不存在這個(gè)鍵值對(duì), 則可以存入并返回
狀態(tài)1
釋放鎖:
使用setnx命令存入數(shù)據(jù)后, 相當(dāng)于獲取到了鎖, 執(zhí)行完業(yè)務(wù)后一定要使用delete命令刪除這個(gè)鍵值
對(duì), 就相當(dāng)于釋放鎖,其他任務(wù)就可以繼續(xù)以上面方式獲取鎖, 因?yàn)樵O(shè)置了鍵值對(duì)的超時(shí)時(shí)間,在規(guī)定
時(shí)間內(nèi)沒有釋放鎖會(huì)redis會(huì)自動(dòng)讓這個(gè)鍵值對(duì)失效,防止死鎖 - Redis的線程模型
從redis的性能上進(jìn)行考慮,單線程避免了上下文頻繁切換問題,效率高;
從redis的內(nèi)部結(jié)構(gòu)設(shè)計(jì)原理進(jìn)行考慮,redis是基于Reactor模式開發(fā)了自己的網(wǎng)絡(luò)事件處理器: 這個(gè)處
理器被稱為文件事件處理器(file event handler)。而這個(gè)文件事件處理器是單線程的,所以才叫redis
的單線程模型,這也決定了redis是單線程的 - Elasticsearch的應(yīng)用場(chǎng)景
互聯(lián)網(wǎng)全文檢: 像百度, 谷歌等
站內(nèi)全文檢索: 貼吧內(nèi)帖子搜索, 京東, 淘寶商品搜索等
總之就是大數(shù)據(jù)量, 海量數(shù)據(jù)的搜索功能都可以用ElasticSearch - 什么是倒排索引
搜索前, 根據(jù)被搜索的內(nèi)容創(chuàng)建文檔對(duì)象, 文檔對(duì)象有唯一id, 對(duì)被搜索內(nèi)容進(jìn)行切分詞, 也就是將被搜索
的內(nèi)容中的一句句話切分成一個(gè)個(gè)詞, 組成索引, 索引記錄了文檔id, 也就是索引和文檔有關(guān)聯(lián)關(guān)系, 查詢
的時(shí)候先查詢索引, 根據(jù)索引找到文檔id, 根據(jù)文檔id找到文檔內(nèi)容, 這個(gè)過程叫做倒排索引算法 - Elasticsearch在5.x,6.x,7.x版本的區(qū)別
5.x Lucene 6.x 的支持,磁盤空間少一半;索引時(shí)間少一半;查詢性能提升25%;支持IPV6。
6.x 開始不支持一個(gè) index 里面存在多個(gè) type
7.x TransportClient被廢棄以至于es7的java代碼,只能使用restclient。對(duì)于java編程,建議采用 Highlevel-rest-client 的方式操作ES集群 - Elasticsearch中分片的概念
單個(gè)節(jié)點(diǎn)由于物理機(jī)硬件限制,存儲(chǔ)的文檔是有限的,如果一個(gè)索引包含海量文檔,則不能在單個(gè)節(jié)點(diǎn)
存儲(chǔ)。ES提供分 片機(jī)制,同一個(gè)索引可以存儲(chǔ)在不同分片(數(shù)據(jù)容器)中,這些分片又可以存儲(chǔ)在集群
中不同節(jié)點(diǎn)上
分片分為 主分片(primary shard) 以及 從分片(replica shard)
主分片與從分片關(guān)系:從分片只是主分片的一個(gè)副本,它用于提供數(shù)據(jù)的冗余副本
從分片應(yīng)用:在硬件故障時(shí)提供數(shù)據(jù)保護(hù),同時(shí)服務(wù)于搜索和檢索這種只讀請(qǐng)求
是否可變:索引中的主分片的數(shù)量在索引創(chuàng)建后就固定下來了,但是從分片的數(shù)量可以隨時(shí)改變
索引默認(rèn)創(chuàng)建的分片:默認(rèn)設(shè)置5個(gè)主分片和一組從分片(即每個(gè)主分片有一個(gè)從分片對(duì)應(yīng)),但是從分
片沒有被啟用(主從分片在同一個(gè)節(jié)點(diǎn)上沒有意義),因此集群健康值顯示為黃色(yellow) - Elasticsearch中refresh和flush是什么
Refresh:
當(dāng)我們向ES發(fā)送請(qǐng)求的時(shí)候,我們發(fā)現(xiàn)es貌似可以在我們發(fā)請(qǐng)求的同時(shí)進(jìn)行搜索。而這個(gè)實(shí)時(shí)建索
引并可以被搜索的過程實(shí)際上是一次es 索引提交(commit)的過程,如果這個(gè)提交的過程直接將
數(shù)據(jù)寫入磁盤(fsync)必然會(huì)影響性能,所以es中設(shè)計(jì)了一種機(jī)制,即:先將index-buffer中文檔
(document)解析完成的segment寫到filesystem cache之中,這樣避免了比較損耗性能的io操
作,又可以使document可以被搜索。以上從index-buffer中取數(shù)據(jù)到filesystem cache中的過程叫
做refresh。
es默認(rèn)的refresh間隔時(shí)間是1s,這也是為什么ES可以進(jìn)行近乎實(shí)時(shí)的搜索
Flush :
我們可能已經(jīng)意識(shí)到如果數(shù)據(jù)在filesystem cache之中是很有可能在意外的故障中丟失。這個(gè)時(shí)候
就需要一種機(jī)制,可以將對(duì)es的操作記錄下來,來確保當(dāng)出現(xiàn)故障的時(shí)候,保留在filesystem的數(shù)
據(jù)不會(huì)丟失,并在重啟的時(shí)候可以從這個(gè)記錄中將數(shù)據(jù)恢復(fù)過來。elasticsearch提供了translog來
記錄這些操作。當(dāng)向elasticsearch發(fā)送創(chuàng)建document索引請(qǐng)求的時(shí)候,document數(shù)據(jù)會(huì)先進(jìn)入
到index buffer之后,與此同時(shí)會(huì)將操作記錄在translog之中,當(dāng)發(fā)生refresh時(shí)(數(shù)據(jù)從index
buffer中進(jìn)入filesystem cache的過程)translog中的操作記錄并不會(huì)被清除,而是當(dāng)數(shù)據(jù)從
filesystem cache中被寫入磁盤之后才會(huì)將translog中清空。而從filesystem cache寫入磁盤的過程
就是flush。 - 如何提升Elasticsearch的查詢效率
調(diào)優(yōu)Filesystem Cache性能的方法:
要讓 ES 性能好,最佳的情況下,就是機(jī)器的內(nèi)存,至少可以容納總數(shù)據(jù)量的一半。索引數(shù)據(jù)占用
的內(nèi)存最好控制在留給Filesystem Cache的內(nèi)存容量中。盡量將所有索引數(shù)據(jù)加載到內(nèi)存中, 這樣
搜索的時(shí)候可以從內(nèi)存中搜索索引速度極快.
冷熱分離方法:
將熱門數(shù)據(jù)寫一個(gè)索引,冷門數(shù)據(jù)單獨(dú)寫一個(gè)索引,這樣在熱門數(shù)據(jù)被預(yù)熱(即寫入Filesystem
Cache)后,不會(huì)被冷數(shù)據(jù)給沖刷掉。
數(shù)據(jù)預(yù)熱方法:
對(duì)于熱門的搜索詞,后臺(tái)系統(tǒng)可以定時(shí)自己去搜索一下,這樣,熱門數(shù)據(jù)就會(huì)刷到Filesystem
Cache中,后面用戶實(shí)際來搜索熱詞時(shí)就是直接從內(nèi)存中搜索了 - 如何提高Elasticsearch的查詢命中率
加入擴(kuò)展詞詞典ext.dic
主要有兩項(xiàng):生詞擴(kuò)展和同義詞擴(kuò)展
生詞擴(kuò)展:把網(wǎng)絡(luò)用語(yǔ)、公司名稱等詞語(yǔ)放入詞典中
同義詞擴(kuò)展:如我們平常用的iphone,當(dāng)輸入“蘋果”的時(shí)候,我們也是期待能查出手機(jī)的。
加入停止詞詞典stop.dic
把 了、啊、哦、的、之類的語(yǔ)氣詞放入停止詞詞典;把數(shù)據(jù)篩選過濾 - RabbitMQ的應(yīng)用場(chǎng)景
異步處理
很多時(shí)候,用戶不想也不需要立即處理消息。消息隊(duì)列提供了異步處理機(jī)制,允許用戶把一個(gè)消息
放入隊(duì)列,但并不立即處理它。想向隊(duì)列中放入多少消息就放多少,然后在需要的時(shí)候再去處理它
們。
應(yīng)用解耦
在項(xiàng)目啟動(dòng)之初來預(yù)測(cè)將來項(xiàng)目會(huì)碰到什么需求,是極其困難的。消息系統(tǒng)在處理過程中間插入了
一個(gè)隱含的、基于數(shù)據(jù)的接口層,兩邊的處理過程都要實(shí)現(xiàn)這一接口。這允許你獨(dú)立的擴(kuò)展或修改
兩邊的處理過程,只要確保它們遵守同樣的接口約束
流量消峰
主要可以用來抗高并發(fā)的寫入, 防止數(shù)據(jù)庫(kù)因?yàn)楦卟l(fā)寫入宕機(jī) - RabbitMQ的底層架構(gòu)
Broker:它提供一種傳輸服務(wù),它的角色就是維護(hù)一條從生產(chǎn)者到消費(fèi)者的路線,保證數(shù)據(jù)能按照指定的
方式進(jìn)行傳輸,
Exchange:消息交換機(jī),它指定消息按什么規(guī)則,路由到哪個(gè)隊(duì)列。
Queue:消息的載體,每個(gè)消息都會(huì)被投到一個(gè)或多個(gè)隊(duì)列。
Binding:綁定,它的作用就是把exchange和queue按照路由規(guī)則綁定起來.
Routing Key:路由關(guān)鍵字,exchange根據(jù)這個(gè)關(guān)鍵字進(jìn)行消息投遞。
vhost:虛擬主機(jī),一個(gè)broker里可以有多個(gè)vhost,用作不同用戶的權(quán)限分離。
Producer:消息生產(chǎn)者,就是投遞消息的程序.
Consumer:消息消費(fèi)者,就是接受消息的程序.
Channel:消息通道,在客戶端的每個(gè)連接里,可建立多個(gè)channel. - RabbitMQ如何保證消息的可靠性
可靠性是保證消息不丟失,分為發(fā)送消息不丟失和消費(fèi)消息不丟失兩種 :
發(fā)送不丟失有confirm和return機(jī)制
消費(fèi)不丟失可以用ack手動(dòng)確認(rèn)機(jī)制 - RabbitMQ如何保證避免消息重復(fù)消費(fèi)
讓每個(gè)消息攜帶一個(gè)全局的唯一ID,即可保證消息的冪等性,具體消費(fèi)過程為:
消費(fèi)者獲取到消息后先根據(jù)id去查詢r(jià)edis是否存在該消息。
如果不存在,則正常消費(fèi),消費(fèi)完畢后寫入redis。
如果存在,則證明消息被消費(fèi)過,直接丟棄。 - RabbitMQ的死信隊(duì)列
死信:是RabbitMQ中的一種消息機(jī)制,當(dāng)消息在隊(duì)列的存活時(shí)間超過設(shè)置的TTL時(shí)間或者消息隊(duì)列的消
息數(shù)量已經(jīng)超過最大隊(duì)列長(zhǎng)度。這樣的消息被認(rèn)為是死亡的信息也就是死信.
死信消息,會(huì)被RabbitMQ自動(dòng)重新投遞到另一個(gè)交換機(jī)上(Exchange),這個(gè)交換機(jī)往往被稱為
DLX(dead-letter-exchange)“死信交換機(jī)”,然后交換機(jī)根據(jù)綁定規(guī)則轉(zhuǎn)發(fā)到對(duì)應(yīng)的隊(duì)列上,監(jiān)聽該隊(duì)列
就可以被重新消費(fèi)。 - RabbitMQ如何基于死信隊(duì)列實(shí)現(xiàn)延遲隊(duì)列以及存在的問題
RabbitMQ中沒有延遲隊(duì)列的功能, 但是可以借助延時(shí)消息, 死信, 死信隊(duì)列來實(shí)現(xiàn)延遲隊(duì)列的功能
第一. 給隊(duì)列發(fā)送消息給消息設(shè)置一個(gè)超時(shí)時(shí)間, 超過這個(gè)時(shí)間沒有被消費(fèi)掉這個(gè)消息就會(huì)被認(rèn)為是死信,
這個(gè)隊(duì)列不設(shè)置消費(fèi)方.
第二. 隊(duì)列中的所有消息, 到達(dá)超時(shí)時(shí)間都會(huì)成為死信, 會(huì)被RabbitMQ發(fā)送到死信交換器
第三. 死信交換器中的數(shù)據(jù)會(huì)被RabbitMQ發(fā)送到死信隊(duì)列
第四. 接收方監(jiān)聽器監(jiān)聽死信隊(duì)列. 這樣從里面消費(fèi)的消息都是超時(shí)后的消息, 也就是先了延遲隊(duì)列的功
能. - Zookeeper的應(yīng)用場(chǎng)景
可以做為dubbo的注冊(cè)中心
可以做分布式鎖
可以為dubbo提供負(fù)載均衡功能
分布式協(xié)調(diào)/通知 - Zookeeper的存儲(chǔ)數(shù)據(jù)的結(jié)構(gòu)
zookeeper的數(shù)據(jù)存儲(chǔ)結(jié)構(gòu)是一個(gè)DataTree數(shù)據(jù)結(jié)構(gòu)。
其內(nèi)部是一個(gè)Map<String, DataNode> nodes的數(shù)據(jù)結(jié)構(gòu),其中key是path,DataNode是真正保存數(shù)
據(jù)的核心數(shù)據(jù)結(jié)構(gòu)。 - Zookeeper的節(jié)點(diǎn)的類型
永久節(jié)點(diǎn) : 無序但是客戶端和zookeeper服務(wù)器斷開連接后不會(huì)被自動(dòng)刪除
永久有序節(jié)點(diǎn) : 有序, 并且客戶端和zookeeper服務(wù)器斷開連接后不會(huì)被自動(dòng)刪除
臨時(shí)節(jié)點(diǎn) : 無序, 客戶端和zookeeper服務(wù)器斷開連接后會(huì)被自動(dòng)刪除
臨時(shí)有序節(jié)點(diǎn) : 有序, 客戶端和zookeeper服務(wù)器斷開連接后會(huì)被自動(dòng)刪除 - Zookeeper的集群架構(gòu)
Zookeeper集群采用選舉機(jī)制, 也就是多臺(tái)zookeeper之間會(huì)互相進(jìn)行投票, 從而選出主機(jī)leader, 其他的
都是備機(jī)flower.
主備之間使用心跳檢測(cè)技術(shù), 備機(jī)每隔一段時(shí)間會(huì)ping主機(jī), 主機(jī)返回pong給備機(jī), 認(rèn)為主機(jī)存活. 如果主
機(jī)不返回pong,則認(rèn)為主機(jī)宕機(jī), 這個(gè)時(shí)候其他備機(jī)會(huì)再次執(zhí)行選舉機(jī)制, 選舉出新的主機(jī)leader來替代老
主機(jī)工作.
Zookeeper集群一旦超過半數(shù)機(jī)器宕機(jī), 則整個(gè)集群不提供服務(wù), 將失去作用, 所以建議zookeeper集群服
務(wù)器數(shù)量為奇數(shù)臺(tái). - Zookeeper如何實(shí)現(xiàn)分布式鎖
使用zookeeper創(chuàng)建臨時(shí)序列節(jié)點(diǎn)來實(shí)現(xiàn)分布式鎖,適用于順序執(zhí)行的程序,大體思路就是創(chuàng)建臨時(shí)序
列節(jié)點(diǎn),找出最小的序列節(jié)點(diǎn),獲取分布式鎖,程序執(zhí)行完成之后此序列節(jié)點(diǎn)消失,通過watch來監(jiān)控
節(jié)點(diǎn)的變化,從剩下的節(jié)點(diǎn)的找到最小的序列節(jié)點(diǎn),獲取分布式鎖,執(zhí)行相應(yīng)處理,依次類推…… - 闡述一下你理解的微服務(wù)架構(gòu)
微服務(wù)架構(gòu)是一種架構(gòu)模式,它提倡將單一應(yīng)用程序劃分成一組小的服務(wù),服務(wù)之間相互協(xié)調(diào)、互相配
合,為用戶提供最終價(jià)值。每個(gè)服務(wù)運(yùn)行在其獨(dú)立的進(jìn)程中,服務(wù)和服務(wù)之間采用輕量級(jí)的通信機(jī)制相
互溝通(通常是基于HTTP的Restful API).每個(gè)服務(wù)都圍繞著具體的業(yè)務(wù)進(jìn)行構(gòu)建,并且能夠被獨(dú)立的部
署到生產(chǎn)環(huán)境、類生產(chǎn)環(huán)境等。另外,應(yīng)盡量避免統(tǒng)一的、集中的服務(wù)管理機(jī)制.這樣實(shí)現(xiàn)了易擴(kuò)展,易維
護(hù), 松耦合的特點(diǎn). - 闡述一下SpringCloud中常用的組件
注冊(cè)中心Eureka : 所有微服務(wù)啟動(dòng)后都要注冊(cè)到Eureka中
遠(yuǎn)程調(diào)用Feign : 底層使用http協(xié)議, 發(fā)送請(qǐng)求給被調(diào)用方, 執(zhí)行后返回結(jié)果
接口負(fù)載均衡Ribbon : 如果被調(diào)用的微服務(wù)是集群, Ribbion起到了負(fù)載均衡的效果
服務(wù)網(wǎng)關(guān)Gateway : 所有外部訪問微服務(wù)的請(qǐng)求都要經(jīng)過網(wǎng)關(guān), 網(wǎng)關(guān)根據(jù)訪問的url路徑來轉(zhuǎn)發(fā)請(qǐng)求到具體
服務(wù).
配置中心Config : 統(tǒng)一管理所有微服務(wù)的配置文件.
熔斷器Hystrix : 并發(fā)量超過微服務(wù)可以承受的極限, 可以進(jìn)行熔斷或者降級(jí). - Eureka的工作機(jī)制
服務(wù)啟動(dòng)后向Eureka注冊(cè),Eureka Server會(huì)將注冊(cè)信息向其他Eureka Server進(jìn)行同步,當(dāng)服務(wù)消費(fèi)者
要調(diào)用服務(wù)提供者,則向服務(wù)注冊(cè)中心獲取服務(wù)提供者地址,然后會(huì)將服務(wù)提供者地址緩存在本地,下
次再調(diào)用時(shí),則直接從本地緩存中取,完成一次調(diào)用。
當(dāng)服務(wù)注冊(cè)中心Eureka Server檢測(cè)到服務(wù)提供者因?yàn)殄礄C(jī)、網(wǎng)絡(luò)原因不可用時(shí),則在服務(wù)注冊(cè)中心將服
務(wù)置為DOWN狀態(tài),并把當(dāng)前服務(wù)提供者狀態(tài)向訂閱者發(fā)布,訂閱過的服務(wù)消費(fèi)者更新本地緩存。
服務(wù)提供者在啟動(dòng)后,周期性(默認(rèn)30秒)向Eureka Server發(fā)送心跳,以證明當(dāng)前服務(wù)是可用狀態(tài)。
Eureka Server在一定的時(shí)間(默認(rèn)90秒)未收到客戶端的心跳,則認(rèn)為服務(wù)宕機(jī),注銷該實(shí)例。 - Ribbon如何實(shí)現(xiàn)負(fù)載均衡的
調(diào)用方微服務(wù)到Eureka中根據(jù)被調(diào)用的服務(wù)名獲取被調(diào)用服務(wù)器地址ip和端口號(hào)列表
在調(diào)用方根據(jù)返回的地址列表Rbbion默認(rèn)使用輪詢的策略, 分配請(qǐng)求進(jìn)行逐個(gè)調(diào)用 - Hystrix的斷路器以及實(shí)現(xiàn)原理
Hystrix的斷路器實(shí)現(xiàn)原理采用, 馬丁福勒斷路器原理如下:
一段時(shí)間內(nèi),失敗率達(dá)到一定閾值(比如50%失敗,或者失敗了50次),斷路器打開,此時(shí)不再請(qǐng)求服
務(wù)提供者,而是只是快速失敗的方法(斷路方法)
斷路器打開一段時(shí)間,自動(dòng)進(jìn)入“半開”狀態(tài),此時(shí),斷路器可允許一個(gè)請(qǐng)求方法服務(wù)提供者,如果請(qǐng)求
調(diào)用成功,則關(guān)閉斷路器,否則繼續(xù)保持?jǐn)嗦菲鞔蜷_狀態(tài)。
斷路器hystrix是保證了局部發(fā)生的錯(cuò)誤,不會(huì)擴(kuò)展到整個(gè)系統(tǒng),從而保證系統(tǒng)的即使出現(xiàn)局部問題也不
會(huì)造成系統(tǒng)雪崩。 - Eureka和Zookeeper實(shí)現(xiàn)注冊(cè)中心的區(qū)別
Zookeeper保證的是CP(一致性和分區(qū)容錯(cuò)性)
zookeeper對(duì)一致性的要求要高于可用性。
Eureka保證的是AP(可用性和分區(qū)容錯(cuò)性)
Eureka看明白了這一點(diǎn), 因此在設(shè)計(jì)時(shí)就優(yōu)先保證可用性。Eureka各個(gè)節(jié)點(diǎn)都是平等的,幾個(gè)節(jié)點(diǎn)掛掉
不會(huì)影響正常節(jié)點(diǎn)的工作,剩余的節(jié)點(diǎn)依然可以提供注冊(cè)和查詢服務(wù)。而Eureka的客戶端在向某個(gè)
Eureka注冊(cè)時(shí),如果發(fā)現(xiàn)連接失敗,則會(huì)自動(dòng)切換至其他節(jié)點(diǎn),只要有一臺(tái)Eureka還在, 就能保住注冊(cè)
服務(wù)的可用性,只不過查到的信息可能不是最新的 - 分布式項(xiàng)目中如何解決分布式事務(wù)的問題
首先盡量不要使用分布式事務(wù), 因?yàn)闀?huì)降低代碼執(zhí)行效率, 盡量保證業(yè)務(wù)的原子性和冪等性, 不使用分布式
事務(wù)最好.
某些業(yè)務(wù)必須用, 則可以使用分布式事務(wù)框架LCN或者阿里的Seata解決 - 闡述一下SpringCloud Alibaba中常用的組件
Nacos(配置中心 + 注冊(cè)中心)
Nacos實(shí)現(xiàn)了服務(wù)的配置中心與服務(wù)注冊(cè)發(fā)現(xiàn)的功能,Nacos可以通過可視化的配置降低相關(guān)的學(xué)
習(xí)與維護(hù)成本,實(shí)現(xiàn)動(dòng)態(tài)的配置管理與分環(huán)境的配置中心控制。 同時(shí)Nacos提供了基于http/RCP的
服務(wù)注冊(cè)與發(fā)現(xiàn)功能
Sentinel (分布式流控)
Sentinel是面向分布式微服務(wù)架構(gòu)的輕量級(jí)高可用的流控組件,以流量作為切入點(diǎn),從流量控制,
熔斷降級(jí),系統(tǒng)負(fù)載保護(hù)等維度幫助用戶保證服務(wù)的穩(wěn)定性。常用與實(shí)現(xiàn)限流、熔斷降級(jí)等策略
Dubbo (遠(yuǎn)程過程調(diào)用)
Dubbo已經(jīng)在圈內(nèi)很火了,SpringCloud Alibaba基于上面提到的Nacos服務(wù)注冊(cè)中心也同樣整合了
Dubbo。
RocketMQ (消息隊(duì)列)
RocketMQ基于Java的高性能、高吞吐量的消息隊(duì)列,在SpringCloud Alibaba生態(tài)用于實(shí)現(xiàn)消息驅(qū)
動(dòng)的業(yè)務(wù)開發(fā),常見的消息隊(duì)列有Kafka、RocketMQ、RabbitMQ等
Seata (分布式事物)
既然是微服務(wù)的產(chǎn)品,那么肯定會(huì)用到分布式事物。Seata就是阿里巴巴開源的一個(gè)高性能分布式
事物的解決方案。 - zuul和gateway區(qū)別?
目前zuul已經(jīng)被gateway取代
Gateway比zuul功能更加強(qiáng)大, gateway內(nèi)部實(shí)現(xiàn)了限流, 負(fù)載均衡等,但是也限制了僅適用于
SpringCould套件, zuul在其他微服務(wù)架構(gòu)下也可以使用, 但是內(nèi)部沒有實(shí)現(xiàn)限流, 負(fù)載均衡等功能
Zuul僅支持同步, gateway支持異步, 所以gateway性能更好 - springCloud和Dubbo區(qū)別?
沒有可比性, dubbo只是底層使用了RPC遠(yuǎn)程過程調(diào)用規(guī)范的一個(gè)遠(yuǎn)程調(diào)用技術(shù). 而SpringCloud框架集
里面有很多子項(xiàng)目, SpringCloud中有注冊(cè)中心, 配置中心, 遠(yuǎn)程調(diào)用, 接口間負(fù)載均衡, 熔斷器, 網(wǎng)關(guān)等, 可
以對(duì)服務(wù)進(jìn)行監(jiān)控, 治理, 配置等有著全套解決方案. - 分布式事務(wù)和分布式任務(wù)如何實(shí)現(xiàn)?
分布式事務(wù)遵循CAP定理, 可以使用2pc兩階段提交, 或者TCC基于補(bǔ)償機(jī)制實(shí)現(xiàn), 又或者可以使用基于消
息最終一致性解決方案等.
具體使用起來也可以使用LCN或者阿里巴巴的Seata框架實(shí)現(xiàn)
分布式任務(wù)可以使用當(dāng)當(dāng)網(wǎng)的ElasticJob分布式任務(wù)框架實(shí)現(xiàn) - redis與mysql怎么保證數(shù)據(jù)的一致?
采用延時(shí)雙刪策略:
先刪除緩存;
再寫數(shù)據(jù)庫(kù);
休眠500毫秒(根據(jù)具體的業(yè)務(wù)時(shí)間來定);
再次刪除緩存。
那么,這個(gè)500毫秒怎么確定的,具體該休眠多久呢?
需要評(píng)估自己的項(xiàng)目的讀數(shù)據(jù)業(yè)務(wù)邏輯的耗時(shí)。這么做的目的,就是確保讀請(qǐng)求結(jié)束,寫請(qǐng)求可以
刪除讀請(qǐng)求造成的緩存臟數(shù)據(jù)。
當(dāng)然,這種策略還要考慮 redis 和數(shù)據(jù)庫(kù)主從同步的耗時(shí)。最后的寫數(shù)據(jù)的休眠時(shí)間:則在讀數(shù)據(jù)
業(yè)務(wù)邏輯的耗時(shí)的基礎(chǔ)上,加上幾百ms即可。比如:休眠1秒。
雙刪失敗如何處理?
設(shè)置緩存數(shù)據(jù)的過期時(shí)間 - 延遲刪除是怎么解決數(shù)據(jù)一致性
延時(shí)雙刪的方案思路是,為了避免更新數(shù)據(jù)庫(kù)的時(shí)候,其他線程從緩存中讀取不到數(shù)據(jù),就在更新完數(shù)
據(jù)庫(kù)之后,在sleep一段時(shí)間,然后再刪除緩存。
sleep的時(shí)間要對(duì)業(yè)務(wù)讀寫緩存的時(shí)間做出評(píng)估,sleep時(shí)間大于讀寫緩存的時(shí)間即可。 - 什么是跨域問題? SpringBoot如何解決跨域問題?
跨域問題:
瀏覽器廠商在生產(chǎn)瀏覽器的時(shí)候, 瀏覽器內(nèi)部已經(jīng)內(nèi)置了同源策略, 也就是要求當(dāng)前頁(yè)面所在的url地
址和發(fā)送請(qǐng)求目標(biāo)的url地址中, 協(xié)議, 域名, 端口號(hào)不可以有變化, 任意一項(xiàng)發(fā)生改變, 服務(wù)器雖然可
以接收請(qǐng)求返回響應(yīng), 但是瀏覽器認(rèn)為不安全, 不接受響應(yīng)的數(shù)據(jù). 這就是跨域問題.
解決方案:
SpringMvc已經(jīng)內(nèi)置了跨域解決方案, 在controller類上加入@CrossOrigin注解就可以解決
底層原理就是在響應(yīng)頭中加入Access-Control-Allow-Origin: * 設(shè)置 - Zookeeper 集群節(jié)點(diǎn)為什么要部署成奇數(shù)
Zookeeper集群中只要有超過半數(shù)機(jī)器不工作,那么整個(gè)集群都是不可用的. 當(dāng)宕掉幾個(gè)zookeeper節(jié)點(diǎn)
服務(wù)器之后,剩下的個(gè)數(shù)必須大于宕掉的個(gè)數(shù),也就是剩下的節(jié)點(diǎn)服務(wù)數(shù)必須大于n/2,這樣zookeeper
集群才可以繼續(xù)使用,無論奇偶數(shù)都可以選舉leader。例如 : 5臺(tái)zookeeper節(jié)點(diǎn)機(jī)器最多宕掉2臺(tái),還
可以繼續(xù)使用,因?yàn)槭O?臺(tái)大于5/2。至于為什么最好為奇數(shù)個(gè)節(jié)點(diǎn)?這樣是為了以最大容錯(cuò)服務(wù)器個(gè)
數(shù)的條件下,能節(jié)省資源。比如,最大容錯(cuò)為2的情況下,對(duì)應(yīng)的zookeeper服務(wù)數(shù),奇數(shù)為5,而偶數(shù)
為6,也就是6個(gè)zookeeper服務(wù)的情況下最多能宕掉2個(gè)服務(wù),所以從節(jié)約資源的角度看,沒必要部署
6(偶數(shù))個(gè)zookeeper服務(wù)節(jié)點(diǎn) - Zookeeper腦裂問題和解決方案?
什么是腦裂:
腦裂(Split-Brain) 就是比如當(dāng)你的集群里面有3個(gè)節(jié)點(diǎn),它們都知道在這個(gè) cluster 里需要選舉出一
個(gè) master。那么當(dāng)它們3個(gè)之間的通信完全沒有問題的時(shí)候,就會(huì)達(dá)成共識(shí),選出其中一個(gè)作為
master。但是如果它們之間的通信出了問題,其中一個(gè)節(jié)點(diǎn)網(wǎng)絡(luò)抖動(dòng)斷開好長(zhǎng)時(shí)間, 那么剩下的兩
個(gè)結(jié)點(diǎn)都會(huì)覺得現(xiàn)在沒有 master,所以又會(huì)選舉出一個(gè) master,當(dāng)網(wǎng)絡(luò)恢復(fù)后集群里面就會(huì)有兩
個(gè) master。兩個(gè)master都會(huì)爭(zhēng)搶資源, 集群就會(huì)混亂出現(xiàn)問題.
解決Split-Brain腦裂的問題方案:
法定人數(shù)方式 : 比如3個(gè)節(jié)點(diǎn)的集群,集群可以容忍1個(gè)節(jié)點(diǎn)失效,這時(shí)候還能選舉出1個(gè)
lead,集群還可用。這是zookeeper防止"腦裂"默認(rèn)采用的方法。
采用Redundant communications (冗余通信)方式:集群中采用多種通信方式,防止一種通
信方式失效導(dǎo)致集群中的節(jié)點(diǎn)無法通信。
Fencing (共享資源) 方式:比如能看到共享資源就表示在集群中,能夠獲得共享資源的鎖的就
是Leader,看不到共享資源的,就不在集群中。 - 什么是冪等性(Idempotence)及用在那里?
冪等性是能夠以同樣的方式做兩次,而最終結(jié)果將保持不變,就好像它只做了一次的特性。
用法:在遠(yuǎn)程服務(wù)或數(shù)據(jù)源中使用冪等性,以便當(dāng)它多次接收指令時(shí),只處理一次。 - 什么是熔斷?什么是服務(wù)降級(jí)?
熔斷
服務(wù)熔斷的作用類似于我們家用的保險(xiǎn)絲,當(dāng)某服務(wù)出現(xiàn)不可用或響應(yīng)超時(shí)的情況時(shí),為了防止整
個(gè)系統(tǒng)出現(xiàn)雪崩,暫時(shí)停止對(duì)該服務(wù)的調(diào)用。
降級(jí)
服務(wù)降級(jí)是從整個(gè)系統(tǒng)的負(fù)荷情況出發(fā)和考慮的,對(duì)某些負(fù)荷會(huì)比較高的情況,為了預(yù)防某些功能
(業(yè)務(wù)場(chǎng)景)出現(xiàn)負(fù)荷過載或者響應(yīng)慢的情況,在其內(nèi)部暫時(shí)舍棄對(duì)一些非核心的接口和數(shù)據(jù)的請(qǐng)
求,而直接返回一個(gè)提前準(zhǔn)備好的fallback(退路)錯(cuò)誤處理信息。這樣,雖然提供的是一個(gè)有損
的服務(wù),但卻保證了整個(gè)系統(tǒng)的穩(wěn)定性和可用性。 - dubbo支持的協(xié)議有哪些
支持 : dubbo協(xié)議, rmi協(xié)議, hessian協(xié)議, http協(xié)議, webservice協(xié)議, thrift協(xié)議, memcached協(xié)議,
redis協(xié)議
dubbo協(xié)議(默認(rèn))
連接個(gè)數(shù):單連接
連接方式:長(zhǎng)連接
傳輸協(xié)議:TCP
傳輸方式:NIO 異步傳輸
序列化:Hessian 二進(jìn)制序列化
適用范圍:傳入傳出參數(shù)數(shù)據(jù)包較小(建議小于100K),消費(fèi)者比提供者個(gè)數(shù)多,單一消費(fèi)
者無法壓滿提供者,盡量不要用 dubbo 協(xié)議傳輸大文件或超大字符串。
適用場(chǎng)景:常規(guī)遠(yuǎn)程服務(wù)方法調(diào)用
rmi協(xié)議
連接個(gè)數(shù):多連接
連接方式:短連接
傳輸協(xié)議:TCP
傳輸方式:同步傳輸
序列化:Java 標(biāo)準(zhǔn)二進(jìn)制序列化
適用范圍:傳入傳出參數(shù)數(shù)據(jù)包大小混合,消費(fèi)者與提供者個(gè)數(shù)差不多,可傳文件。
適用場(chǎng)景:常規(guī)遠(yuǎn)程服務(wù)方法調(diào)用,與原生RMI服務(wù)互操作
hessian協(xié)議
連接個(gè)數(shù):多連接
連接方式:短連接
傳輸協(xié)議:HTTP
傳輸方式:同步傳輸
序列化:Hessian二進(jìn)制序列化
適用范圍:傳入傳出參數(shù)數(shù)據(jù)包較大,提供者比消費(fèi)者個(gè)數(shù)多,提供者壓力較大,可傳文件。
適用場(chǎng)景:頁(yè)面?zhèn)鬏?#xff0c;文件傳輸,或與原生hessian服務(wù)互操作
http協(xié)議
連接個(gè)數(shù):多連接
連接方式:短連接
傳輸協(xié)議:HTTP
傳輸方式:同步傳輸
序列化:表單序列化
適用范圍:傳入傳出參數(shù)數(shù)據(jù)包大小混合,提供者比消費(fèi)者個(gè)數(shù)多,可用瀏覽器查看,可用表
單或URL傳入?yún)?shù),暫不支持傳文件。
適用場(chǎng)景:需同時(shí)給應(yīng)用程序和瀏覽器 JS 使用的服務(wù)。
WebService協(xié)議
連接個(gè)數(shù):多連接
連接方式:短連接
傳輸協(xié)議:HTTP
傳輸方式:同步傳輸
序列化:SOAP 文本序列化
適用場(chǎng)景:系統(tǒng)集成,跨語(yǔ)言調(diào)用 - dubbo的連接方式有幾種
無注冊(cè)中心, 直接連接
在開發(fā)階段, 服務(wù)提供方?jīng)]有必要部署集群, 所以采用服務(wù)調(diào)用方直接連接服務(wù)提供方更方便測(cè)試與
開發(fā)
采用Zookeeper作為注冊(cè)中心連接
在線上部署階段使用, 對(duì)于某些并發(fā)訪問壓力大的服務(wù)器節(jié)點(diǎn)可以部署集群, 這時(shí)dubbo的服務(wù)提
供方服務(wù)器集群可以使用zookeeper來管理.
分組連接
當(dāng)一個(gè)接口有多種實(shí)現(xiàn)時(shí),可以用 group 區(qū)分。 - dubbo的負(fù)載均衡策略有哪些
Random LoadBalance 隨機(jī), 按權(quán)重設(shè)置隨機(jī)概率(默認(rèn))
RoundRobin LoadBalance 輪詢, 按權(quán)重設(shè)置輪詢比率
LeastActive LoadBalance 最少活躍調(diào)用數(shù), 相同活躍數(shù)的隨機(jī)
ConsistentHash LoadBalance 一致性Hash, 相同參數(shù)的請(qǐng)求總是發(fā)到同一提供者 - dubbo的序列化有哪些, 推薦哪種
Hessian 序列化:是修改過的 hessian lite,默認(rèn)啟用, 推薦使用.
json 序列化:使用 FastJson 庫(kù)
java 序列化:JDK 提供的序列化,性能不理想
dubbo 序列化:未成熟的高效 java 序列化實(shí)現(xiàn),不建議在生產(chǎn)環(huán)境使用
十、設(shè)計(jì)模式面試 【2道】
- 談一談你了解的設(shè)計(jì)模式有哪些?
大致按照模式的應(yīng)用目標(biāo)分類,設(shè)計(jì)模式可以分為創(chuàng)建型模式、結(jié)構(gòu)型模式和行為型模式。
創(chuàng)建型模式 : 是對(duì)對(duì)象創(chuàng)建過程的各種問題和解決方案的總結(jié),包括各種工廠模式(Factory、Abstract
Factory)、單例模式(Singleton)、構(gòu)建器模式(Builder)、原型模式(ProtoType)。
結(jié)構(gòu)型模式 : 是針對(duì)軟件設(shè)計(jì)結(jié)構(gòu)的總結(jié),關(guān)注于類、對(duì)象繼承、組合方式的實(shí)踐經(jīng)驗(yàn)。常見的結(jié)構(gòu)型
模式,包括橋接模式(Bridge)、適配器模式(Adapter)、裝飾者模式(Decorator)、代理模式
(Proxy)、組合模式(Composite)、外觀模式(Facade)、享元模式(Flyweight)等。
行為型模式 : 是從類或?qū)ο笾g交互、職責(zé)劃分等角度總結(jié)的模式。比較常見的行為型模式有策略模式
(Strategy)、解釋器模式(Interpreter)、命令模式(Command)、觀察者模式(Observer)、迭
代器模式(Iterator)、模板方法模式(Template Method)、訪問者模式(Visitor) - 設(shè)計(jì)模式開發(fā)中的應(yīng)用!
單例模式: 例如: 配置類config的對(duì)象只能有一個(gè)
工廠模式: 例如: 使用BeanFactory工廠創(chuàng)建對(duì)象, 不用自己去new對(duì)象
代理模式: 例如: spring中的aop底層實(shí)現(xiàn)就是動(dòng)態(tài)代理
適配器模式: 例如: SpringMvc中的HandlerInterceptorAdapter就是適配器模式
建造者模式: 例如: mybatis中的SqlsessionFactoryBuilder就是建造者模式, 遵循了單一職責(zé)原則, 只做一
件事.
十一、 數(shù)據(jù)結(jié)構(gòu) 【19道】
- 什么是空間和時(shí)間復(fù)雜度?
時(shí)間復(fù)雜度 :
(1)時(shí)間頻度 一個(gè)算法執(zhí)行所耗費(fèi)的時(shí)間,從理論上是不能算出來的,必須上機(jī)運(yùn)行測(cè)試才能知道。
但我們不可能也沒有必要對(duì)每個(gè)算法都上機(jī)測(cè)試,只需知道哪個(gè)算法花費(fèi)的時(shí)間多,哪個(gè)算法花費(fèi)的時(shí)
間少就可以了。并且一個(gè)算法花費(fèi)的時(shí)間與算法中語(yǔ)句的執(zhí)行次數(shù)成正比例,哪個(gè)算法中語(yǔ)句執(zhí)行次數(shù)
多,它花費(fèi)時(shí)間就多。一個(gè)算法中的語(yǔ)句執(zhí)行次數(shù)稱為語(yǔ)句頻度或時(shí)間頻度。記為T(n)。
(2)時(shí)間復(fù)雜度在剛才提到的時(shí)間頻度中,n稱為問題的規(guī)模,當(dāng)n不斷變化時(shí),時(shí)間頻度T(n)也會(huì)不斷
變化。但有時(shí)我們想知道它變化時(shí)呈現(xiàn)什么規(guī)律。為此,我們引入時(shí)間復(fù)雜度概念。 一般情況下,算法
中基本操作重復(fù)執(zhí)行的次數(shù)是問題規(guī)模n的某個(gè)函數(shù),用T(n)表示,若有某個(gè)輔助函數(shù)f(n),使得當(dāng)n趨近
于無窮大時(shí),T(n)/f(n)的極限值為不等于零的常數(shù),則稱f(n)是T(n)的同數(shù)量級(jí)函數(shù)。記作T(n)=O(f(n)),
稱O(f(n)) 為算法的漸進(jìn)時(shí)間復(fù)雜度,簡(jiǎn)稱時(shí)間復(fù)雜度。
空間復(fù)雜度 :
一個(gè)程序的空間復(fù)雜度是指運(yùn)行完一個(gè)程序所需內(nèi)存的大小。利用程序的空間復(fù)雜度,可以對(duì)程序的運(yùn)
行所需要的內(nèi)存多少有個(gè)預(yù)先估計(jì)。一個(gè)程序執(zhí)行時(shí)除了需要存儲(chǔ)空間和存儲(chǔ)本身所使用的指令、常
數(shù)、變量和輸入數(shù)據(jù)外,還需要一些對(duì)數(shù)據(jù)進(jìn)行操作的工作單元和存儲(chǔ)一些為現(xiàn)實(shí)計(jì)算所需信息的輔助
空間。程序執(zhí)行時(shí)所需存儲(chǔ)空間包括以下兩部分。
(1)固定部分。這部分空間的大小與輸入/輸出的數(shù)據(jù)的個(gè)數(shù)多少、數(shù)值無關(guān)。主要包括指令空間(即
代碼空間)、數(shù)據(jù)空間(常量、簡(jiǎn)單變量)等所占的空間。這部分屬于靜態(tài)空間。
(2)可變空間,這部分空間的主要包括動(dòng)態(tài)分配的空間,以及遞歸棧所需的空間等。這部分的空間大小
與算法有關(guān)。
一個(gè)算法所需的存儲(chǔ)空間用f(n)表示。S(n)=O(f(n)) 其中n為問題的規(guī)模,S(n)表示空間復(fù)雜度。 - 常見的數(shù)據(jù)結(jié)構(gòu)有哪些?
數(shù)組, 鏈表, 隊(duì)列, 堆, 棧, 樹, 散列表, 圖 - 鏈表的數(shù)據(jù)結(jié)構(gòu)的特點(diǎn)
鏈表使用不連續(xù)內(nèi)存空間, 空間利用率高
鏈表插入刪除效率高, 查詢修改效率慢
鏈表占用空間大小不固定, 可以擴(kuò)展 - 棧數(shù)據(jù)結(jié)構(gòu)的特點(diǎn)
棧是一種操作受限的線性表,只允許從一端插入和刪除數(shù)據(jù)。擁有后進(jìn)先出的特點(diǎn)
棧的插入和刪除只能在一個(gè)位置上進(jìn)行的表,棧只有進(jìn)棧、出棧兩種操作。前者相當(dāng)于插入,后者相當(dāng)
于刪除最后的元素。
從底層看,棧就是CPU寄存器里的某個(gè)指針?biāo)赶虻囊黄瑑?nèi)存區(qū)域 - 隊(duì)列數(shù)據(jù)結(jié)構(gòu)的特點(diǎn)
隊(duì)列也是一種操作受限的數(shù)據(jù)結(jié)構(gòu),只能從隊(duì)尾的一端進(jìn)行插入,隊(duì)頭的一端進(jìn)行刪除。先進(jìn)先出
FIFO。雙端隊(duì)列不受這種限制,兩端都能進(jìn)行插入和刪除。 - 說一說什么是跳表?Redis為什么用跳表實(shí)現(xiàn)有序集合?
Redis中的有序集合是通過跳表來實(shí)現(xiàn)的,還用到了散列表,它支持的核心操作有:插入一個(gè)數(shù)據(jù)、刪除
一個(gè)數(shù)據(jù)、查找一個(gè)數(shù)據(jù)、按照區(qū)間查找數(shù)據(jù)、迭代輸出有序序列。
按照區(qū)間來查找數(shù)據(jù),這個(gè)操作紅黑樹的效率沒有跳表高,其他幾個(gè)操作紅黑樹都可以完成,時(shí)間復(fù)雜
度都一樣。按照區(qū)間來查找,跳表可以做到O(logn)的時(shí)間復(fù)雜度定位區(qū)間的起點(diǎn),然后在原始鏈表中的
順序往后遍歷即可,非常高效。
跳表使用空間換時(shí)間,通過構(gòu)建多級(jí)索引來提高查詢的效率,實(shí)現(xiàn)基于鏈表的二分查找,跳表是一個(gè)動(dòng)
態(tài)數(shù)據(jù)結(jié)構(gòu),支持快速的插入、刪除、查找操作,時(shí)間復(fù)雜度都是O(logn)。
跳表的空間復(fù)雜度是O(n),通過改變索引構(gòu)建策略 - 散列表的數(shù)據(jù)結(jié)構(gòu)特點(diǎn)
哈希表的查找效率主要取決于構(gòu)造哈希表時(shí)選取的哈希函數(shù)和處理沖突的方法。
在各種查找方法中,平均査找長(zhǎng)度與結(jié)點(diǎn)個(gè)數(shù)n無關(guān)的查找方法是哈希表查找法。
哈希函數(shù)取值是否均勻是評(píng)價(jià)哈希函數(shù)好壞的標(biāo)準(zhǔn)。
哈希存儲(chǔ)方法只能存儲(chǔ)數(shù)據(jù)元素的值,不能存儲(chǔ)數(shù)據(jù)元素之間的關(guān)系。 - 二叉樹數(shù)據(jù)數(shù)據(jù)結(jié)構(gòu)特點(diǎn)
每個(gè)結(jié)點(diǎn)最多有兩棵子樹,所以二叉樹中不存在度大于2的結(jié)點(diǎn)。注意不是只有兩棵子樹,而是最多有。
沒有子樹或者有一棵子樹都是可以的。
左子樹和右子樹是有順序的,次序不能任意顛倒
即使樹中某結(jié)點(diǎn)只有一棵子樹,也要區(qū)分它是左子樹還是右子樹。 - 圖數(shù)據(jù)結(jié)構(gòu)特點(diǎn)
無向圖 : 每個(gè)節(jié)點(diǎn)都沒有方向,邊上可能有權(quán)重
有向圖 : 每個(gè)節(jié)點(diǎn)是有方向的的
有向無環(huán)圖 : 可以描述任務(wù)之間的關(guān)系 - 堆數(shù)據(jù)結(jié)構(gòu)特點(diǎn)
將根節(jié)點(diǎn)最大的堆叫大頂堆或者大根堆,根節(jié)點(diǎn)最小的堆叫小頂堆或小根堆。
常見的堆有二叉堆,斐波拉契堆。
如果是大頂堆,常見操作及時(shí)間復(fù)雜度:
查找最大值:o(1)
刪除最大值:o(logn)
添加值:o(1)或o(logn) - 大頂堆和小頂堆的區(qū)別?
大頂堆 : 根結(jié)點(diǎn)(亦稱為堆頂)的關(guān)鍵字是堆里所有結(jié)點(diǎn)關(guān)鍵字中最大者,稱為大頂堆。大根堆要求根
節(jié)點(diǎn)的關(guān)鍵字既大于或等于左子樹的關(guān)鍵字值,又大于或等于右子樹的關(guān)鍵字值。
小頂堆 : 根結(jié)點(diǎn)(亦稱為堆頂)的關(guān)鍵字是堆里所有結(jié)點(diǎn)關(guān)鍵字中最小者,稱為小頂堆。小根堆要求根
節(jié)點(diǎn)的關(guān)鍵字既小于或等于左子樹的關(guān)鍵字值,又小于或等于右子樹的關(guān)鍵字值。 - 說一說常見的排序算法和對(duì)應(yīng)的時(shí)間復(fù)雜度
- 用Java代碼實(shí)現(xiàn)冒泡排序和快速排序
package 冒泡排序;
import java.util.Arrays;
/**
* 冒泡排序
* @author mmz
*/
public class BubbleSort {
public static void BubbleSort(int[] arr) {
int temp;//定義一個(gè)臨時(shí)變量
for(int i=0;i<arr.length-1;i++){//冒泡趟數(shù)
for(int j=0;j<arr.length-i-1;j++){
if(arr[j+1]<arr[j]){
temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
}
public static void main(String[] args) {
int arr[] = new int[]{1,6,2,2,5};
BubbleSort.BubbleSort(arr);
System.out.println(Arrays.toString(arr));
}
}
// 快速排序
public class QuickSort{
public static void quickSort(int[] arr,int low,int high){
int i,j,temp,t;
if(low>high){
return;
}
i=low;
j=high;
//temp就是基準(zhǔn)位
temp = arr[low];
while (i<j) {
//先看右邊,依次往左遞減
while (temp<=arr[j]&&i<j) {
j--;
}
//再看左邊,依次往右遞增
while (temp>=arr[i]&&i<j) {
i++;
}
//如果滿足條件則交換
if (i<j) {
t = arr[j];
arr[j] = arr[i];
arr[i] = t;
}
}
//最后將基準(zhǔn)為與i和j相等位置的數(shù)字交換
arr[low] = arr[i];
arr[i] = temp;
//遞歸調(diào)用左半數(shù)組
quickSort(arr, low, j-1);
//遞歸調(diào)用右半數(shù)組
quickSort(arr, j+1, high);
}
public static void main(String[] args){
int[] arr = {10,7,2,4,7,62,3,4,2,1,8,9,19};
quickSort(arr, 0, arr.length-1);
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
}
}
- 100萬(wàn)用戶如何根據(jù)年齡排序?
可以通過桶排序,基數(shù)排序,計(jì)數(shù)排序
桶排序:桶排序要把數(shù)據(jù)進(jìn)行劃分到m個(gè)桶內(nèi),希望的是桶內(nèi)數(shù)據(jù)是均勻的,并且桶與桶之間有著天然
的大小順序。極端情況下時(shí)間復(fù)雜度會(huì)退化為O(nlog n); 比較適合外部排序。在進(jìn)行劃分桶數(shù)據(jù)的時(shí)
候,可能存在桶數(shù)據(jù)不均勻的情況,可以選擇在多的數(shù)據(jù)桶進(jìn)行繼續(xù)劃分桶,直到桶數(shù)據(jù)可以加載到內(nèi)
存中為止。
計(jì)數(shù)排序:把一系列的數(shù)字統(tǒng)計(jì)個(gè)數(shù)放在數(shù)組內(nèi)A。 依次累加,統(tǒng)計(jì)結(jié)果還是在同一個(gè)數(shù)組中存放A。
臨時(shí)數(shù)組C存放排序后的結(jié)果。 遍歷最初時(shí)的數(shù)據(jù)B,從A中找到該值。 A數(shù)組中的值-1就是數(shù)據(jù)B在臨時(shí)
數(shù)組C存放的位置。 把A數(shù)組中的值減1. 循環(huán)這個(gè)過程。該計(jì)數(shù)規(guī)則只適合數(shù)據(jù)范圍不大的情景 - 深度優(yōu)先和廣度優(yōu)先搜索算法?
廣度優(yōu)先搜索(Breadth-First-Search),一般簡(jiǎn)稱為 BFS。直觀地講,它其實(shí)就是一種地毯式層層推進(jìn)
的搜索策略,即先查找離起始頂點(diǎn)最近的,然后是次近的,依次往外搜索。
visited,布爾數(shù)組,記錄頂點(diǎn)是否已經(jīng)被訪問過,訪問過則為真,沒有訪問過則為假,這里用 0 和 1 表
示。
vertex,記錄上一層的頂點(diǎn),也即已經(jīng)被訪問但其相連的頂點(diǎn)還沒有被訪問的頂點(diǎn)。當(dāng)一層的頂點(diǎn)搜索
完成后,我們還需要通過這一層的頂點(diǎn)來遍歷與其相連的下一層頂點(diǎn),這里我們用隊(duì)列來記錄上一層的
頂點(diǎn)。
prev,記錄搜索路徑,保存的是當(dāng)前頂點(diǎn)是從哪個(gè)頂點(diǎn)遍歷過來的,比如 prev[4] = 1,說明頂點(diǎn) 4 是通
過頂點(diǎn) 1 而被訪問到的。
深度優(yōu)先搜索(Depth-First-Search),簡(jiǎn)稱 DFS,最直觀的例子就是走迷宮。
假設(shè)你站在迷宮的某個(gè)分岔路口,你想找到出口。你隨意選擇一個(gè)岔路口來走,走著走著發(fā)現(xiàn)走不通的
時(shí)候就原路返回到上一個(gè)分岔路口,再選擇另一條路繼續(xù)走,直到找到出口,這種走法就是深度優(yōu)先搜
索的策略。
深度優(yōu)先搜索用的是一種比較著名的思想——回溯思想,這種思想非常適合用遞歸來實(shí)現(xiàn)。深度優(yōu)先搜
索的代碼里面有幾個(gè)和廣度優(yōu)先搜索一樣的部分 visited、prev 和 Print() 函數(shù),它們的作用也都是一樣
的。此外,還有一個(gè)特殊的 found 變量,標(biāo)記是否找到終止頂點(diǎn),找到之后我們就可以停止遞歸不用再
繼續(xù)查找了。 - 如何快速獲取Top10熱門搜索關(guān)鍵詞?
使用優(yōu)先級(jí)隊(duì)列實(shí)現(xiàn) : 優(yōu)先級(jí)隊(duì)列,顧名思義,它首先應(yīng)該是一個(gè)隊(duì)列。隊(duì)列最大的特性就是先進(jìn)先
出。不過,在優(yōu)先級(jí)隊(duì)列中,數(shù)據(jù)的出隊(duì)順序不是先進(jìn)先出,而是按照優(yōu)先級(jí)來,優(yōu)先級(jí)最高的,最先
出隊(duì) - 單向鏈表反轉(zhuǎn)如何實(shí)現(xiàn)!
①.迭代反轉(zhuǎn)鏈表:從當(dāng)前鏈表的首元節(jié)點(diǎn)開始,一直遍歷至鏈表的最后一個(gè)節(jié)點(diǎn),這期間會(huì)逐個(gè)改變所遍歷到
的節(jié)點(diǎn)的指針域,另其指向前一個(gè)節(jié)點(diǎn)。
public static Node reverse2(Node head) {
if (head == null || head.getNext() == null) {
return head;
}
// 上一結(jié)點(diǎn)等于頭節(jié)點(diǎn)
Node pre = head;
// 當(dāng)前結(jié)點(diǎn)
Node cur = head.getNext();
// 臨時(shí)結(jié)點(diǎn),用于保存當(dāng)前結(jié)點(diǎn)的指針域(即下一結(jié)點(diǎn))
Node tmp;
// 當(dāng)前結(jié)點(diǎn)為null,說明位于尾結(jié)點(diǎn)
while (cur != null) {
//臨時(shí)節(jié)點(diǎn), 保存當(dāng)前節(jié)點(diǎn)下一個(gè)節(jié)點(diǎn)
tmp = cur.getNext();
// 反轉(zhuǎn)指針域的指向
cur.setNext(pre);
// 指針往下移動(dòng)
pre = cur;
cur = tmp;
}
// 最后將原鏈表的頭節(jié)點(diǎn)的指針域置為null,還回新鏈表的頭結(jié)點(diǎn),即原鏈表的尾結(jié)點(diǎn)
head.setNext(null);
return pre;
}
②.遞歸反轉(zhuǎn)鏈表:從鏈表的尾節(jié)點(diǎn)開始,依次向前遍歷,遍歷過程依次改變各節(jié)點(diǎn)的指向,即另其指向前一個(gè)
節(jié)點(diǎn)。
public static Node Reverse1(Node head) {
// head看作是前一結(jié)點(diǎn),head.getNext()是當(dāng)前結(jié)點(diǎn),reHead是反轉(zhuǎn)后新鏈表的頭結(jié)點(diǎn)
if (head == null || head.getNext() == null) {
// 若為空鏈或者當(dāng)前結(jié)點(diǎn)在尾結(jié)點(diǎn),則直接還回
return head;
}
// 先反轉(zhuǎn)后續(xù)節(jié)點(diǎn)head.getNext()
Node reHead = Reverse1(head.getNext());
// 將當(dāng)前結(jié)點(diǎn)的指針域指向前一結(jié)點(diǎn)
head.getNext().setNext(head);
// 前一結(jié)點(diǎn)的指針域令為null;
head.setNext(null);
// 反轉(zhuǎn)后新鏈表的頭結(jié)點(diǎn)
return reHead;
}
③.頭插法反轉(zhuǎn)鏈表:在原有鏈表的基礎(chǔ)上,依次將位于鏈表頭部的節(jié)點(diǎn)摘下,然后采用從頭部插入的方式生成
一個(gè)新鏈表,則此鏈表即為原鏈表的反轉(zhuǎn)。```java
public ListNode reverseList(ListNode head) {
ListNode dum = new ListNode();
ListNode pre = head;
while (pre!=null){
ListNode pNext = pre.next;
pre.next = dum.next;
dum.next = pre;
pre = pNext;
}
head = dum.next;
return head;
}
- 如何判斷鏈表是否有環(huán)?
使用追趕的方法,設(shè)定兩個(gè)指針slow、fast
從頭指針開始,每次fast指針前進(jìn)1步, slow指針前進(jìn)2步。
如存在環(huán),則兩者相遇;如不存在環(huán),fast遇到NULL退出 - 如何找到單向鏈表的中間元素
設(shè)定兩個(gè)指針slow、fast
從頭指針開始,每次fast指針前進(jìn)1步, slow指針前進(jìn)2步
當(dāng)fast指針走到鏈表結(jié)尾位置, slow指針?biāo)幬恢玫脑鼐褪侵虚g元素.
十二、 工具使用 【3道】
- 在Linux中如何查看tomcat日志, 命令是什么?
tail -f tomcat日志所在路徑 - maven推送自己寫的工具包項(xiàng)目到公司maven私服供其他組員使用的命令?
mvn deploy 可以推送到公司內(nèi)部的私服服務(wù)器,
其他組員maven的settings.xml中可以配置公司私服的地址,
然后項(xiàng)目的pom文件中寫上這個(gè)jar包的坐標(biāo), 就會(huì)自動(dòng)從公司私服下載這個(gè)依賴包, 項(xiàng)目中就可以直接使
用. - 使用maven項(xiàng)目如何打包部署
使用mvn package命令進(jìn)行打包
War包項(xiàng)目打包后將打包好的文件上傳到服務(wù)器tomcat的webapps目錄, 重啟tomcat
Springboot項(xiàng)目打包后是jar包, 將打包好的文件上傳到服務(wù)器, 執(zhí)行java -jar xxx.jar啟動(dòng)運(yùn)行項(xiàng)目.