1.Java 類的加載流程是怎樣的?什么是雙親委派機(jī)制?
類加載的主要任務(wù):根據(jù)一個(gè)類的全限定名讀取該類的二進(jìn)制字節(jié)流到JVM內(nèi)部,然后轉(zhuǎn)換為一個(gè)對(duì)應(yīng)的java.lang.Class對(duì)象實(shí)例。
類從被加載到JVM中到卸載總共經(jīng)歷7個(gè)階段:加載、驗(yàn)證、準(zhǔn)備、解析、初始化、使用和卸載。
加載:類加載階段是由類加載器根據(jù)類文件全限定類名,來讀取這個(gè)類文件的二進(jìn)制字節(jié)流到JVM中,并存儲(chǔ)在內(nèi)存的方法區(qū)中,然后將其裝換為一個(gè)對(duì)應(yīng)的java.lang.Object對(duì)象實(shí)例。
驗(yàn)證:驗(yàn)證是否符合class文件規(guī)范;檢查final類是否有子類;檢查final方法是否被子類重寫;檢查父類和子類聲明方法是否兼容等。
準(zhǔn)備:為類中static變量分配內(nèi)存空間并初始化;被final修飾的靜態(tài)變量會(huì)直接賦予原值。
解析:將常量池中所有符號(hào)引用轉(zhuǎn)換為直接飲用,得到類、字段或者方法在內(nèi)存中的指針或偏移量,以便直接調(diào)用。
初始化:賦值static變量,執(zhí)行static塊;先初始化父類再初始化子類。
雙親委派:當(dāng)某個(gè)類加載器需要加載類文件時(shí),會(huì)首先把這個(gè)任務(wù)委托給他的上級(jí)類加載器,遞歸這個(gè)操作。如果上級(jí)的類加載器無法加載時(shí),將這個(gè)任務(wù)退回給下一級(jí)類加載器。判斷任何一級(jí)加載器加載過類文件直接返回。作用是保證每個(gè)類文件只被加載一次。
2.簡(jiǎn)述 MVC 與 MVVM 的區(qū)別,MVVM 的優(yōu)點(diǎn)是什么?
VM:在前端頁面中,把Model用純JavaScript對(duì)象表示,View負(fù)責(zé)顯示,兩者做到了最大限度的分離,把Model和View關(guān)聯(lián)起來的就是ViewModel。
區(qū)別:MVC和MVVM的區(qū)別并不是VM瓦全取代C,只是在MVC的基礎(chǔ)上增加了一層VM,弱化了C的概念,VM存在的目的在于抽離C中展示的業(yè)務(wù)邏輯,而不是替代C,ViewModel負(fù)責(zé)把Model的數(shù)據(jù)同步到View顯示出來,還負(fù)責(zé)把View的修改同步回Model。
優(yōu)點(diǎn):
低耦合:MVVM模式中,數(shù)據(jù)是獨(dú)立于UI,VM只負(fù)責(zé)處理和提供數(shù)據(jù)。
自動(dòng)同步數(shù)據(jù):VM通過雙向數(shù)據(jù)綁定把V和M連接起來,V和M兩者可以自動(dòng)同步。
可重用:可以封裝視圖邏輯,其他V也可以重用。
獨(dú)立開發(fā):可以獨(dú)立開發(fā)VM,設(shè)計(jì)人員可專注于頁面設(shè)計(jì)。
可測(cè)試:VM處理數(shù)據(jù)和業(yè)務(wù)邏輯,V關(guān)注UI,方便單獨(dú)測(cè)試。
3.簡(jiǎn)述 Netty 線程模型,Netty 為什么如此高效?
Netty是一款基于NIO(Nonblocking I/O,非阻塞I/O)開發(fā)的網(wǎng)絡(luò)通信框架,對(duì)比與BIO(Blocking I/O,阻塞I/O),并發(fā)性能得到很大提升。
NIO的單線程處理連接的數(shù)量比BIO高很多,原因就是Selector。
當(dāng)建立一個(gè)連接后,第一步是接收完客戶端發(fā)過來的全部數(shù)據(jù),第二步是服務(wù)端處理完請(qǐng)求業(yè)務(wù)之后返回response給客戶端。NIO和BIO的區(qū)別主要在于第一步。
BIO中,等待客戶端發(fā)數(shù)據(jù)過程是阻塞的,造成一個(gè)線程只能處理一個(gè)請(qǐng)求,而機(jī)器支持的最大線程數(shù)有限,所以并發(fā)能力差。
NIO中,一個(gè)線程接收數(shù)據(jù)不會(huì)阻塞,而是將請(qǐng)求交給Selector,Selector會(huì)不斷遍歷socket狀態(tài),一旦建立完成就會(huì)交給線程處理請(qǐng)求,這樣能讓一個(gè)線程處理多個(gè)請(qǐng)求。
4.HashMap 實(shí)現(xiàn)原理,為什么使用紅黑樹?
HashMap是使用頻率最高的處理鍵值對(duì)的數(shù)據(jù)結(jié)構(gòu),無序、允許插入null的key和value。
HashMap基于數(shù)組+鏈表實(shí)現(xiàn),使用拉鏈法處理碰撞,在JDK8中,當(dāng)鏈表長(zhǎng)度大于8(默認(rèn)值)時(shí)轉(zhuǎn)為紅黑樹,當(dāng)長(zhǎng)度小于6轉(zhuǎn)為鏈表。
HashMap有一個(gè)Node<K,V>[] table字段,即哈希桶數(shù)組,數(shù)組元素是Node對(duì)象
哈希桶會(huì)在首次使用時(shí)初始化,默認(rèn)大小是16,并根據(jù)需要調(diào)整大小,大小總為2的n次冪。如果構(gòu)造函數(shù)傳入大小不是2的n次冪,那么初始化時(shí)會(huì)根據(jù)算法得出最接近且大于這個(gè)值的2的次冪的值。
HashMap屬性字段
影響HashMap性能的主要參數(shù)是:初始容量和負(fù)載因子。當(dāng)數(shù)組元素?cái)?shù)量超過容量值,會(huì)發(fā)生擴(kuò)容,容量為原來的兩倍,并對(duì)key重新散列。
初始容量過小會(huì)多次觸發(fā)擴(kuò)容和 rehash,所以預(yù)分配一個(gè)足夠大的容量更加有效
負(fù)載因子默認(rèn)值是 0.75f,它是對(duì)時(shí)間和空間成本的一個(gè)很好的平衡,一般不用修改,較高的值會(huì)減少空間開銷,但會(huì)增加查找的成本
不管多么合理的散列算法,也免不了鏈表過長(zhǎng)的情況,從而影響 HashMap 的性能,所以,JDK8 在鏈表長(zhǎng)度大于 8 時(shí),將其轉(zhuǎn)為紅黑樹,以利用紅黑樹快速增刪改查的特點(diǎn)。
先了解一下二叉樹
(1)左子樹上所有結(jié)點(diǎn)的值均小于或等于它的根結(jié)點(diǎn)的值。
(2)右子樹上所有結(jié)點(diǎn)的值均大于或等于它的根結(jié)點(diǎn)的值。
(3)左、右子樹也分別為二叉排序樹
二叉樹查找原理:找到數(shù)組中間位置元素v,將數(shù)組分為>v和<v兩部分,然后將v和要查找的數(shù)進(jìn)行比較,小于找左邊,大于找右邊,直至找到元素,查找次數(shù)是目標(biāo)元素所在二叉樹的層數(shù)。
紅黑樹
紅黑樹是一種自平衡的二叉樹。自平衡就是對(duì)HashMap鏈表可能很長(zhǎng)最初的優(yōu)化,平衡能保證單側(cè)樹不會(huì)過長(zhǎng),導(dǎo)致查詢效率低,這也是相比于二叉樹的優(yōu)勢(shì)。
性質(zhì):
1.包含二叉樹的性質(zhì)。
2.節(jié)點(diǎn)只能是紅色或黑色。
3.根結(jié)點(diǎn)是黑色。
4.每個(gè)葉節(jié)點(diǎn)是黑色(NIL節(jié)點(diǎn),空節(jié)點(diǎn))。
5.從每個(gè)葉節(jié)點(diǎn)到根結(jié)點(diǎn)路徑上,不會(huì)出現(xiàn)兩個(gè)相連的紅色節(jié)點(diǎn)。
6.從任意節(jié)點(diǎn)到葉子節(jié)點(diǎn)的所有路徑上,黑色節(jié)點(diǎn)數(shù)量相同。
紅黑樹通過“變色”和“旋轉(zhuǎn)”維護(hù)其平衡。
HashMap中怎么使用紅黑樹
JDK7中HashMap利用鏈表解決沖突,這可能導(dǎo)致鏈表過長(zhǎng),由于每次put/set都會(huì)遍歷鏈表,導(dǎo)致效率下降。
HashMap中的紅黑樹節(jié)點(diǎn)用TreeNode類
TreeNode和Entry都是Node的子類,也就說Node可能是鏈表結(jié)構(gòu),也可能是紅黑樹結(jié)構(gòu)。
5.Java 中接口和抽象類的區(qū)別
定義
抽象類:抽象類必須用abstract修飾,子類必須實(shí)現(xiàn)抽象類中的抽象方法,如果未實(shí)現(xiàn)的,那么子類葉必須用abstract修飾。抽象類的默認(rèn)權(quán)限是public,也可以聲明為protected,如果定義private,那么子類無法繼承。抽象類不能實(shí)例化。
接口:接口中的變量隱式的使用public static final修飾,并且必須初始化。方法隱式的使用public abstract修飾,只能是public否則會(huì)編譯報(bào)錯(cuò)。從JDK8開始接口中方法允許有默認(rèn)實(shí)現(xiàn)。
區(qū)別
1.抽象類只能繼承一次,但是可以實(shí)現(xiàn)多個(gè)接口。
2.接口和抽象類必須實(shí)現(xiàn)其中所有方法,抽象類中如果有為實(shí)現(xiàn)的方法,那么子類必須也定義為抽象方法。抽象類中可以有具體方法。
3.接口中的變量必須用public static final定義,并且必須初始化,實(shí)現(xiàn)類不能修改,也不能重新定義。
4.接口中的方法只能是public abstract,不能是static,接口中的方法不允許實(shí)現(xiàn)類重寫,抽象類可以有static方法。
6.成員變量和方法的區(qū)別?
成員變量作用域整個(gè)類,類當(dāng)中任何方法都可以訪問。方法作用域也是整個(gè)類,其他方法可以調(diào)用,但是方法中定的變量作用域只在方法內(nèi)。
7.CAS 實(shí)現(xiàn)原理是什么?
在計(jì)算機(jī)科學(xué)中,比較和交換(Compare And Swap)是用于實(shí)現(xiàn)多線程同步的原子指令。它將內(nèi)存中的值與給定的值對(duì)比,相同情況下將內(nèi)存中的值修改為新值,否則更新失敗。比如線程1更新時(shí)發(fā)現(xiàn),線程2修改過內(nèi)存中的值,那么線程1會(huì)讀取內(nèi)存中的最新值(volatitl類型)然后再更新,這保證線程1不會(huì)死循環(huán)。
8.ThreadLocal 實(shí)現(xiàn)原理是什么?
ThreadLocal俗稱線程本地變量。ThreadLocal為每個(gè)線程維護(hù)了一份變量,每個(gè)線程可以訪問自己的變量副本,不涉及變量線程之間共享。
ThreadLocal維護(hù)了一個(gè)ThreadLocalMap,是Thread的一個(gè)屬性,key是ThreadLocal變量,value是線程本地的數(shù)據(jù)。
由于ThreadLocalMap維護(hù)的Entry繼承自WeakReference<ThreadLocal>,也就是說ThreadLocal自身的回收不受ThreadLocalMap的這個(gè)弱引用的影響。但是Entry被ThreadLocalMap強(qiáng)引用,所以Entry不能被GC。ThreadLocalMap的expungeStaleEntry這個(gè)方法,這個(gè)方法在ThreadLocalMap get、set、remove、rehash等方法都會(huì)調(diào)用到。方法有兩個(gè)作用:第一將remove的Entry置空,第二找到已經(jīng)被GC的ThreadLocal,然后清理掉ThreadLocalMap對(duì)Entry的引用。這樣Entry就會(huì)被后續(xù)GC回收。
如果數(shù)據(jù)初始化好之后,一直不調(diào)用get、set等方法,這樣Entry就一直不能回收,導(dǎo)致內(nèi)存泄漏。所以一旦數(shù)據(jù)不使用最好主動(dòng)remove。
9.Java 常見鎖有哪些?ReentrantLock 是怎么實(shí)現(xiàn)的?? ?
樂觀鎖和悲觀鎖
對(duì)于同一個(gè)數(shù)據(jù)的并發(fā)操作,悲觀鎖認(rèn)為自己在使用數(shù)據(jù)的時(shí)候一定有其他線程來修改數(shù)據(jù),因此在獲取數(shù)據(jù)的時(shí)候會(huì)先加鎖,確保數(shù)據(jù)不會(huì)被別的線程修改。Java中,synchronized和Lock實(shí)現(xiàn)的都是悲觀鎖。
樂觀鎖認(rèn)為自己在使用數(shù)據(jù)的時(shí)候,不會(huì)有其他線程修改數(shù)據(jù),所以不會(huì)添加鎖,只是在更新數(shù)據(jù)的時(shí)候去判斷之前有沒有別的線程修改了這個(gè)數(shù)據(jù)。如果沒有被更新,當(dāng)前線程將自己修改的數(shù)據(jù)成功寫入。如果數(shù)據(jù)被修改了,則可能報(bào)錯(cuò)或者發(fā)起重試。樂觀鎖在Java中是通過無鎖編程實(shí)現(xiàn),最長(zhǎng)采用的是CAS算法,Java原子類中的遞增操作就是通過CAS自旋實(shí)現(xiàn)的。
悲觀鎖適合寫操作多的場(chǎng)景,樂觀鎖適合讀操作多的場(chǎng)景。
自旋鎖和適應(yīng)性自旋鎖
自旋鎖本身有一定缺陷,它不能代替阻塞。自旋鎖雖然避免了切換線程的開銷,但是會(huì)占用處理器的時(shí)間。如果鎖被占用的時(shí)間很短,自旋等待的效果就會(huì)很好。如果鎖被占用的時(shí)間很長(zhǎng),那自旋的線程只會(huì)浪費(fèi)CPU資源。所以自旋等待的時(shí)間必須有一定的限度,如果自旋超過了一定次數(shù)(默認(rèn)10次,可以用-XX:PreBlockSpin來更改)沒有成功獲的鎖,就應(yīng)當(dāng)掛起線程。
CAS實(shí)現(xiàn)的原理也是資源鎖,AtomicInteger中進(jìn)行自增操作,代碼中的do-while循環(huán)就是自旋操作,如果修改失敗則通過循環(huán)來執(zhí)行自旋,知道修改成功。
JDK6中引入了適應(yīng)性自旋鎖。自適應(yīng)意味著自旋等待次數(shù)不固定,而是由前一次在同一個(gè)鎖上的自旋時(shí)間及鎖的擁有者的狀態(tài)來決定。如果在同一個(gè)兌現(xiàn)鎖上,自旋等待成功獲取鎖,并且線程正在運(yùn)行中,那么虛擬機(jī)認(rèn)為這次自旋獲得鎖很可能成功,所以允許自旋等待更長(zhǎng)時(shí)間。如果自旋很少成功獲得鎖,那么以后嘗試獲得鎖很可能放棄自旋,直接將阻塞線程,避免浪費(fèi)CPU資源。
無鎖、偏向鎖、輕量級(jí)鎖、重量級(jí)鎖
這四種鎖是指鎖的狀態(tài),專門針對(duì)synchronized。
無鎖:所有線程都能訪問并修改同一個(gè)資源,但同時(shí)只有一個(gè)線程能修改成功,參考CAS原理;
偏向鎖:當(dāng)一段同步代碼一直被一個(gè)線程鎖訪問,不存在線程競(jìng)爭(zhēng),那么該線程就會(huì)自動(dòng)獲得鎖降低獲取鎖的代價(jià)。
輕量級(jí)鎖:當(dāng)有線程競(jìng)爭(zhēng)時(shí),偏向鎖會(huì)升級(jí)為輕量級(jí)鎖,其他線程會(huì)通過自旋形式嘗試獲取鎖,不會(huì)阻塞,從而提高性能。
重量級(jí)鎖:如果自旋一定次數(shù)還未獲得鎖,輕量級(jí)鎖就會(huì)升級(jí)重量級(jí)。將擁有所以外的所有線程阻塞。
公平鎖和非公平鎖
公平鎖:多個(gè)線程按照申請(qǐng)鎖順序來獲取鎖,線程直接進(jìn)入隊(duì)列中排隊(duì),隊(duì)列中的第一個(gè)線程才能獲得鎖。公平鎖的優(yōu)點(diǎn)是不會(huì)讓線程一直等待下去,缺點(diǎn)是整體吞吐效率比非公平鎖低,等待隊(duì)列中第一個(gè)線程以外的線程都要阻塞,CPU喚醒阻塞線程的開銷比非公平鎖大。
非公平鎖:多個(gè)線程加鎖時(shí)直接嘗試獲取鎖,獲取不到才到等待隊(duì)列的隊(duì)尾等待。如果此時(shí)剛好能獲得鎖,那么這個(gè)線程可以無需阻塞直接獲得鎖,所以非公平鎖有可能出現(xiàn)后申請(qǐng)鎖得線程先獲得鎖。優(yōu)點(diǎn)是可以減少喚起線程的CPU開銷,整體吞吐效率高,缺點(diǎn)是處于等待隊(duì)列的線程可能會(huì)餓死,或者很久才能獲得鎖。ReentrantLock默認(rèn)是非公平鎖,可通過構(gòu)造函數(shù)參數(shù)設(shè)置公平鎖。
可重入鎖和非可重入鎖
可重入鎖:又名遞歸鎖,是指在同一個(gè)線程在外層方法獲得鎖的時(shí)候,再進(jìn)入該線程的內(nèi)層方法會(huì)自動(dòng)獲得鎖(前提是鎖對(duì)象必須是同一個(gè)對(duì)象或者class),不會(huì)因?yàn)橹矮@得過還沒釋放而阻塞。Java中ReentrantLock和synchronized都是可重入鎖,可重入的在一定成都可避免死鎖:
在上面的代碼中,類中的兩個(gè)方法都是被內(nèi)置鎖synchronized修飾的,doSomething()方法中調(diào)用doOthers()方法。因?yàn)閮?nèi)置鎖是可重入的,所以同一個(gè)線程在調(diào)用doOthers()時(shí)可以直接獲得當(dāng)前對(duì)象的鎖,進(jìn)入doOthers()進(jìn)行操作。
如果是一個(gè)不可重入鎖,那么當(dāng)前線程在調(diào)用doOthers()之前需要將執(zhí)行doSomething()時(shí)獲取當(dāng)前對(duì)象的鎖釋放掉,實(shí)際上該對(duì)象鎖已被當(dāng)前線程所持有,且無法釋放。所以此時(shí)會(huì)出現(xiàn)死鎖。
非可重入鎖相反。
獨(dú)享鎖和共享鎖
獨(dú)享鎖:也叫排他鎖,是指該鎖一次只能被一個(gè)線程所持有。如果線程T對(duì)數(shù)據(jù)A加上排它鎖后,則其他線程不能再對(duì)A加任何類型的鎖。獲得排它鎖的線程即能讀數(shù)據(jù)又能修改數(shù)據(jù)。JDK中的synchronized和JUC中Lock的實(shí)現(xiàn)類就是互斥鎖。
共享鎖:是指該鎖可被多個(gè)線程所持有。如果線程T對(duì)數(shù)據(jù)A加上共享鎖后,則其他線程只能對(duì)A再加共享鎖,不能加排它鎖。獲得共享鎖的線程只能讀數(shù)據(jù),不能修改數(shù)據(jù)。ReentrantReadWriteLock有兩把鎖:ReadLock和WriteLock,讀鎖和寫鎖的加鎖方式不一樣。讀鎖是共享鎖,寫鎖是獨(dú)享鎖讀鎖的共享鎖可保證并發(fā)讀非常高效,而讀寫、寫讀、寫寫的過程互斥,因?yàn)樽x鎖和寫鎖是分離的。
10.GC回收算法
標(biāo)記-清除法:標(biāo)記沒用的對(duì)象,然后一個(gè)一個(gè)回收
? ? 缺點(diǎn):標(biāo)記和清除兩個(gè)過程效率不高,產(chǎn)生內(nèi)存隨便導(dǎo)致大對(duì)象需要非配內(nèi)存空間時(shí)無法找到連續(xù)內(nèi)存而需要進(jìn)行一次GC
復(fù)制法:將內(nèi)存空間平均分成兩份,當(dāng)一塊區(qū)域用完之后,將有用的對(duì)象復(fù)制到另一塊區(qū)域,然后把已使用的區(qū)域一次性清理
? ? 缺點(diǎn):內(nèi)存空間變小
標(biāo)記-整理法:標(biāo)記處沒用的對(duì)象,讓活著的對(duì)象向一邊移動(dòng),然后直接清理掉邊界以外的垃圾
? ? 有點(diǎn):解決了標(biāo)記-清除法導(dǎo)致的內(nèi)存碎片問題和存活率較高時(shí)復(fù)制算法效率低的問題
分代回收法:根據(jù)對(duì)象存活周期的不同將內(nèi)存劃分為幾塊,一般是新生代和老年代,新生代基本用復(fù)制算法,老年代采用標(biāo)記-整理算法。
11.hashMap 1.7 / 1.8 的實(shí)現(xiàn)區(qū)別
·put時(shí)出現(xiàn)hash沖突,1.7把數(shù)據(jù)存放在鏈表里,1.8是先存放在鏈表中,鏈表長(zhǎng)度超過8轉(zhuǎn)為紅黑樹
·1.7的擴(kuò)容條件是數(shù)組長(zhǎng)度大于閾值且存在hash沖突,1.8擴(kuò)容條件是數(shù)組長(zhǎng)度大于閾值或鏈表轉(zhuǎn)為空黑樹
使用 hashmap 時(shí),一開始最好指定下長(zhǎng)度,畢竟擴(kuò)容時(shí),需要重新根據(jù) key 計(jì)算數(shù)組下標(biāo),還是很影響效率的。
12.Java 如何高效進(jìn)行數(shù)組拷貝
JDK中提供了一個(gè)高效的API來實(shí)現(xiàn)數(shù)組復(fù)制。
System.arraycopy(array, 0, arraydst, 0, size);
System.arraycopuy()函數(shù)是native函數(shù),通常native函數(shù)的性能要優(yōu)于普通函數(shù)
13.簡(jiǎn)述 Spring 的初始化流程
加載流程是:初始化環(huán)境 --> 加載配置文件 --> 實(shí)例化Bean --> 調(diào)用Bean顯示信息
a、加載Spring配置信息,并標(biāo)記配置文件的資源。
b、讀取配置文件資源,然后進(jìn)行解析。解析配置文件中每一個(gè)<bean>標(biāo)簽。
d、進(jìn)行Bean實(shí)例化操作,完成Bean屬性的設(shè)置。
e、對(duì)完成屬性設(shè)置的Bean進(jìn)行后續(xù)加工,直接裝配出一個(gè)準(zhǔn)備就緒的Bean。
14.簡(jiǎn)述 synchronized,volatile,可重入鎖的不同使用場(chǎng)景及優(yōu)缺點(diǎn)
synchronized:適用于同步代碼執(zhí)行時(shí)間較長(zhǎng);優(yōu)點(diǎn)是線程不會(huì)進(jìn)行自旋,不占用CPU資源;缺點(diǎn)是線程阻塞,響應(yīng)時(shí)間長(zhǎng)
volatile:單例模式的雙重檢查;線程能直接讀取內(nèi)存中最新的數(shù)據(jù);不能保證線程安全
可重入鎖:用在定時(shí)任務(wù),如果定時(shí)任務(wù)執(zhí)行時(shí)間超過下次計(jì)劃執(zhí)行時(shí)間,確保只有一個(gè)線程正在執(zhí)行;優(yōu)點(diǎn)是有公平和非公平兩種模式,還可以避免死鎖;缺點(diǎn)是需要在finally塊中釋放鎖
15.Java 線程間有多少通信方式?
a 、通過synchronized實(shí)現(xiàn):兩個(gè)線程鎖定同一個(gè)對(duì)象,線程1執(zhí)行對(duì)象的A方法,線程2執(zhí)行對(duì)象的B方法,線程2等線程線程1執(zhí)行完A方法才能執(zhí)行
b、通過while循環(huán)實(shí)現(xiàn):兩個(gè)線程鎖定同一個(gè)對(duì)象,線程1修改對(duì)象的屬性,線程2檢查對(duì)象屬性
c、通過wait/notify實(shí)現(xiàn):生產(chǎn)者消費(fèi)者例子。生產(chǎn)者往容器中添加,消費(fèi)者從容器中往外拿,當(dāng)容器滿了停止生產(chǎn)開始消費(fèi),當(dāng)容器空了停止消費(fèi)開始生產(chǎn)。
d、管道通信:通過管道,將一個(gè)線程中的消息發(fā)送給另一個(gè)。java.io.PipedInputStream 和 java.io.PipedOutputStream
16.hashmap 和 hashtable 的區(qū)別是什么?
1.繼承的父類不同:HashTable繼承自Dictionary類,而HashMap繼承AbstractMap。但二者都實(shí)現(xiàn)了Map接口。
2.線程安全性不同:HashTable這是線程安全的,其方法是Synchronized修飾的,HashMap是線程不安全的,可以用Collections.synchronizedMap()包裝。因?yàn)槎嗑€程并發(fā)put元素,或?qū)е聰?shù)據(jù)覆蓋問題。
3.是否提供contains方法:HashMap值提供containsKey和containsValue方法,HashTable多提供了contains方法,但與containsValue功能相同。
4.key和value是否允許null:HashMap允許一個(gè)key為null,允許多個(gè)value為null;HashTable不存于存在key或value為null,編譯通過,但運(yùn)行或報(bào)空指針。
5.遍歷方式實(shí)現(xiàn)不同:HashMap、HashTable都使用了Iterator,但HashTable還使用了Enumeration的方式 。
6.hash值不同:HashTable直接使用key的hashCode。而HashMap重新計(jì)算hash值
7.內(nèi)部實(shí)現(xiàn)使用的數(shù)組初始化和擴(kuò)容方式不同:HashTable初始容量默認(rèn)11,HashMap默認(rèn)16;HashTable不要求容量是2的次冪,HahsMap要求;Hashtable擴(kuò)容時(shí),將容量變?yōu)樵瓉淼?倍加1,而HashMap擴(kuò)容時(shí),將容量變?yōu)樵瓉淼?倍。
17.synchronized 關(guān)鍵字底層是如何實(shí)現(xiàn)的?它與 Lock 相比優(yōu)缺點(diǎn)分別是什么?
實(shí)現(xiàn):Java對(duì)象的鎖信息存在對(duì)象頭里,因此Java識(shí)別對(duì)象是否持有鎖是通過檢查對(duì)象頭中的鎖標(biāo)志來判斷。monitor(監(jiān)視器)存在與對(duì)象頭中,當(dāng)嘗試獲取鎖是,判斷鎖計(jì)數(shù)器是否等于0.等于0獲取鎖然后鎖計(jì)數(shù)器加1,釋放鎖時(shí)鎖計(jì)數(shù)器減1.
對(duì)比:
synchronized:優(yōu)點(diǎn)是實(shí)現(xiàn)簡(jiǎn)單,自動(dòng)上鎖釋放鎖;缺點(diǎn)是悲觀鎖,效率低
Lock:提供了讀寫鎖、公平鎖、非公平鎖,更靈活;缺點(diǎn)是需要手動(dòng)釋放鎖
18.簡(jiǎn)述 Redis 中如何防止緩存雪崩和緩存擊穿
緩存穿透:查詢一個(gè)本身不存在數(shù)據(jù)庫的數(shù)據(jù),這樣每次查詢都會(huì)訪問到數(shù)據(jù)庫,這樣會(huì)對(duì)數(shù)據(jù)庫造成很大壓力。
避免:可以將數(shù)據(jù)庫不存在的值也加入緩存,存儲(chǔ)空值并設(shè)置過期時(shí)間。
緩存擊穿:查詢一個(gè)數(shù)據(jù),恰巧這個(gè)數(shù)據(jù)在redis中失效了,會(huì)訪問數(shù)據(jù)庫,如果請(qǐng)求量很大,會(huì)給數(shù)據(jù)庫造成很大壓力。
避免:比如針對(duì)熱點(diǎn)數(shù)據(jù)短時(shí)間內(nèi)訪問量較大數(shù)據(jù),適當(dāng)設(shè)置大一點(diǎn)過期時(shí)間。
緩存雪崩:在某一時(shí)間段,緩存集中過期失效或者重啟緩存服務(wù),所有的請(qǐng)求都會(huì)訪問到數(shù)據(jù)庫。
避免:可以根據(jù)業(yè)務(wù)數(shù)據(jù)不同,設(shè)置不同有效期,甚至可以對(duì)同一類數(shù)據(jù)設(shè)置有效期是加入隨機(jī)參數(shù),這樣可以避免同一時(shí)間全部數(shù)據(jù)或同一類數(shù)據(jù)全部過期。
19.簡(jiǎn)述 Redis 持久化中 rdb 以及 aof 方案的優(yōu)缺點(diǎn)
RDB:將Redis中緩存的數(shù)據(jù)定時(shí)記錄到磁盤上的dump文件中。可以配置單位時(shí)間修改若干key進(jìn)行持久化。
優(yōu)點(diǎn):定時(shí)fork一個(gè)子進(jìn)程,現(xiàn)將數(shù)據(jù)寫入臨時(shí)文件,成功之后再替換之前文件,效率更高
缺點(diǎn):如果在定時(shí)持久化之前服務(wù)器宕機(jī),會(huì)導(dǎo)致從上次持久化現(xiàn)在時(shí)間點(diǎn)數(shù)據(jù)丟失
AOF:將Redis的操作日志追加到文件中。發(fā)生修改數(shù)據(jù)時(shí)、每秒、從不同步三種策略。
優(yōu)點(diǎn):數(shù)據(jù)安全性高,當(dāng)文件過大時(shí)還會(huì)進(jìn)行rewrite操作來壓縮文件,先寫入最新的keys數(shù)據(jù)到臨時(shí)文件,最終替換aof文件;文件內(nèi)容清晰易懂
缺點(diǎn):同等數(shù)據(jù)量AOF恢復(fù)數(shù)據(jù)比RDB慢
20.數(shù)據(jù)庫有哪些常見索引?數(shù)據(jù)庫設(shè)計(jì)的范式是什么?
索引類型:
唯一索引:在創(chuàng)建唯一索引時(shí)要不能給具有相同的索引值。
主鍵索引:在我們給一個(gè)字段設(shè)置主鍵的時(shí)候,它就會(huì)自動(dòng)創(chuàng)建主鍵索引,用來確保每一個(gè)值都是唯一的。
聚集索引:我們?cè)诒碇刑砑訑?shù)據(jù)的順序,與我們創(chuàng)建的索引鍵值相同,而且一個(gè)表中只能有一個(gè)聚集索引。
普通索引:它的結(jié)構(gòu)主要以B+樹和哈希索引為主,主要是對(duì)數(shù)據(jù)表中的數(shù)據(jù)進(jìn)行精確查找。
全文索引:它的作用是搜索數(shù)據(jù)表中的字段是不是包含我們搜索的關(guān)鍵字,就像搜索引擎中的模糊查詢。
三大范式:三大范式只是一般設(shè)計(jì)數(shù)據(jù)庫的基本理念
第一范式:每一列屬性都是不可再分的屬性值;兩列的屬性相近或相似或一樣,盡量合并屬性一樣的列;
第二范式:每一行的數(shù)據(jù)只能與其中一列相關(guān),即避免同一列出現(xiàn)重復(fù)數(shù)據(jù),比如維護(hù)一個(gè)人的訂單信息,每行是一個(gè)訂單,那么人員信息每行都一樣,需要拆開成訂單表和人員信息表;
第三范式:數(shù)據(jù)不能存在傳遞關(guān)系,即每個(gè)屬性都跟主鍵有直接關(guān)系而不是間接關(guān)系。比如Student表(學(xué)號(hào),姓名,年齡,性別,所在院校,院校地址,院校電話),這樣一個(gè)表結(jié)構(gòu),就存在上述關(guān)系。 學(xué)號(hào)--> 所在院校 --> (院校地址,院校電話)。這樣的表結(jié)構(gòu),我們應(yīng)該拆開來,如下:(學(xué)號(hào),姓名,年齡,性別,所在院校)--(所在院校,院校地址,院校電話)。
21.Redis 如何實(shí)現(xiàn)分布式鎖?
滿足條件:任意時(shí)刻是能有一個(gè)節(jié)點(diǎn)持有鎖;鎖只能被持有的節(jié)點(diǎn)刪除;持有鎖節(jié)點(diǎn)宕機(jī)不影響其他節(jié)點(diǎn)獲取鎖。
實(shí)現(xiàn):setNX,只在key不存在時(shí)設(shè)置;設(shè)置key的過期時(shí)間;在catch或者finally塊中釋放鎖。
22.簡(jiǎn)述 Redis 的哨兵機(jī)制
目的:為了解決在主從復(fù)制架構(gòu)中出現(xiàn)宕機(jī)的情況。
Redis的Sentinel系統(tǒng)用于管理多個(gè)Redis服務(wù)器,系統(tǒng)執(zhí)行三項(xiàng)任務(wù):
·監(jiān)控:Sentinel會(huì)不斷的定期檢查你的主服務(wù)器和從服務(wù)器是否運(yùn)行正常;
·提醒:當(dāng)被監(jiān)控的某臺(tái)服務(wù)器出現(xiàn)故障,Sentinel可以通過API向管理員或者其他應(yīng)用程序發(fā)送通知;
·自動(dòng)故障遷移:當(dāng)一個(gè)主服務(wù)器不能正常工作時(shí),它會(huì)在從服務(wù)器中選擇一個(gè)升級(jí)為新的主服務(wù)器,并讓其他從服務(wù)器都改為復(fù)制新的主服務(wù)器;當(dāng)客戶端連接已經(jīng)故障的服務(wù)器時(shí),集群也會(huì)向客戶端返回新的主服務(wù)器地址(此操作需要客戶端連接sentinel節(jié)點(diǎn),而不能連接固定ip端口的主機(jī),否則不能實(shí)現(xiàn)自動(dòng)切換)。
23.簡(jiǎn)述數(shù)據(jù)庫中的 ACID 分別是什么?
A:原子性:事務(wù)中各項(xiàng)操作,要么全做要么全不做,任何一項(xiàng)操作的失敗都會(huì)導(dǎo)致整個(gè)事務(wù)的失敗
C:一致性:事務(wù)結(jié)束后系統(tǒng)狀態(tài)是一致的;參考守恒定律,5個(gè)賬戶互相轉(zhuǎn)賬,賬戶總余額不能變
I:隔離性:并發(fā)執(zhí)行的事務(wù)彼此無法看到對(duì)方的中間狀態(tài)
D:持久性:事務(wù)完成后所做的改動(dòng)都會(huì)被持久化,即使發(fā)生災(zāi)難性的失敗
24.什么情況下會(huì)發(fā)生死鎖,如何解決死鎖?
條件:
互斥:同一時(shí)間內(nèi)資源僅能被一個(gè)線程占有;
不可剝奪:線程占有資源未釋放前不能被其他線程占有;
占有和等待:線程已經(jīng)占有了一個(gè)資源,但又請(qǐng)求新的資源,但是新的資源被其他線程占有
循環(huán)等待:前三個(gè)條件的結(jié)果。
避免:
線程有順序加鎖
線程加鎖有條件釋放,不能無限占有
25.Cookie和Session的關(guān)系和區(qū)別是什么?
作用:http請(qǐng)求是無狀態(tài)的,一旦請(qǐng)求數(shù)據(jù)提交完畢就會(huì)關(guān)閉請(qǐng)求,再次提交數(shù)據(jù)需要再發(fā)起請(qǐng)求,所以服務(wù)器無法追蹤和判斷請(qǐng)求管理,確定身份,而cookie和session可以幫助服務(wù)器確定用戶身份。
cookie:第一次登陸時(shí),服務(wù)器會(huì)返回一段數(shù)據(jù)(cookie)給瀏覽器,瀏覽器會(huì)把cookie保存起來,再次發(fā)送請(qǐng)求會(huì)攜帶cookie發(fā)送給服務(wù)器,1以讓服務(wù)端確認(rèn)用戶身份。
session:session存在服務(wù)端,而cookie則是存儲(chǔ)在客戶端本地。第一次登陸服務(wù)器會(huì)存儲(chǔ)session同時(shí)生成一個(gè)session_id,通過http響應(yīng)頭返回給瀏覽器,然后瀏覽器會(huì)把session_id保存在cookie中,下一次請(qǐng)求,瀏覽器會(huì)發(fā)送session_id到服務(wù)器,服務(wù)器通過session_id獲取到對(duì)應(yīng)數(shù)據(jù)來判斷用戶的身份。
26.Redis 有幾種數(shù)據(jù)結(jié)構(gòu)?Zset 是如何實(shí)現(xiàn)的?
數(shù)據(jù)類型:
String:最基本的數(shù)據(jù)類型,一個(gè)key對(duì)應(yīng)一個(gè)value。
Hash(哈希):是一個(gè)HashMap,指值本身是一種鍵值對(duì)結(jié)構(gòu),如value={{field1,value1},......fieldN,valueN}}
鏈表:List就是鏈表(redis使用的是雙端鏈表),是有序的,value可以重復(fù),可以通過下標(biāo)取出對(duì)應(yīng)的value值,左右兩邊都能進(jìn)行插入和刪除操作。
Set(集合):集合類型也是用來保存多個(gè)字符串的元素,但和列表不同的是集合中:1.不允許有重復(fù)元素,2.集合中元素?zé)o序,不能通過索引下標(biāo)獲取,3.支持集合間的操作,可以去多個(gè)集合的交集、并集、差集
zset(有序集合):去集合區(qū)別是元素可以排序,為每個(gè)元素設(shè)置一個(gè)分?jǐn)?shù),作為排序的依據(jù)。有序集合的元素不允許重復(fù),但是分?jǐn)?shù)可以重復(fù)。
27.Redis 序列化有哪些方式?
JDKSerializationRedisSerializer:redis默認(rèn)的序列化方式。序列化的對(duì)象需要實(shí)現(xiàn)Serializer接口。
Jackson2JsonRedisSerializer:如果存儲(chǔ)對(duì)象為json的話推薦使用,不僅可以將對(duì)象序列化,還可以將對(duì)象轉(zhuǎn)為json字符串保存到redis中,被序列化對(duì)象不需要實(shí)現(xiàn)Serializable接口。序列化結(jié)果清晰,容易閱讀,存儲(chǔ)字節(jié)少,速度快。
StringRedisSerializer:如果key-value都是string的話,將RedisTemplate序列化方式改為Jackson2JsonRedisSerializer,StringRedisTemplate序列化方式不需要改。
28.MySQL 為什么使用 B+ 樹來作索引,對(duì)比 B 樹它的優(yōu)點(diǎn)和缺點(diǎn)是什么?
區(qū)別:
優(yōu)點(diǎn):
IO次數(shù)少:B+樹的非葉節(jié)點(diǎn)不存儲(chǔ)數(shù)據(jù)信息,因此在內(nèi)存頁中能存粗更鎖的key,一次查詢IO次數(shù)等于B+樹的高度
遍歷更加方便:B+樹的葉子節(jié)點(diǎn)都是相連的,因此對(duì)于整棵樹的遍歷只需要一次線性遍歷葉子幾點(diǎn)。由于數(shù)據(jù)順序排列且相連,便于區(qū)間查找和搜索。而B樹需要對(duì)每一層遞歸遍歷,相鄰的元素可能在內(nèi)存中不相鄰。
缺點(diǎn):
B樹每個(gè)節(jié)點(diǎn)都存key和value,如果訪問量高的數(shù)據(jù)接近根節(jié)點(diǎn),那么查詢更迅速。
使用B+樹的理由:
B+樹查詢磁盤的IO次數(shù)更少,且查詢效率更穩(wěn)定,由于葉子節(jié)點(diǎn)數(shù)據(jù)排列有序且相連,便于區(qū)間查找。
29.簡(jiǎn)述 CAP 理論
CAP原理指的是,在分布式系統(tǒng)中這三個(gè)要素最多只能同時(shí)實(shí)現(xiàn)兩個(gè)。因此在進(jìn)行分布式系統(tǒng)設(shè)計(jì)是,必須作出取舍。而對(duì)于分布式系統(tǒng),分區(qū)容忍性是基本要求,否則就失去了價(jià)值。因此設(shè)計(jì)分布式系統(tǒng),就是在一致性可高可用性之間做取舍。對(duì)于大多數(shù)Web應(yīng)用,其實(shí)并不去要強(qiáng)一致性,因此犧牲一致性換取高可用性,是目前多數(shù)分布式系統(tǒng)產(chǎn)品的方向。
一致性(Consistency):數(shù)據(jù)在多個(gè)副本之間是否能夠保持一致。(當(dāng)一個(gè)系統(tǒng)在一致狀態(tài)下更新后,應(yīng)保持系統(tǒng)中所有數(shù)據(jù)仍處于一致的狀態(tài))
高可用性(Availability):系統(tǒng)提供的服務(wù)必須一直處于可用狀態(tài),對(duì)每個(gè)操作的請(qǐng)求必須在有限時(shí)間內(nèi)返回結(jié)果。
分區(qū)容錯(cuò)性(Tolerance of network Partition):分布式系統(tǒng)在遇到網(wǎng)絡(luò)分區(qū)故障時(shí),仍然需要保證對(duì)外提供一致性和可用性的服務(wù),除非整個(gè)網(wǎng)絡(luò)發(fā)生故障。
為什么只能同時(shí)滿足兩個(gè):
例如,有ABC三臺(tái)服務(wù)器,value初始都收0,A服務(wù)器收到了修改value=1的請(qǐng)求,這是C服務(wù)器發(fā)生故障。為了滿足分區(qū)容錯(cuò),此時(shí)服務(wù)對(duì)外必須提供服務(wù)。
? ? 如果滿足一致性,則A服務(wù)器的修改將不會(huì)提交成功,因?yàn)镃服務(wù)器故障不能完成修改
? ? 如果滿足高可用性,則允許AB服務(wù)器修改value=1
30.簡(jiǎn)述 HTTPS 的認(rèn)證過程
1.服務(wù)器生成一對(duì)公鑰和私鑰
2.把公鑰放到證書里發(fā)送給客戶端,私鑰自己保存
3.客戶端首先向一個(gè)權(quán)威的服務(wù)器檢查證書的合法性,如果證書合法,客戶端會(huì)產(chǎn)生一段隨機(jī)數(shù),這個(gè)隨機(jī)數(shù)就是通信密鑰,用公鑰加密這段隨機(jī)數(shù),然后發(fā)送到服務(wù)器
4.服務(wù)器解密獲取通信密鑰,然后雙方就用通信密鑰進(jìn)行加密揭秘通信
31.