華爲優招面試經驗。html
1.筆試(這部分按照華爲之前的風格不會爲難人的,認真作AC一道題就能夠進面試了,我編程能力通常吧,作了一道半而已,-_-||!)前端
3.面試分爲業務面和綜合面兩面,業務面就是技術面,綜合面就問的比較雜了,我選的崗位是IT軟件開發,下面分享一下我依稀記得面試題目:你們接好了!java
4.錄用簽約node
5.入職linux
1nginx
2程序員
3面試
4redis
5算法
我是8月17參加的面試,當天我還遲到了2分鐘,讓我去的東門,好不容易纔找到,其實去南門進去就是了,可能他們考慮人分流的緣由吧。
業務面:先介紹下面試官,面試個人面試官是個大概37歲左右的大叔戴個眼鏡,頭髮黑白參差的那種,就像吳秀波的那種髮色,可是髮型徹底不同,頭髮是那種寸頭,人家是根根有勁,給人感受滄桑乾練,就感受很牛逼的樣子....
1.首先自我介紹,介紹完了他就問你挑一兩個你作的工做比較多的項目說說看,我簡歷上寫了三個項目,一個資源目錄的項目(這個比較熟悉),一個給學校作研究的試點項目(有關中小河流的預報,這個就是和我研究的模式挖掘很相關),還有一個是我7,8月正在廣州實習的珠三角水資源配置工程項目。
2. 我就主要說了資源目錄的那個項目,從數據庫的分表提及,由於咱們那個數據庫的數據有1000多萬的數據量,爲了方便查詢我說咱們採用了分表處理,給他介紹了咱們採用的分表方式,數據庫這個點他卻是沒有問我問題;以後說到數據抽取,創建模型都沒有問我問題(感到慶幸),說到編目的時候我提到了多線程的使用,這時候他問題就來了,首先問了我使用什麼什麼完成「消費者和生產者模式「,這個我有準備就回答了,而後我說咱們認爲編目操做是IO比較頻繁的操做,採用了生產者與消費者線程 1:2這樣的比例來進行多線程操做,他再次提問:爲何IO密集的操做須要較多的線程數?,我當時就懵逼了。。。,這個沒答上來,我說我不大清楚。。。,第三個問題:那你把你用到的多線程操做類給我寫出來吧,我當時也是心懸了一下,還好,我想了下給他把我使用的線程池和使用的鎖,阻塞隊列勉強寫了下,他看了下,我以爲應該差很少,確定有遺漏的,主要寫出來了
3. 後面在這個項目上沒問什麼了,讓我在介紹下一個項目,我就介紹起了我研究的成果,模式挖掘的那塊東西,才說了兩句,他就讓停下來了,「我不關心你的學術研究,你說說下一個項目吧「,好直接。。。,不關心非技術的項目,而後我又介紹了珠三角的項目,其實這個項目是實習才接觸的,一個多月吧,也不是很熟悉,大致介紹完了,他說大家那個定時任務怎麼作的?還好還好,我能答出來,我說咱們使用的是Quartz來作的定時任務,他點了頭,沒有繼續問了(幸好沒有繼續問底層,否則就GG。。。)
4. 介紹完了,我看他們每一個人是有三頁的問題紙,(這可能就是若是咱們不主動說話,他會按照問題紙來提問吧。。可怕),其實我已經說了不少廢話了,耗了很長的時間,可是面試官可能必需要問他紙上準備好的問題,而後他就提問「java內存的空間分佈「,這個是比較簡單的問題了,還讓我把內存模型畫了出來,這也難不倒我,也比較簡單。以後jvm的垃圾回收機制,這個我也比較熟,讓比較了CMS和G1的區別,還提了「若是是大數據量的狀況下使用什麼回收器比較好?「G1是針對服務器的,而且是分塊整理的固然選擇G1。最後問了一個問題「若是jvm一直在作FullGC怎麼辦?「這個我開始反應就回答了設置jvm的堆區最大值,他說要是仍是FullGC呢?我就沒答上來,他說咱們須要檢測爲何出現這種狀況了,我立馬接上話說使用jconsole之類的工具進行檢測,算是求生欲很強了。。。
5. 繼續提問「緩存池咱們不少地方都要用到,如何實現緩存池?「說實話剛聽到這個問題我又懵了,我想到了Java io的緩存池,好像是隊列實現的,我就答了能夠使用隊列實現,面試官說「你以爲這樣的數據結構好嘛,咱們是否是能夠換個容器?「這個時候我纔想起來,咱們能夠使用map來實現。
6. 下面就是一大堆map的問題,hashmap的底層結構,爲何是2*n次方,如何擴容,併發怎麼解決,concurrentHashMap如何實現高效讀寫(分區+鎖技術),最後竟然問了句:爲何負載因子是0.75,這個就沒有答上來,答案你們本身百度。
7. 前先後後也有半小時了,面試官問題也差很少了,他就說能夠了,我這裏你經過了,竟然經過了,其實好幾題沒答上來,大致還行,基礎真的好重要呀,華爲問的基礎仍是比較簡單的,但願你們好好複習一下,不難的,沒有那麼恐怖,也可能我遇到的面試官不錯的緣由吧,我不會還會引導我回答,很棒的一位大佬,頗有範兒。
綜合面:綜合面就比較簡單了,問的技術問題很少,個人綜合面面試官是位40多歲的大叔,頭髮沒前面那個技術大佬那麼多白髮,可是也有少許白頭髮,髮型就徹底不同了,軟軟的順順的感受,是那種給人看起來很和善主管感受,綜合面的自我介紹我沒發揮好有點緊張,可是以後我徹底找到了節奏,期間他我把全部的項目都簡要的說完了,沒業務面那麼細節說,問了一個抽象工廠模式,問了對華爲的映像,如何評價外界對華爲的不一樣見解,如何看待華爲的文化,我還特意扯了我去了華爲總部參觀,把華爲不折不扣的誇了一番,(其實參觀過華爲總部你就會以爲華爲是真的牛逼,他的那個企業解決方案作的真的是好)這裏面試官還特地和我聊起我怎麼過來面試的,由於我面試以前在廣州實習,是坐飛機趕去南京面試的,買的晚上7.25的機票由於颱風問題,延誤到凌晨2點多我纔到宿舍。。。,真的累人,以後他還問了薪資待遇問題,我說南京這邊價格通常在14k吧,原來沒準備說,面試官特地說沒事,仍是說了下。最後我以爲今年華爲遷到東莞,可能要招的人多一點,面試官一直在詢問我是否接受派遣,因此我就很虛,感受就算運氣好能進也是會被安排到東莞去了,算了要是能進也是能夠接受的,畢竟華爲的平臺仍是大的。最後讓我問他問題,我就問了他華爲的培訓相關事宜和晉升機制,面試官很耐心的給我解答了,最有問了個你們都關心的問題,何時有通知?回答一週以內,至此面試所有結束
如今的簡歷狀態是錄用排序中,這個狀態我也查了下資料,過了面試以後按分數排序,靠前進資源池吧,估計一個禮拜若是發信息也是是否進資源池的信息,進了資源池其實尚未肯定能拿offer,還要看人家能不能把你撈起來,大廠就是有資本的,今年爲華爲的大年,小夥伴的機會仍是大的,在此我也但願咱們能拿到本身想要的結果,加油!
面試發現常常有些重複的面試問題,本身也應該學會記錄下來,最好本身能作成筆記,在下一次面的時候說得有條不紊,深刻具體,面試官想必也很開心。如下是我我的總結,請參考:
HashSet底層原理:(問了大概率跟HashMap一塊兒面)
HashMap底層原理:(很是大概率問到)
Hashtable底層原理:(問的少,問了大概率問你跟HashMap的區別)
synchronized底層如何實現?鎖優化,怎麼優化?
ReentrantLock 底層實現;
ConcurrentHashMap 的工做原理,底層原理(談到多線程高併發大概率會問它)
JVM調優(JVM層層漸進問時大概率問)
JVM內存管理,JVM的常見的垃圾收集器,GC調優,Minor GC ,Full GC 觸發條件(像是必考題)
java內存模型
線程池的工做原理(談到多線程高併發大概率會問它)
ThreadLocal的底層原理(有時問)
voliate底層原理
NIO底層原理
IOC底層實現原理(Spring IOC ,AOP會問的兩個原理,面試官常常會問看過源碼嗎?因此你有所準備吧)
AOP底層實現原理
MyisAM和innodb的有關索引的疑問(容易混淆,能夠問的會深刻)
HashSet底層原理:(面試過)
http://zhangshixi.iteye.com/blog/673143
http://www.javashuo.com/article/p-memupmkd-bc.html
HashSet實現Set接口,由哈希表(其實是一個HashMap實例)支持。它不保證set 的迭代順序;特別是它不保證該順序恆久不變。此類容許使用null元素。
2. HashSet的實現:
對於HashSet而言,它是基於HashMap實現的,HashSet底層使用HashMap來保存全部元素,所以HashSet 的實現比較簡單,相關HashSet的操做,基本上都是直接調用底層HashMap的相關方法來完成, (實際底層會初始化一個空的HashMap,並使用默認初始容量爲16和加載因子0.75。)
HashSet的源代碼
對於HashSet中保存的對象,請注意正確重寫其equals和hashCode方法,以保證放入的對象的惟一性。
插入
當有新值加入時,底層的HashMap會判斷Key值是否存在(HashMap細節請移步深刻理解HashMap),若是不存在,則插入新值,同時這個插入的細節會依照HashMap插入細節;若是存在就不插入
HashMap底層原理:
1. HashMap概述:
HashMap是基於哈希表的Map接口的非同步實現。此實現提供全部可選的映射操做,並容許使用null值和null鍵。此類不保證映射的順序,特別是它不保證該順序恆久不變。
2. HashMap的數據結構:
HashMap其實是一個「數組+鏈表+紅黑樹」的數據結構
3. HashMap的存取實現:
(1.8以前的)
當咱們往HashMap中put元素的時候,先根據key的hashCode從新計算hash值,根據hash值獲得這個元素在數組中的位置(即下標),若是數組該位置上已經存放有其餘元素了,那麼在這個位置上的元素將以鏈表的形式存放,新加入的放在鏈頭,最早加入的放在鏈尾。若是數組該位置上沒有元素,就直接將該元素放到此數組中的該位置上。
1.8:
put():
根據key計算獲得key.hash = (h = k.hashCode()) ^ (h >>> 16);
根據key.hash計算獲得桶數組的索引index = key.hash & (table.length - 1),這樣就找到該key的存放位置了:
①若是該位置沒有數據,用該數據新生成一個節點保存新數據,返回null;
②若是該位置有數據是一個紅黑樹,那麼執行相應的插入 / 更新操做
③若是該位置有數據是一個鏈表,分兩種狀況一是該鏈表沒有這個節點,另外一個是該鏈表上有這個節點,注意這裏判斷的依據是key.hash是否同樣:若是該鏈表沒有這個節點,那麼採用尾插法新增節點保存新數據,返回null;若是該鏈表已經有這個節點了,那麼找到該節點並更新新數據,返回老數據。注意: HashMap的put會返回key的上一次保存的數據。
get():
計算需獲取數據的hash值(計算過程跟put同樣),計算存放在數組table中的位置(計算過程跟put同樣),而後依次在數組,紅黑樹,鏈表中查找(經過equals()判斷),最後再判斷獲取的數據是否爲空,若爲空返回null不然返回該數據
樹化與還原
哈希表的最小樹形化容量
當哈希表中的容量大於這個值時(64),表中的桶才能進行樹形化
不然桶內元素太多時會擴容,而不是樹形化
爲了不進行擴容、樹形化選擇的衝突,這個值不能小於 4 * TREEIFY_THRESHOLD
一個桶的樹化閾值
當桶中元素個數超過這個值時(8),須要使用紅黑樹節點替換鏈表節點
這個值必須爲 8,要否則頻繁轉換效率也不高
一個樹的鏈表還原閾值
當擴容時,桶中元素個數小於這個值(6),就會把樹形的桶元素還原(切分)爲鏈表結構
這個值應該比上面那個小,至少爲 6,避免頻繁轉換
條件1. 若是當前桶數組爲null或者桶數組的長度 < MIN_TREEIFY_CAPACITY(64),則進行擴容處理(見代碼片斷2:resize());
條件2. 當不知足條件1的時候則將桶中鏈表內的元素轉換成紅黑樹!!!稍後再詳細討論紅黑樹。
擴容機制的實現
擴容(resize)就是從新計算容量。當向HashMap對象裏不停的添加元素,而HashMap對象內部的桶數組沒法裝載更多的元素時,HashMap對象就須要擴大桶數組的長度,以便能裝入更多的元素。
capacity 就是數組的長度/大小,loadFactor 是這個數組填滿程度的最大比比例。
size表示當前HashMap中已經儲存的Node<key,value>的數量,包括桶數組和鏈表 / 紅黑樹中的的Node<key,value>。
threshold表示擴容的臨界值,若是size大於這個值,則必需調用resize()方法進行擴容。
在jdk1.7及之前,threshold = capacity * loadFactor,其中 capacity 爲桶數組的長度。這裏須要說明一點,默認負載因子0.75是是對空間和時間(縱向橫向)效率的一個平衡選擇,建議你們不要修改。 jdk1.8對threshold值進行了改進,經過一系列位移操做算法最後獲得一個power of two size的值
何時擴容
當向容器添加元素的時候,會判斷當前容器的元素個數,若是大於等於閾值---即當前數組的長度乘以加載因子的值的時候,就要自動擴容啦。
擴容必須知足兩個條件:
1、存放新值的時候 當前已有元素的個數 (size) 必須大於等於閾值
2、存放新值的時候當前存放數據發生hash碰撞(當前key計算的hash值換算出來的數組下標位置已經存在值)
//若是計算的哈希位置有值(及hash衝突),且key值同樣,則覆蓋原值value,並返回原值value
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
resize()方法:該函數有2種使用狀況1.初始化哈希表 2.當前數組容量太小,需擴容
過程:
插入鍵值對時發現容量不足,調用resize()方法方法,
1.首先進行異常狀況的判斷,如是否須要初始化,二是若當前容量》最大值則不擴容,
2.而後根據新容量(是就容量的2倍)新建數組,將舊數組上的數據(鍵值對)轉移到新的數組中,這裏包括:(遍歷舊數組的每一個元素,從新計算每一個數據在數組中的存放位置(原位置或者原位置+舊容量),將舊數組上的每一個數據逐個轉移到新數組中,這裏採用的是尾插法。)
3.新數組table引用到HashMap的table屬性上
4.最後從新設置擴容闕值,此時哈希表table=擴容後(2倍)&轉移了舊數據的新table
synchronized底層如何實現?鎖優化,怎麼優化?
synchronized 是 Java 內建的同步機制,因此也有人稱其爲 Intrinsic Locking,它提供了互斥的語義和可見性,當一個線程已經獲取當前鎖時,其餘試圖獲取的線程只能等待或者阻塞在那裏。
原理:
synchronized能夠保證方法或者代碼塊在運行時,同一時刻只有一個方法能夠進入到臨界區,同時它還能夠保證共享變量的內存可見性
底層實現:
同步代碼塊是使用monitorenter和monitorexit指令實現的,,當且一個monitor被持有以後,他將處於鎖定狀態。線程執行到monitorenter指令時,將會嘗試獲取對象所對應的monitor全部權,即嘗試獲取對象的鎖;
同步方法(在這看不出來須要看JVM底層實現)依靠的是方法修飾符上的ACC_SYNCHRONIZED實現。 synchronized方法則會被翻譯成普通的方法調用和返回指令如:invokevirtual、areturn指令,在VM字節碼層面並無任何特別的指令來實現被synchronized修飾的方法,而是在Class文件的方法表中將該方法的access_flags字段中的synchronized標誌位置1,表示該方法是同步方法並使用調用該方法的對象或該方法所屬的Class在JVM的內部對象表示 Klass 作爲鎖對象。
Java對象頭和monitor是實現synchronized的基礎!
synchronized存放的位置:
synchronized用的鎖是存在Java對象頭裏的。
其中, Java對象頭包括:
Mark Word(標記字段):用於存儲對象自身的運行時數據,如哈希碼(HashCode)、GC分代年齡、鎖狀態標誌、線程持有的鎖、偏向線程 ID、偏向時間戳等等。它是實現輕量級鎖和偏向鎖的關鍵
Klass Pointer(類型指針):是對象指向它的類元數據的指針,虛擬機經過這個指針來肯定這個對象是哪一個類的實例
monitor: 能夠把它理解爲一個同步工具,它一般被描述爲一個對象。 是線程私有的數據結構
鎖優化,怎麼優化?
jdk1.6對鎖的實現引入了大量的優化。鎖主要存在四中狀態,依次是:無鎖狀態、偏向鎖狀態、輕量級鎖狀態、重量級鎖狀態,他們會隨着競爭的激烈而逐漸升級。注意鎖能夠升級不可降級,這種策略是爲了提升得到鎖和釋放鎖的效率。重量級鎖降級發生於STW階段,降級對象爲僅僅能被VMThread訪問而沒有其餘JavaThread訪問的對象。( HotSpot JVM/JRockit JVM是支持鎖降級的)
偏斜鎖:
當沒有競爭出現時,默認會使用偏斜鎖。JVM 會利用 CAS 操做(compare and swap),在對象頭上的 Mark Word 部分設置線程 ID,以表示這個對象偏向於當前線程,因此並不涉及真正的互斥鎖。
自旋鎖:
自旋鎖 for(;;)結合cas確保線程獲取取鎖
就是讓該線程等待一段時間,不會被當即掛起,看持有鎖的線程是否會很快釋放鎖。怎麼等待呢?執行一段無心義的循環便可(自旋)。
輕量級鎖:
引入偏向鎖主要目的是:爲了在無多線程競爭的狀況下儘可能減小沒必要要的輕量級鎖執行路徑。當關閉偏向鎖功能或者多個線程競爭偏向鎖致使偏向鎖升級爲輕量級鎖,則會嘗試獲取輕量級鎖
重量級鎖:
重量級鎖經過對象內部的監視器(monitor)實現,其中monitor的本質是依賴於底層操做系統的Mutex Lock實現,操做系統實現線程之間的切換須要從用戶態到內核態的切換,切換成本很是高。
ReentrantLock 底層實現
https://blog.csdn.net/u011202334/article/details/73188404
AQS原理:
AQS和Condition各自維護了不一樣的隊列,在使用lock和condition的時候,其實就是兩個隊列的互相移動。若是咱們想自定義一個同步器,能夠實現AQS。它提供了獲取共享鎖和互斥鎖的方式,都是基於對state操做而言的。
概念+實現:
ReentrantLock實現了Lock接口,是AQS( 一個用來構建鎖和同步工具的框架, AQS沒有鎖之類的概念)的一種。加鎖和解鎖都須要顯式寫出,注意必定要在適當時候unlock。ReentranLock這個是可重入的。其實要弄明白它爲啥可重入的呢,咋實現的呢。其實它內部自定義了同步器Sync,這個又實現了AQS,同時又實現了AOS,然後者就提供了一種互斥鎖持有的方式。其實就是每次獲取鎖的時候,看下當前維護的那個線程和當前請求的線程是否同樣,同樣就可重入了。
和synhronized相比:
synchronized相比,ReentrantLock用起來會複雜一些。在基本的加鎖和解鎖上,二者是同樣的,因此無特殊狀況下,推薦使用synchronized。ReentrantLock的優點在於它更靈活、更強大,增長了輪訓、超時、中斷等高級功能。
可重入鎖。可重入鎖是指同一個線程能夠屢次獲取同一把鎖。ReentrantLock和synchronized都是可重入鎖。
可中斷鎖。可中斷鎖是指線程嘗試獲取鎖的過程當中,是否能夠響應中斷。synchronized是不可中斷鎖,而ReentrantLock則z,dz提供了中斷功能。
公平鎖與非公平鎖。公平鎖是指多個線程同時嘗試獲取同一把鎖時,獲取鎖的順序按照線程達到的順序,而非公平鎖則容許線程「插隊」。synchronized是非公平鎖,而ReentrantLock的默認實現是非公平鎖,可是也能夠設置爲公平鎖。
lock()和unlock()是怎麼實現的呢?
由lock()和unlock的源碼能夠看到,它們只是分別調用了sync對象的lock()和release(1)方法。而 Sync是ReentrantLock的內部類, 其擴展了AbstractQueuedSynchronizer。
lock():
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
首先用一個CAS操做,判斷state是不是0(表示當前鎖未被佔用),若是是0則把它置爲1,而且設置當前線程爲該鎖的獨佔線程,表示獲取鎖成功。當多個線程同時嘗試佔用同一個鎖時,CAS操做只能保證一個線程操做成功,剩下的只能乖乖的去排隊啦。( 「非公平」即體如今這裏)。
設置state失敗,走到了else裏面。咱們往下看acquire。
第一步。嘗試去獲取鎖。若是嘗試獲取鎖成功,方法直接返回。
2. 第二步,入隊。(自旋+CAS組合來實現非阻塞的原子操做)
3. 第三步,掛起。讓已經入隊的線程嘗試獲取鎖,若失敗則會被掛起
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
unlock():
流程大體爲先嚐試釋放鎖,若釋放成功,那麼查看頭結點的狀態是否爲SIGNAL,若是是則喚醒頭結點的下個節點關聯的線程,
若是釋放失敗那麼返回false表示解鎖失敗。這裏咱們也發現了,每次都只喚起頭結點的下一個節點關聯的線程。
public void unlock() {
sync.release(1);
}
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
ConcurrentHashMap 的工做原理
概念:
ConcurrentHashMap的目標是實現支持高併發、高吞吐量的線程安全的HashMap。
1.8以前:
數據結構:
ConcurrentHashMap是由Segment數組結構和多個HashEntry數組結構組成。Segment是一種可重入鎖ReentrantLock,在ConcurrentHashMap裏扮演鎖的角色,HashEntry則用於存儲鍵值對數據。一個ConcurrentHashMap裏包含一個Segment數組,Segment的結構和HashMap類似,是一種數組和鏈表結構,一個Segment裏包含一個HashEntry數組,每一個HashEntry是一個鏈表結構的元素,每一個Segment守護者一個HashEntry數組裏的元素,當對HashEntry數組的數據進行修改時,必須首先得到它對應的Segment鎖。
put和get的時候,都是現根據key.hashCode()算出放到哪一個Segment中: ConcurrentHashMap中默認是把segments初始化爲長度爲16的數組
http://www.javashuo.com/article/p-sfmdxgau-bp.html
1.8後:
變化:
ConcurrentHashMap的JDK8與JDK7版本的併發實現相比,最大的區別在於JDK8的鎖粒度更細,理想狀況下talbe數組元素的大小就是其支持併發的最大個數
實現:
改進一:取消segments字段,直接採用transient volatile HashEntry<K,V>[] table保存數據,採用table數組元素做爲鎖,從而實現了對每一行數據進行加鎖,進一步減小併發衝突的機率。
數據結構:
改進二:將原先table數組+單向鏈表的數據結構,變動爲table數組+單向鏈表+紅黑樹的結構。對於hash表來講,最核心的能力在於將key hash以後能均勻的分佈在數組中。
概念:
JDK1.8的實現已經摒棄了Segment的概念,而是直接用Node數組+鏈表+紅黑樹的數據結構來實現,併發控制使用Synchronized和CAS來操做,整個看起來就像是優化過且線程安全的HashMap,雖然在JDK1.8中還能看到Segment的數據結構,可是已經簡化了屬性,只是爲了兼容舊版本。
樹化和還原:
與HashMap同樣。
一些成員:
Node是ConcurrentHashMap存儲結構的基本單元,繼承於HashMap中的Entry,用於存儲數據。,就是一個鏈表,可是隻容許對數據進行查找,不容許進行修改
經過TreeNode做爲存儲結構代替Node來轉換成黑紅樹。
TreeBin
TreeBin就是封裝TreeNode的容器,它提供轉換黑紅樹的一些條件和鎖的控制
// 讀寫鎖狀態
static final int WRITER = 1; // 獲取寫鎖的狀態
static final int WAITER = 2; // 等待寫鎖的狀態
static final int READER = 4; // 增長數據時讀鎖的狀態
構造器
public ConcurrentHashMap() {
} 初始化實際上是一個空實現,初始化操做並非在構造函數實現的,而是在put操做中實現。還提供了其餘的構造函數,有指定容量大小或者指定負載因子,跟HashMap同樣。
存取實現:
put():對當前的table進行無條件自循環直到put成功
若是沒有初始化就先調用initTable()方法來進行初始化過程
若是沒有hash衝突就直接CAS插入
若是還在進行擴容操做就先進行擴容
若是存在hash衝突,就加鎖來保證線程安全,這裏有兩種狀況,一種是鏈表形式就直接遍歷到尾端插入,一種是紅黑樹就按照紅黑樹結構插入,
最後一個若是該鏈表的數量大於閾值8,就要先轉換成黑紅樹的結構,break再一次進入循環
若是添加成功就調用addCount()方法統計size,而且檢查是否須要擴容。
get()
計算hash值,定位到該table索引位置,若是是首節點符合就返回
若是遇到擴容的時候,會調用標誌正在擴容節點ForwardingNode的find方法,查找該節點,匹配就返回
以上都不符合的話,就往下遍歷節點,匹配就返回,不然最後就返回null
歸納版:
(1)對於get讀操做,若是當前節點有數據,還沒遷移完成,此時不影響讀,可以正常進行。
若是當前鏈表已經遷移完成,那麼頭節點會被設置成fwd節點,此時get線程會幫助擴容。
(2)對於put/remove寫操做,若是當前鏈表已經遷移完成,那麼頭節點會被設置成fwd節點,此時寫線程會幫助擴容,若是擴容沒有完成,當前鏈表的頭節點會被鎖住,因此寫線程會被阻塞,直到擴容完成。
擴容機制:https://www.e-learn.cn/content/java/1154828
引入了一個ForwardingNode類,在一個線程發起擴容的時候,就會改變sizeCtl這個值,
sizeCtl :默認爲0,用來控制table的初始化和擴容操做,具體應用在後續會體現出來。
-1 表明table正在初始化
-N 表示有N-1個線程正在進行擴容操做 。
擴容時候會判斷這個值,
若是超過閾值就要擴容,首先根據運算獲得須要遍歷的次數i,而後利用tabAt方法得到i位置的元素f,初始化一個forwardNode實例fwd,若是f == null,則在table中的i位置放入fwd,
不然採用頭插法的方式把當前舊table數組的指定任務範圍的數據給遷移到新的數組中,
而後
給舊table原位置賦值fwd。直到遍歷過全部的節點之後就完成了複製工做,把table指向nextTable,並更新sizeCtl爲新數組大小的0.75倍,擴容完成。在此期間若是其餘線程的有讀寫操做都會判斷head節點是否爲forwardNode節點,若是是就幫助擴容。
Hashtable底層原理:
概念:
HashTable類繼承自Dictionary類,實現了Map接口。 大部分的操做都是經過synchronized鎖保護的,是線程安全的, key、value都不能夠爲null, 每次put方法不容許null值,若是發現是null,則直接拋出異常。
官方文檔也說了:若是在非線程安全的狀況下使用,建議使用HashMap替換,若是在線程安全的狀況下使用,建議使用ConcurrentHashMap替換。
數據結構:
數組+鏈表。
存取實現:
put():
限制了value不能爲null。
因爲直接使用key.hashcode(),而沒有向hashmap同樣先判斷key是否爲null,因此key爲null時,調用key.hashcode()會出錯,因此hashtable中key也不能爲null。
Hashtable是在鏈表的頭部添加元素的。
int index = (hash & 0x7FFFFFFF) %tab.length;獲取index的方式與HashMap不一樣
擴容機制:
Hashtable默認capacity是11,默認負載因子是0.75.。當前表中的Entry數量,若是超過了閾值,就會擴容,即調用rehash方法,從新計算每一個鍵值對的hashCode;
判斷新的容量是否超過了上限,沒超過就新建一個新數組,大小爲原數組的2倍+1,將舊數的鍵值對從新hash添加到新數組中。
JVM調優
查看堆空間大小分配(年輕代、年老代、持久代分配)
垃圾回收監控(長時間監控回收狀況)
線程信息監控:系統線程數量
線程狀態監控:各個線程都處在什麼樣的狀態下
線程詳細信息:查看線程內部運行狀況,死鎖檢查
CPU熱點:檢查系統哪些方法佔用了大量CPU時間
內存熱點:檢查哪些對象在系統中數量最大
jvm問題排查和調優:
jps主要用來輸出JVM中運行的進程狀態信息。
jstat命令能夠用於持續觀察虛擬機內存中各個分區的使用率以及GC的統計數據
jmap能夠用來查看堆內存的使用詳情。
jstack能夠用來查看Java進程內的線程堆棧信息。 jstack是個很是好用的工具,結合應用日誌能夠迅速定位到問題線程。
Java性能分析工具
jdk會自帶JMC(JavaMissionControl)工具。能夠分析本地應用以及鏈接遠程ip使用。提供了實時分析線程、內存,CPU、GC等信息的可視化界面。
JVM內存調優
對JVM內存的系統級的調優主要的目的是減小GC的頻率和Full GC的次數。過多的GC和Full GC是會佔用不少的系統資源(主要是CPU),影響系統的吞吐量。
使用JDK提供的內存查看工具,好比JConsole和Java VisualVM。
致使Full GC通常因爲如下幾種狀況:
舊生代空間不足
調優時儘可能讓對象在新生代GC時被回收、讓對象在新生代多存活一段時間和不要建立過大的對象及數組避免直接在舊生代建立對象
新生代設置太小
一是新生代GC次數很是頻繁,增大系統消耗;二是致使大對象直接進入舊生代,佔據了舊生代剩餘空間,誘發Full GC
2). 新生代設置過大
一是新生代設置過大會致使舊生代太小(堆總量必定),從而誘發Full GC;二是新生代GC耗時大幅度增長
3). Survivor設置太小
致使對象從eden直接到達舊生代
4). Survivor設置過大
致使eden太小,增長了GC頻率
通常說來新生代佔整個堆1/3比較合適
GC策略的設置方式
1). 吞吐量優先可由-XX:GCTimeRatio=n來設置
2). 暫停時間優先可由-XX:MaxGCPauseRatio=n來設置
JVM內存管理:
1.先講內存5大模塊以及他們各類的做用。
2.將垃圾收集器,垃圾收集算法
3.適當講講GC優化,JVM優化
JVM的常見的垃圾收集器:
(注:此回答源於楊曉峯的Java核心技術36講之一)
GC調優:
GC日誌分析
調優命令
調優工具
調優命令
Sun JDK監控和故障處理命令有jps jstat jmap jhat jstack jinfo
jps,JVM Process Status Tool,顯示指定系統內全部的HotSpot虛擬機進程。
jstat,JVM statistics Monitoring是用於監視虛擬機運行時狀態信息的命令,它能夠顯示出虛擬機進程中的類裝載、內存、垃圾收集、JIT編譯等運行數據。
jmap,JVM Memory Map命令用於生成heap dump文件
jhat,JVM Heap Analysis Tool命令是與jmap搭配使用,用來分析jmap生成的dump,jhat內置了一個微型的HTTP/HTML服務器,生成dump的分析結果後,能夠在瀏覽器中查看
jstack,用於生成java虛擬機當前時刻的線程快照。
jinfo,JVM Configuration info 這個命令做用是實時查看和調整虛擬機運行參數。
調優工具
經常使用調優工具分爲兩類,jdk自帶監控工具:jconsole和jvisualvm,第三方有:MAT(Memory Analyzer Tool)、GChisto。
jconsole,Java Monitoring and Management Console是從java5開始,在JDK中自帶的java監控和管理控制檯,用於對JVM中內存,線程和類等的監控
GC觸發的條件有兩種。(1)程序調用System.gc時能夠觸發;(2)系統自身來決定GC觸發的時機。
要徹底回收一個對象,至少須要通過兩次標記的過程。
第一次標記:對於一個沒有其餘引用的對象,篩選該對象是否有必要執行finalize()方法,若是沒有執行必要,則意味可直接回收。(篩選依據:是否複寫或執行過finalize()方法;由於finalize方法只能被執行一次)。
第二次標記:若是被篩選斷定位有必要執行,則會放入FQueue隊列,並自動建立一個低優先級的finalize線程來執行釋放操做。若是在一個對象釋放前被其餘對象引用,則該對象會被移除FQueue隊列。
Minor GC ,Full GC 觸發條件
Minor GC觸發條件:當Eden區滿時,觸發Minor GC。
Full GC觸發條件:
(1)調用System.gc時,系統建議執行Full GC,可是沒必要然執行
(2)老年代空間不足
(3)方法區空間不足
(4)經過Minor GC後進入老年代的平均大小大於老年代的可用內存
(5)由Eden區、From Space區向To Space區複製時,對象大小大於To Space可用內存,則把該對象轉存到老年代,且老年代的可用內存小於該對象大小
java內存模型
與JVM 內存模型不一樣。
Java內存模型即Java Memory Model,簡稱JMM。JMM定義了Java 虛擬機(JVM)在計算機內存(RAM)中的工做方式。JVM是整個計算機虛擬模型,因此JMM是隸屬於JVM的。
Java內存模型定義了多線程之間共享變量的可見性以及如何在須要的時候對共享變量進行同步。
Java線程之間的通訊採用的是過共享內存模型,這裏提到的共享內存模型指的就是Java內存模型(簡稱JMM),JMM決定一個線程對共享變量的寫入什麼時候對另外一個線程可見。從抽象的角度來看,JMM定義了線程和主內存之間的抽象關係:線程之間的共享變量存儲在主內存(main memory)中,每一個線程都有一個私有的本地內存(local memory),本地內存中存儲了該線程以讀/寫共享變量的副本。
線程池的工做原理
1.先講下做用
減小資源的開銷 能夠減小每次建立銷燬線程的開銷
提升響應速度 因爲線程已經建立成功
提升線程的可管理性
2.講實現
線程池主要有兩部分組成,多個工做線程和一個阻塞隊列。
其中工做線程是一組已經處在運行中的線程,它們不斷地向阻塞隊列中領取任務執行。而阻塞隊列用於存儲工做線程來不及處理的任務。
3.細分講下線程的組成
建立一個線程池須要要的一些核心參數。
corePoolSize:基本線程數量它表示你但願線程池達到的一個值。線程池會盡可能把實際線程數量保持在這個值上下。
maximumPoolSize:最大線程數量這是線程數量的上界。若是實際線程數量達到這個值:阻塞隊列未滿:任務存入阻塞隊列等待執行阻塞隊列已滿:調用飽和策略。
keepAliveTime:空閒線程的存活時間當實際線程數量超過corePoolSize時,若線程空閒的時間超過該值,就會被中止。 PS:當任務不少,且任務執行時間很短的狀況下,能夠將該值調大,提升線程利用率。
timeUnit:keepAliveTime的單位
runnableTaskQueue:任務隊列
這是一個存聽任務的阻塞隊列,能夠有以下幾種選擇:
ArrayBlockingQueue 它是一個由數組實現的阻塞隊列,FIFO。
LinkedBlockingQueue 它是一個由鏈表實現的阻塞隊列,FIFO。吞吐量一般要高於ArrayBlockingQueue。fixedThreadPool使用的阻塞隊列就是它。它是一個無界隊列。
SynchronousQueue 它是一個沒有存儲空間的阻塞隊列,任務提交給它以後必需要交給一條工做線程處理;若是當前沒有空閒的工做線程,則當即建立一條新的工做線程。 cachedThreadPool用的阻塞隊列就是它。它是一個無界隊列。 PriorityBlockingQueue 它是一個優先權阻塞隊列。
handler:飽和策略當實際線程數達到maximumPoolSize,而且阻塞隊列已滿時,就會調用飽和策略。
AbortPolicy 默認。直接拋異常。 CallerRunsPolicy 只用調用者所在的線程執行任務。 DiscardOldestPolicy 丟棄任務隊列中最久的任務。 DiscardPolicy 丟棄當前任務。
4.運行機制
當有請求到來時:
1.若當前實際線程數量少於 corePoolSize,即便有空閒線程,也會建立一個新的工做線程;
2 若當前實際線程數量處於corePoolSize和maximumPoolSize之間,而且阻塞隊列沒滿,則任務將被放入阻塞隊列中等待執行;
3.若當前實際線程數量小於 maximumPoolSize,但阻塞隊列已滿,則直接建立新線程處理任務;
4.若當前實際線程數量已經達到maximumPoolSize,而且阻塞隊列已滿,則使用飽和策略。
ThreadLocal的底層原理
歸納:
該類提供了線程局部 (thread-local) 變量。這些變量不一樣於它們的普通對應物,由於訪問某個變量(經過其 get 或 set 方法)的每一個線程都有本身的局部變量
使用:
set(obj):向當前線程中存儲數據 get():獲取當前線程中的數據 remove():刪除當前線程中的數據
實現原理:
ThreadLocal並不維護ThreadLocalMap(ThreadLocalMap是Thread的)並非一個存儲數據的容器,它只是至關於一個工具包,提供了操做該容器的方法,如get、set、remove等。而ThreadLocal內部類ThreadLocalMap纔是存儲數據的容器,而且該容器由Thread維護。每個Thread對象均含有一個ThreadLocalMap類型的成員變量threadLocals,它存儲本線程中全部ThreadLocal對象及其對應的值( ThreadLocalMap 是個弱引用類,內部一個Entry由ThreadLocal對象和Object構成,
爲何要用弱引用呢?
若是是直接new一個對象的話,使用完以後設置爲null後才能被垃圾收集器清理,若是爲弱引用,使用完後垃圾收集器自動清理key,程序員不用再關注指針。
操做細節
進行set,get等操做都是首先會獲取當前線程對象,而後獲取當前線程的ThreadLocalMap對象。再以當前ThreadLocal對象爲key ,再作相應的處理。
內存泄露問題
在ThreadLocalMap中,只有key是弱引用,value仍然是一個強引用。
每次操做set、get、remove操做時,ThreadLocal都會將key爲null的Entry刪除,從而避免內存泄漏。
固然,當若是一個線程運行週期較長,並且將一個大對象放入LocalThreadMap後便再也不調用set、get、remove方法,此時該仍然可能會致使內存泄漏。這個問題確實存在,沒辦法經過ThreadLocal解決,而是須要程序員在完成ThreadLocal的使用後要養成手動調用remove的習慣,從而避免內存泄漏。
使用場景;
Web系統Session的存儲
當請求到來時,能夠將當前Session信息存儲在ThreadLocal中,在請求處理過程當中能夠隨時使用Session信息,每一個請求之間的Session信息互不影響。當請求處理完成後經過remove方法將當前Session信息清除便可。
voliate 的實現原理
爲何volatile能保證共享變量的內存可見性?
volatile變量寫
當被volatile修飾的變量進行寫操做時,這個變量將會被直接寫入共享內存,而非線程的專屬存儲空間。
volatile變量讀
當讀取一個被volatile修飾的變量時,會直接從共享內存中讀,而非線程專屬的存儲空間中讀。
禁止指令重排序
volatile讀
若volatile讀操做的前一行爲volatile讀/寫,則這兩行不會發生重排序 volatile讀操做和它後一行代碼都不會發生重排序
volatile寫
volatile寫操做和它前一行代碼都不會發生重排序; 若volatile寫操做的後一行代碼爲volatile讀/寫,則這兩行不會發生重排序。
當volatile變量寫後,線程中本地內存中共享變量就會置爲失效的狀態,所以線程B再須要讀取從主內存中去讀取該變量的最新值。
NIO底層原理
1概念:
NIO 指新IO,核心是同步非阻塞,解決傳統IO的阻塞問題。操做對象是Buffer。其實NIO的核心是IO線程池,(必定要記住這個關鍵點)。 NIO中的IO多路複用調用系統級別的select和poll模型,由系統進行監控IO狀態,避免用戶線程經過反覆嘗試的方式查詢狀態。
Java NIO :同步非阻塞,服務器實現模式爲一個請求一個線程,即客戶端發送的鏈接請求都會註冊到多路複用器上,多路複用器輪詢到鏈接有I/O請求時才啓動一個線程進行處理。
2.工做原理:
由一個專門的線程來處理全部的 IO 事件,並負責分發。
事件驅動機制:事件到的時候觸發,而不是同步的去監視事件。
線程通信:線程之間經過 wait,notify 等方式通信。保證每次上下文切換都是有意義的。減小無謂的線程切換。
3.通訊模型是怎麼實現的呢?
java NIO採用了雙向通道(channel)進行數據傳輸,而不是單向的流(stream),在通道上能夠註冊咱們感興趣的事件。
四種事件
服務端接收客戶端鏈接事件SelectionKey.OP_ACCEPT(16)
客戶端鏈接服務端事件SelectionKey.OP_CONNECT(8)
讀事件SelectionKey.OP_READ(1)
寫事件SelectionKey.OP_WRITE(4)
服務端和客戶端各自維護一個管理通道的對象,咱們稱之爲selector,該對象能檢測一個或多個通道 (channel) 上的事件。咱們以服務端爲例,若是服務端的selector上註冊了讀事件,某時刻客戶端給服務端發送了一些數據,阻塞I/O這時會調用read()方法阻塞地讀取數據,而NIO的服務端會在selector中添加一個讀事件。服務端的處理線程會輪詢地訪問selector,若是訪問selector時發現有感興趣的事件到達,則處理這些事件,若是沒有感興趣的事件到達,則處理線程會一直阻塞直到感興趣的事件到達爲止。
IOC底層實現原理
概念:
IOC 是面向對象編程中的一種設計原則,IOC理論提出的觀點大致是這樣的:藉助於「第三方」實現具備依賴關係的對象之間的解耦。。所謂IoC,對於spring框架來講,就是由spring來負責控制對象的生命週期和對象間的關係。是說建立對象的控制權進行轉移,之前建立對象的主動權和建立時機是由本身把控的,而如今這種權力轉移到第三方。
實現原理:
它是經過反射機制+工廠模式實現的,在實例化一個類時,它經過反射調用類中set方法將事先保存在HashMap中的類屬性注入到類中。
控制反轉就是:得到依賴對象的方式反轉了。
1、依賴注入發生的時間
(1).用戶第一次經過getBean方法向IoC容索要Bean時,IoC容器觸發依賴注入。
(2).當用戶在Bean定義資源中爲元素配置了lazy-init屬性,即讓容器在解析註冊Bean定義時進行預實例化,觸發依賴注入。
2.依賴注入實如今如下兩個方法中:
(1).createBeanInstance:生成Bean所包含的java對象實例。
(2).populateBean :對Bean屬性的依賴注入進行處理。
AOP底層實現原理
概念
AOP(Aspect-OrientedProgramming,面向方面編程),能夠說是OOP(Object-Oriented Programing,面向對象編程)的補充和完善。OOP引入封裝、繼承和多態性等概念來創建一種對象層次結構,用以模擬公共行爲的一個集合。 而AOP技術則偏偏相反,它利用一種稱爲「橫切」的技術,剖解開封裝的對象內部,並將那些影響了多個類的公共行爲封裝到一個可重用模塊,並將其名爲「Aspect」,即方面。簡單地說,就是將那些與業務無關,卻爲業務模塊所共同調用的邏輯或責任封裝起來,便於減小系統的重複代碼,下降模塊間的耦合度,並有利於將來的可操做性和可維護性。
AOP的核心思想就是「將應用程序中的商業邏輯同對其提供支持的通用服務進行分離。
AOP的實現
實現AOP的技術,主要分爲兩大類:一是採用動態代理技術,利用截取消息的方式,對該消息進行裝飾,以取代原有對象行爲的執行;二是採用靜態織入的方式,引入特定的語法建立「方面」,從而使得編譯器能夠在編譯期間織入有關「方面」的代碼。
如何使用Spring AOP
能夠經過配置文件或者編程的方式來使用Spring AOP。 配置能夠經過xml文件來進行,大概有四種方式:
配置ProxyFactoryBean,顯式地設置advisors, advice, target等
2. 配置AutoProxyCreator,這種方式下,仍是如之前同樣使用定義的bean,可是從容器中得到的其實已是代理對象 3. 經過來配置
4. 經過來配置,使用AspectJ的註解來標識通知及切入點
Spring AOP的實現
如何生成代理類:
Spring提供了兩種方式來生成代理對象: JDKProxy和Cglib,具體使用哪一種方式生成由AopProxyFactory根據AdvisedSupport對象的配置來決定。默認的策略是若是目標類是接口,則使用JDK動態代理技術,不然使用Cglib來生成代理
切面是如何織入的?
InvocationHandler是JDK動態代理的核心,生成的代理對象的方法調用都會委託到InvocationHandler.invoke()方法。
MyisAM和innodb的有關索引的疑問
二者都是什麼索引?彙集仍是非彙集https://www.cnblogs.com/olinux/p/5217186.html
MyISAM(非彙集)
使用B+Tree做爲索引結構,葉節點的data域存放的是數據記錄的地址。
MyISAM中索引檢索的算法爲首先按照B+Tree搜索算法搜索索引,若是指定的Key存在,則取出其data域的值,而後以data域的值爲地址,讀取相應數據記錄。
InnoDB(彙集索引)
第一個重大區別是InnoDB的數據文件自己就是索引文件,這棵樹的葉節點data域保存了完整的數據記錄。
可是輔助索引搜索須要檢索兩遍索引:首先檢索輔助索引得到主鍵,而後用主鍵到主索引中檢索得到記錄。
由於InnoDB的數據文件自己要按主鍵彙集,因此InnoDB要求表必須有主鍵(MyISAM能夠沒有),若是沒有顯式指定,則MySQL系統會自動選擇一個能夠惟一標識數據記錄的列做爲主鍵,若是不存在這種列,則MySQL自動爲InnoDB表生成一個隱含字段做爲主鍵,這個字段長度爲6個字節,類型爲長整形。
簡單說:
若是咱們定義了主鍵(PRIMARY KEY),那麼InnoDB會選擇其做爲彙集索引;若是沒有顯式定義主鍵,則InnoDB會選擇第一個不包含有NULL值的惟一索引做爲主鍵索引
先推薦一個寫的不錯的博客,專門關於面試的,比較詳盡仔細:關於面試。我在這裏簡單總結幾點:
1、簡歷要用心準備好,我的信息,特別是聯繫方式必定要清晰明確,自身掌握的技能要完成清晰,項目經歷最好按照時間順序,說明本人在項目中的職責,完成的工做,有什麼樣的提高或收穫;
2、通常面試流程是電面=》HR現場面=》技術面=》結果,並非每個面試結果就能立馬有結果,因此當面試官說回去等消息的時候,並不表明沒有機會,有時候須要討論篩選才能最終肯定人選。
3、關於自我介紹,最好簡明扼要,能體現自身的特色,表達流暢、自信,提早最好準備;
4、準備好紮實的基礎知識,以及對經歷過的項目要有足夠的認識,每個項目都是一次學習、提高的機會,通常JAVA集合類是考察的重點;
5、通常好一點的面試官會順着知識點逐漸深刻或者逐漸擴展,因此對於知識點的掌握最好全面深刻,不要蜻蜓點水式的學習;
6、當遇到一些設計類的問題時,通常面試官考察的是你的思路,對問題的應變能力,對於事物觀察的點;
1、HashMap源碼,實現原理,JDK8之後對HashMap作了怎樣的優化。
答:HashMap是基於哈希表的Map接口的非同步實現,提供全部可選的映射操做,並容許使用null值和null鍵,不保證映射的順序;HashMap是一個「鏈表散列」的數據結構,即數組和鏈表的結合體;它的底層就是一個數組結構,數組中的每一項又是一個鏈表,每當新建一個HashMap時,就會初始化一個數組;
可參考博客:完全搞懂JAVA集合HashMap,HashTable,ConcurrentHashMap之關聯
而在JDK8中引入了紅黑樹的部分,當存入到數組中的鏈表長度大於(默認)8時,即轉爲紅黑樹;利用紅黑樹快速增刪改查的特色提升HashMap的性能,其中會用到紅黑樹的插入、刪除、查找等算法。本文再也不對紅黑樹展開討論,想了解更多紅黑樹數據結構的工做原理能夠參考http://blog.csdn.net/v_july_v/article/details/6105630。
可參考博客:JAVA8系列之從新認識HashMap
2、HashMap的擴容是怎樣擴容的,爲何都是2的N次冪的大小。
答:能夠參考上文 JAVA8系列之從新認識HashMap 有詳細的講解
3、HashMap,HashTable,ConcurrentHashMap的區別
答:
a、HashMap是非線程安全的,HashTable是線程安全的。
b、HashMap的鍵和值都容許有null值存在,而HashTable則不行。
c、由於線程安全的問題,HashMap效率比HashTable的要高。
HashMap:它根據鍵的hashCode值存儲數據,大多數狀況下能夠直接定位到它的值,於是具備很快的訪問速度,但遍歷順序倒是不肯定的。 HashMap最多隻容許一條記錄的鍵爲null,容許多條記錄的值爲null。HashMap非線程安全,即任一時刻能夠有多個線程同時寫HashMap,可能會致使數據的不一致。若是須要知足線程安全,能夠用 Collections的synchronizedMap方法使HashMap具備線程安全的能力,或者使用ConcurrentHashMap。
Hashtable:Hashtable是遺留類,不少映射的經常使用功能與HashMap相似,不一樣的是它承自Dictionary類,而且是線程安全的,任一時間只有一個線程能寫Hashtable,併發性不如ConcurrentHashMap,由於ConcurrentHashMap引入了分段鎖。
4、極高併發下HashTable和ConcurrentHashMap哪一個性能更好,爲何,如何實現的。
答:固然是ConcurrentHashMap,由於ConcurrentHashMap引入了分段鎖,而HashTable則使用的是方法級別的鎖;所以在新版本中通常不建議使用HashTable,不須要線程安全的場合能夠使用HashMap,而須要線程安全的場合能夠使用ConcurrentHashMap;
5、HashMap在高併發下若是沒有處理線程安全會有怎樣的隱患,具體表現是什麼。
答:可能形成死循環,具體表現鏈表的循環指向;
6、JAVA中四種修飾符的限制範圍。
private:修飾的成員只能在同類中別訪問,而在同包、子類和其餘包中都不能被訪問
public:修飾的成員在同類、同包、子類(繼承自本類)、其餘包均可以訪問
protected:修飾的成員在同類、同包、子類中能夠訪問,其餘包中不能被訪問
default:修飾的成員在同類、同包中能夠訪問,但其餘包中無論是否是子類都不能被訪問
7、Object中的方法
構造函數
hashCode():用戶獲取對象的hash值,用於檢索
queals():用於確認兩個對象是否相等;補充,哈希值相同的對象不必定equals(),但equals()的兩個對象,hash值必定相等
toString():返回一個String對象,用來標識本身
getClass():返回一個class對象,打印的格式通常爲 class package.name.xxx,常常用於java的反射機制
clone():用來另存一個當前存在的對象
finalize():垃圾回收的時候回用到,匿名對象回收以前會調用到
wait():用於讓當前線程失去操做權限,當前線程進入等待序列
wait(long)、wait(long,int):用戶設定下一次獲取鎖的距離當前釋放鎖的間隔時間
notify():用於隨機通知一個持有對象鎖的線程獲取操做的權限
notifyAll():用於通知全部持有對象鎖的線程獲取操做權限
8、接口和抽象類的區別
答:一個類能夠實現多個接口,但只能繼承一個抽象類;抽象類能夠包含具體的方法,接口全部的方法都是抽象的(JDK8開始新增功能接口中有default方法);抽象類能夠聲明和使用字段,接口則不能,但能夠建立靜態的final常量;抽象類的方法能夠是protected、public、private或者默認的package,接口的方法都是public;抽象類能夠定義構造函數,接口不能;接口被聲明爲public,省略後,包外的類不能訪問接口;
9、動態代理的兩種方式,以及區別
答:jdk動態代理和cglib動態代理;
JDK動態代理只能對實現了接口的類生成代理,而不能針對類;cglib是針對類實現代理,主要是對指定的類生成一個子類,覆蓋其中的方法,由於是繼承,因此該類或方法最好不要聲明稱final,final能夠阻止繼承和多態;
10、java序列化的方式
答:實現Serializable接口、實現Externalizable接口(通常只但願序列化一部分數據,其餘數據都使用transient修飾的話有點麻煩,這時候能夠使用externalizable接口,指定序列化的屬性)
11、傳值和傳引用的區別,java是怎麼樣的,有沒有傳值傳引用
答:首先,java中是沒有指針的,只存在值傳遞;而咱們常常看到對於對象的傳遞彷佛有點像引用傳遞,能夠改變對象中的某個屬性的值,請不要被這個假象矇蔽了雙眼,實際上這個傳入函數的值是對象引用的拷貝,即傳遞的是引用的地址值,因此仍是按值傳遞;
傳值調用時,改變的是形參的值,並無改變實參的值,實參的值能夠傳遞給形參,可是這個傳遞是單向的,形參不能傳遞會實參;
傳引用調用時,若是參數是對象,不管是對象作了何種操做,都不會改變實參對象的引用,可是若是改變了對象的內容,就會改變實參對象的內容;
12、@transactional註解在什麼狀況下會失效,爲何。
答:一個目標對象的方法調用改目標對象的另一個方法時,即便被調用的方法已使用了@Transactional註解標記,事務也不會有效執行;Spring的官方說明在代理下(默認或者配置爲proxy-targer-class="true"),只有當前代理類的外部方法調用註解方法時代理纔會被攔截。
1、B+樹
參考:B+樹介紹
2、八大排序算法
參考:八大排序算法JAVA實現
3、一致性Hash算法,一致性Hash算法的應用
答:一致性hash算法是一個負載均衡算法,能夠用在分佈式緩存、數據庫的分庫分表等場景,還能夠應用在負載均衡器中做爲負載均衡算法。在多臺服務器時,對於某個請求資源經過hash算法,映射到某一臺服務器,當增長或者減小一臺服務器時,可能會改變這些資源對應的hash值,這樣可能致使一部分緩存或者數據的丟失。一致性hash就是儘量在將同一個資源請求到同一臺服務器中;
1、JVM的內存結構
答:主要分爲三大塊堆內存、方法區、棧;棧又分爲JVM棧、本地方法棧
堆(heap space),堆內存是JVM中最大的一塊,有年輕代和老年代組成,而年輕代又分爲三分部分,Eden區,From Survivor,To Survivor,默認狀況下按照8:1:1來分配
方法區(Method area),存儲類信息、常量、靜態變量等數據,是線程共享的區域
程序計數器(Program counter Register),是一塊較小的內存空間,是當前線程所執行的字節碼的行號指示器
JVM棧(JVM stacks),也是線程私有的,生命週期與線程相同,每一個方法被執行時都會建立一個棧幀,用於存儲局部變量表、操做棧、動態連接、方法出口等信息
本地方法棧(Native Mthod Stacks),爲虛擬機使用的native方法服務
2、關於垃圾回收和常見的GC算法,請參考:GC專家系列-理解java垃圾回收
1、JAVA實現多線程的幾種方式
a、繼承Thread類實現
public class MyThread extends Thread {
public void run() {
System.out.println("MyThread.run()");
}
}
MyThread myThread1 = new MyThread();
MyThread myThread2 = new MyThread();
myThread1.start();
myThread2.start();
b、實現Runnable接口
若是本身的類已經extends另外一個類,就沒法直接extends Thread,此時,必須實現一個Runnable接口,以下:
public class MyThread extends OtherClass implements Runnable {
public void run() {
System.out.println("MyThread.run()");
}
}
MyThread myThread = new MyThread();
Thread thread = new Thread(myThread);
thread.start();
c、使用ExecutorService、Callable、Future實現有返回結果的多線程
import java.util.concurrent.*;
import java.util.Date;
import java.util.List;
import java.util.ArrayList;
/**
* 有返回值的線程
*/
@SuppressWarnings("unchecked")
public class Test {
public static void main(String[] args) throws ExecutionException,
InterruptedException {
System.out.println("----程序開始運行----");
Date date1 = new Date();
int taskSize = 5;
// 建立一個線程池
ExecutorService pool = Executors.newFixedThreadPool(taskSize);
// 建立多個有返回值的任務
List<Future> list = new ArrayList<Future>();
for (int i = 0; i < taskSize; i++) {
Callable c = new MyCallable(i + " ");
// 執行任務並獲取Future對象
Future f = pool.submit(c);
// System.out.println(">>>" + f.get().toString());
list.add(f);
}
// 關閉線程池
pool.shutdown();
// 獲取全部併發任務的運行結果
for (Future f : list) {
// 從Future對象上獲取任務的返回值,並輸出到控制檯
System.out.println(">>>" + f.get().toString());
}
Date date2 = new Date();
System.out.println("----程序結束運行----,程序運行時間【"
+ (date2.getTime() - date1.getTime()) + "毫秒】");
}
}
class MyCallable implements Callable<Object> {
private String taskNum;
MyCallable(String taskNum) {
this.taskNum = taskNum;
}
public Object call() throws Exception {
System.out.println(">>>" + taskNum + "任務啓動");
Date dateTmp1 = new Date();
Thread.sleep(1000);
Date dateTmp2 = new Date();
long time = dateTmp2.getTime() - dateTmp1.getTime();
System.out.println(">>>" + taskNum + "任務終止");
return taskNum + "任務返回運行結果,當前任務時間【" + time + "毫秒】";
}
}
2、Callable和Future
答:Callable接口相似於Runnable,可是Runnable不會返回結果,而且沒法拋出返回結果的異常,而Callable更強大,被線程執行之後,能夠返回值,這個返回值就是經過Future拿到,也就是說,Future能夠拿到異步執行任務的返回值,能夠看如下例子:
import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
public class Test {
public static void main(String[] args) {
Callable<Integer> callable = new Callable<Integer>() {
@Override
public Integer call() throws Exception {
return new Random().nextInt(100);
}
};
FutureTask<Integer> futureTask = new FutureTask<Integer>(callable);
new Thread(futureTask).start();
try {
Thread.sleep(1000);
System.err.println(futureTask.get());
} catch (Exception e) {
e.printStackTrace();
}
}
}
ExecutorService繼承自Executor,目的是爲咱們管理Thread對象,從而簡化併發變成,Executor使咱們無需顯示的去管理線程的聲明週期,是JDK5以後啓動任務的首選方式。
執行多個帶返回值的任務,並取得多個返回值,代碼以下:
import java.util.concurrent.Callable;
import java.util.concurrent.CompletionService;
import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class CallableAndFuture {
public static void main(String[] args) {
ExecutorService threadPool = Executors.newCachedThreadPool();
CompletionService<Integer> cs = new ExecutorCompletionService<Integer>(threadPool);
for( int i = 0; i < 5; i++ ){
final int taskId = i;
cs.submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
return taskId;
}
});
}
for( int i = 0; i < 5; i++ ){
try {
System.err.println(cs.take().get());
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
3、線程池的參數有哪些,在線程池建立一個線程的過程
corePoolSize:核心線程數,可以同時執行的任務數量
maximumPoolSize:除去緩衝隊列中等待的任務,最大能容納的任務數(其實就是包括了核心線程池的數量)
keepAliveTime:超出workQueue的等待任務的存活時間,就是指maximumPoolSize裏面的等待任務的存活等待時間
unit:時間單位
workQueue:阻塞等待線程的隊列,通常使用new LinkedBlockingQueue()這個,若是不指定容量,會一直往裏添加,沒有限制,workQueue永遠不會滿,通常選擇沒有容量上限的隊列
threadFactory:建立線程的工廠,使用系統默認的類
handler:當任務數超過maximumPoolSize時,對任務的處理策略,默認策略是拒絕添加
執行流程:當線程數小於corePoolSize時,每添加一個任務,則當即開啓線程執行;當corePoolSize滿的時候,後面添加的任務將放入緩衝隊列workQueue等待;當workQueue滿的時候,看是否超過maximumPoolSize線程數,若是超過,則拒絕執行,若是沒有超過,則建立線程理解執行;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* 對線程池進行管理和封裝
* @author guoqing
*
*/
public class ThreadPoolManager {
private static ThreadPoolManager mInstance = new ThreadPoolManager();
private ThreadPoolExecutor executor;
private int corePoolSize; //核心線程池數量,表示可以同時執行的任務數量
private int maximumPoolSize; //最大線程池數量,實際上是包含了核心線程池數量在內的
private long keepAliveTime = 1; //存活時間,表示最大線程池中等待任務的存活時間
private TimeUnit unit = TimeUnit.HOURS; //存活時間的時間單位
public static ThreadPoolManager getInstance() {
return mInstance;
}
private ThreadPoolManager() {
//核心線程數量的計算規則:當前設備的可用處理器核心數*2+1,可以讓cpu獲得最大效率的發揮
corePoolSize = Runtime.getRuntime().availableProcessors()*2+1;
maximumPoolSize = corePoolSize; //雖然用不到,可是不能爲0,不然會報錯
//線程池機制:領工資的機制
executor = new ThreadPoolExecutor(corePoolSize,
maximumPoolSize,
keepAliveTime,
unit,
new LinkedBlockingQueue<Runnable>(), //緩衝隊列,超出核心線程池的任務會被放入緩衝隊列中等待
Executors.defaultThreadFactory(), //建立線程的工廠類
new ThreadPoolExecutor.AbortPolicy() //當最大線程池也超出的時候,則拒絕執行
);
}
/**
* 往線程池中添加任務
* @param r
*/
public void executor(Runnable r) {
if(r!=null) {
executor.execute(r);
}
}
/**
* 從線程池中移除任務
* @param r
*/
public void remove(Runnable r) {
if(r!=null) {
executor.remove(r);
}
}
}
4、volatile關鍵字的做用,原理
答:保證內存可見性和禁止指令重排。實現原理可參考:JAVA併發變成--valatile關鍵字剖析
5、synchronized關鍵字的用法,優缺點
答:java關鍵字,當它用來修飾一個方法或者代碼塊的時候,可以保證在同一時刻最多隻有一個線程執行該代碼段的代碼;
synchronized修飾的方法或者對象,只能以同步的方式執行,會引發性能問題;沒法中斷一個正在等候得到鎖的線程,也沒法經過投票得到鎖;一個優先級高的線程等待一個優先級低的線程釋放鎖會致使優先級倒置,引發性能風險;
6、Lock接口有哪些實現類,使用場景是什麼
答:Lock接口有三個實現類,一個是ReentrantLock,另兩個是ReentrantReadWriteLock類中的兩個靜態內部類ReadLock和WriteLock。
使用場景:通常應用於多度少寫,由於讀的線程之間沒有競爭,因此比起synchronzied,性能要好不少;
7、悲觀鎖、樂觀鎖的優缺點,CAS有什麼缺陷,該如何解決
悲觀鎖:老是假設最壞的狀況,每次去拿數據的時候都認爲別人會修改,因此每次拿數據的時候都會上鎖,這樣別人拿數據的時候就會阻塞知道它拿到鎖;好比關係型數據庫的行鎖、表鎖、讀鎖、寫鎖;好比java裏面的同步原語synchronized關鍵字的實現也是悲觀鎖;
樂觀鎖:每次去拿數據的時候都認爲別人不會修改,因此不會上鎖,可是在更新的時候會判斷一下再次期間別人有沒有更新這個數據。樂觀鎖適用於多讀的應用類型,能夠提升吞吐量。java中java.util.conncurrent.atomic包下面的原子變量類就是使用了樂觀鎖的一種實現方式CAS實現的;
CAS:CAS是樂觀鎖技術,當多個線程嘗試使用CAS同時更新同一個變量時,只有其中一個線程能更新變量的值,而其餘線程都失敗,失敗的線程不會被掛起,而是被告知此次競爭失敗,並能夠再次嘗試;
CAS的缺陷:ABA問題、循環時間長開銷大,只能保證一個共享變量的原子操做;
8、ABC三個線程如何保證順序執行
答:用Thread.join() 方法,或者線程池newSingleThreadExecutor(原理是會將全部線程放入一個隊列,而隊列則保證了FIFO),也能夠經過ReentrantLock,state整數用阿里判斷輪到誰來執行
9、線程的狀態都有哪些(五大狀態)
新建狀態(new):當用new操做符建立一個線程時,如new Thread(),線程尚未開始運行,此時處於仙劍狀態;
就緒狀態(runnable):一個新建立的線程並不自動開始運行,要執行線程,必需要調用線程的start()方法,當線程對象調用start()方法即啓動了線程,start()方法建立線程運行的系統資源,並調度線程運行run()方法。當start()方法返回後,線程就處於就緒狀態;
運行狀態(running):當線程得到cpu時間後,他才進入運行狀態,真正開始實行run()方法
阻塞狀態(blocked):當線程運行過程當中,可能因爲各類緣由進入阻塞狀態;
a.線程經過調用sleep方法進入睡眠狀態
b.線程調用一個在I/O上被阻塞的操做,即該操做在輸入輸出操做完成以前不會返回到它的調用者
c.線程試圖獲得一個鎖,而該鎖正被其餘線程持有
d.線程正等待某個觸發條件
死亡狀態(dead):run方法天然退出而天然死亡,或者一個未捕獲的異常終止了run方法而使線程猝死
10、sleep和wait的區別
答:首先,sleep()方法屬於Thread類的,而wait()方法是屬於Object類的;sleep()方法致使了程序暫停執行指定的時間,讓出cpu給其餘線程,可是他的監控狀態依然保持,當指定的時間到了又自動回恢復運行狀態,調用了sleep()方法的過程當中,線程不會釋放對象鎖;而當調用了wait()方法的時候,線程回放棄對象鎖,進入等待此對象的等待鎖定池,只有針對此對象調用notify()方法後本線程才進入對象鎖定池準備。
11、notify()和notifyAll()的區別
答:notify()方法表示,當前線程已經放棄對資源的佔有,通知等待的線程來獲取對資源的佔有權,可是隻有一個線程可以從wait狀態中恢復;notifyAll()方法表示,當前的線程已經放棄對資源的佔有,通知全部的等待線程從wait()方法後的語句開始執行,但最終只有一個線程能競爭得到鎖並執行;notify()是對notifyAll()的一個優化,
12、ThreadLocal的瞭解,實現原理。
答:ThreadLocal,線程本地變量。定義了一個ThreadLocal,每一個線程往這個ThreadLocal中讀寫都是線程隔離的,互相之間不會影響,他提供了一種將可變數據經過每一個線程有本身的獨立副本從而實現線程封閉的機制;實現的思路,Thread類有一個類型爲ThreadLocal.ThreadLocalMap的實例變量threadLocals,也就是說每一個線程都有一個本身的ThreadLocalMap。ThreadLocalMap有本身的獨立實現,能夠簡單的將它的key視做ThreadLocal,value爲代碼中放入的值(實際上key並非ThreadLocal本省,而是它的一個弱引用)。每一個線程在往ThreadLocal裏set值的時候,都會往本身的ThreadLocalMap裏存,讀也是已某個ThreadLocal做爲引用,在本身的map裏找對應的key,從而實現了線程的隔離。若是想詳細瞭解,能夠參考:ThreadLocal源碼解讀
1、常見的數據庫優化手段
答:庫表優化,表設計合理化,符合三大範式;添加適當的索引(普通索引、主鍵索引、惟一索引、全文索引);分庫分表;讀寫分離等;sql語句優化,定位執行效率低,慢sql的語句,經過explain分析低效率的緣由;
2、索引的優缺點,什麼字段上創建索引
答:優勢方面:第一,經過建立惟一索引能夠保證數據的惟一性;第二,能夠大大加快數據的檢索速度,是主要目的;第三;在使用分組和排序子句進行數據檢索時,能夠顯著減小查詢中分組和排序的時間;第四,能夠在查詢中使用優化隱藏器,提升系統的性能;
缺點方面:第一,建立索引和維護索引要耗費時間,而且隨着數據量的增長而增長;第二,每個索引須要佔用額外的物理空間,須要的磁盤開銷更大;第三,當對錶中的數據進行增長、刪除、修改操做時,索引也要動態維護,下降了數據的維護速度;
通常來講,在常常須要搜索的列上,強制該列的惟一性和組織表中數據的排列結構的列,在常常用在連接的列上,在常常須要排序的列上,在常用在where字句的列上能夠添加索引,以提高查詢速度;一樣,對於一些甚少使用或者參考的列,只有不多數值的列(如性別),定義爲text,image,bit的列,修改性能遠遠大於檢索性能的列不適合添加索引;
3、數據庫鏈接池
答:數據庫鏈接池(Connection pooling)是程序啓動時創建足夠的數據庫鏈接,並將這些鏈接組成一個鏈接池,由程序動態的對池中的鏈接進行申請、使用、釋放;
(1)程序初始化時建立鏈接池
(2)使用時向鏈接池申請可用鏈接
(3)使用完畢,將鏈接返還給鏈接池
(4)程序退出時,斷開全部的鏈接,並釋放資源
1、TCP和UDP的區別
答:TCP(傳輸控制協議),UDP(用戶數據報協議)
(1)TCP面向鏈接(如打電話先撥號創建鏈接);UDP是無鏈接的,即發送數據以前不須要創建鏈接;
(2)TCP提供可靠的服務。也就是說,經過TCP鏈接傳送的數據,無差錯,不丟失,不重複,且按序達到;UDP盡最大努力交付,即不保證可靠交付;
(3)TCP面向字節流,其實是TCP把數據當作一連串無結構的字節流;UDP是面向報文,UDP沒有擁塞控制,所以網絡出現擁塞不會使源主機的發送速率下降(對實時應用頗有用,如IP電話,實時視頻會議等)
(4)每一條TCP鏈接只能是點到點的,UDP支持一對一,一對多,多對一和多對多的交互通訊;
(5)TCP首部開銷20字節,UDP首部開銷8字節;
(6)TCP的邏輯通訊信道是全雙工的可靠信道,DUP則是不可靠信道;
2、三次握手,四次揮手,爲何要四次揮手。
答:三次握手的目的是創建可靠的通訊信道,簡單來講就是數據的發送與接收,主要目的是雙方確認本身與對方的發送和接收機能正常;
第一次握手:Client什麼都不能確認,Server確認了對方發送正常;
第二次握手:Clent確認了,本身發送、接收正常,對方發送、接收正常;Server確認了本身接收正常,對方發送正常;
第三次握手:Clent確認了,本身發送、接收正常,對方發送、接收正常;Server確認了本身發送、接收正常,對方發送、接收正常;
因此,通過三次握手以後,就能確認雙方收發功能都正常;
四次揮手:
A:「喂,我不說了 (FIN)。」A->FIN_WAIT1
B:「我知道了(ACK)。等下,上一句還沒說完。Balabala…..(傳輸數據)」B->CLOSE_WAIT | A->FIN_WAIT2
B:」好了,說完了,我也不說了(FIN)。」B->LAST_ACK
A:」我知道了(ACK)。」A->TIME_WAIT | B->CLOSED
A等待2MSL,保證B收到了消息,不然重說一次」我知道了」,A->CLOSED
3、長鏈接和短鏈接。
短鏈接:鏈接=》傳輸數據=》關閉鏈接
HTTP是無狀態的,瀏覽器和服務器之間每進行一次http操做,就創建一次鏈接,但任務結束就中斷鏈接;也能夠理解爲短鏈接是指socket鏈接後,發送接收完數據立刻斷開鏈接;
長鏈接:鏈接=》傳輸數據=》保持鏈接=》傳輸數據=》。。。=》關閉鏈接
長鏈接指創建socket鏈接後無論是否使用都保持鏈接,但安全性較差;
此處推薦閱讀:java23種設計模式深刻理解
1、單例模式的幾種寫法
懶漢模式
public class Singleton {
private static Singleton instance = null;
private Singleton(){}
public static synchronized Singleton getInstance(){
//若是尚未被實例化過,就實例化一個,而後返回
if(instance == null){
instance = new Singleton();
}
return instance;
}
}
餓漢模式
public class Singleton {
//類加載的時候instance就已經指向了一個實例
private static Singleton instance = new Singleton();
private Singleton(){}
public static Singleton getInstance(){
return instance;
}
}
雙重檢驗鎖
public class Singleton {
private static Singleton instance = null;
private Singleton(){}
public static Singleton getInstance(){
if(instance == null){
synchronized (Singleton.class){
if(instance == null){
instance = new Singleton();
}
}
}
return instance;
}
}
靜態內部類:由於JAVA靜態內部類的特性,加載的時候不會加載內部靜態類,使用的時候纔會加載,而使用的時候類加載又是線程安全的,這就完美達到了效果;
public class Singleton {
private static class SingletonHolder{
private static Singleton instance = new Singleton();
}
private Singleton(){}
public static Singleton getInstance(){
return SingletonHolder.instance;
}
}
枚舉:
public enum Singleton {
INSTANCE;
}
2、Spring使用了哪些設計模式
(1)工廠模式,在各類BeanFactory以及ApplicationContext建立中都用到了;
(2)模板模式,也是在各類BeanFactory以及ApplicationContext建立中都用到了;
(3)代理模式,在AOP實現中用到了JDK的動態代理;
(4)單例模式,好比建立bean的時候;
(5)策略模式,第一個地方,加載資源文件的地方,使用了不一樣的方法,好比:classPathResource,FileSystemResource,ServletContextResource,UrlResource但他們都有共同的接口Resource;第二個地方就是AOP的實現中,採用了不一樣的方式,JDK動態代理和CGLIB代理;
1、分佈式事務的控制
能夠參考分佈式系統事務一致性解決方案
2、分佈式鎖
答:通常使用zk瞬時有序節點實現的分佈式鎖,或者利用redis的setnx()封裝分佈式鎖;提供思路,具體的能夠自行詳細理解;
3、分佈式session如何設計
答:一個比較成熟的方案是經過redis進行session共享。詳細的原理能夠參考一種分佈式session實現方案
4、關於dubbo
能夠參考博文:Dubbo學習總結(2)——Dubbo架構詳解
5、能夠了解zk相關知識
1、redis和memcached的區別
(1)redis和memcache都是將數據放入內存中,都是內存數據庫。可是memcache能夠緩存圖片、視頻等數據;
(2)redis不只僅支持簡單的k/v數據,還提供list、set、hash等數據結構的存儲;
(3)虛擬內存--redis當物理內存用完時,能夠將一些好久沒有用到的value交換到磁盤;
(4)過時策略--memcache在set時就指定,例如set key1008,即永不過時,redis經過expire設定;
(5)分佈式--設定memcache集羣,利用magent作一主多從;redis能夠作一主多從或一主一從;
(6)存儲數據安全--memcache掛掉後,數據沒了,redis能夠按期保存到磁盤進行持久化;
(7)災難恢復--memcache掛掉後,數據不可恢復。redis數據丟失後能夠經過aof恢復;
(8)redis支持數據備份,即master-slave主備模式;
2、redis是單線程的麼(是的)
3、redis的持久化策略
答:rdb:快照形式是直接把內存中的數據保存到一個dump文件中,定時保存
aof:把全部的對redis的服務器進行修改的命令都存到一個文件裏,命令的集合
1、SpringMvc工做原理
(1)用戶發送請求至前端控制器DispatcherServlet
(2)DispatcherServlet收到請求調用HandlerMapping處理映射器
(3)處理器映射器找到具體的處理器(能夠根據xml配置、註解進行查找),生成處理器對象及處理器攔截器(若有則生成)一併返回給DispatcherServlet
(4)DispatcherServlet調用HandlerAdapter處理器映射器
(5)HandlerAdapter通過適配調用具體的處理器(Controller,也叫後端控制器)
(6)Controller執行完成返回ModelAndView
(7)HandlerAdapter將Controller執行結果ModelAndView返回給DispatcherServlet
(8)DispatcherServlet將ModelAndView傳給ViewResolver視圖解析器
(9)ViewResolver解析後返回具體的view
(10)DispatcherServlet根據view進行試圖渲染(即將模型數據填充至視圖中)
(11)DispatcherServlet響應用戶
如下組件一般使用框架提供實現:
DispatcherServlet:做爲前端控制器,整個流程控制的中心,控制其它組件執行,統一調度,下降組件之間的耦合性,提升每一個組件的擴展性。
HandlerMapping:經過擴展處理器映射器實現不一樣的映射方式,例如:配置文件方式,實現接口方式,註解方式等。
HandlAdapter:經過擴展處理器適配器,支持更多類型的處理器。
ViewResolver:經過擴展視圖解析器,支持更多類型的視圖解析,例如:jsp、freemarker、pdf、excel等。
2、Quartz概念及原理
org.quartz.Job:它是一個抽象接口,表示一個工做,也是咱們要執行的具體的內容,只定義了一個接口方法:void execute(JobExecutionContext context)
org.quartz.JobDetail:JobDetail表示一個具體的可執行的調度程序,Job是這個可執行調度程序所要執行的內容,它包含了這個調度任務的方案和策略
org.quartz.Trigger:Trigger是一個抽象接口,表示一個調度參數的配置,經過配置他,來告訴調度器何時去調用JobDetail
org.quartz.Scheduler:一個調度容器,能夠註冊多個Trigger和JobDetail。當Trigger和JobDetail組合,就能夠被Scheduler容器調度了
3、Spring的IOC有什麼優點
答:要了解IOC首先要明白依賴倒置原則(Dependency Inversion Principle),就是把本來的高層建築依賴底層建築倒置過來,變成底層建築依賴高層建築。高層建築決定須要什麼,底層去實現這樣的需求,可是高層並不用管底層的是怎麼實現的;而控制反轉(Inversion of Control)就是依賴倒置原則的一種代碼的設計思路;
IOC思想的核心,資源不禁使用資源的雙方管理,由不適用資源的第三方管理。
優點:資源集中管理,實現資源的可配置和易管理;下降了使用資源雙方的依賴程度,也就是下降了耦合度;
ZooKeeper是一個開放源碼的分佈式協調服務,它是集羣的管理者,監視着集羣中各個節點的狀態根據節點提交的反饋進行下一步合理操做。最終,將簡單易用的接口和性能高效、功能穩定的系統提供給用戶。
分佈式應用程序能夠基於Zookeeper實現諸如數據發佈/訂閱、負載均衡、命名服務、分佈式協調/通知、集羣管理、Master選舉、分佈式鎖和分佈式隊列等功能。
Zookeeper保證了以下分佈式一致性特性:
· 順序一致性
· 原子性
· 單一視圖
· 可靠性
· 實時性(最終一致性)
客戶端的讀請求能夠被集羣中的任意一臺機器處理,若是讀請求在節點上註冊了監聽器,這個監聽器也是由所鏈接的zookeeper機器來處理。對於寫請求,這些請求會同時發給其餘zookeeper機器而且達成一致後,請求才會返回成功。所以,隨着zookeeper的集羣機器增多,讀請求的吞吐會提升可是寫請求的吞吐會降低。
有序性是zookeeper中很是重要的一個特性,全部的更新都是全局有序的,每一個更新都有一個惟一的時間戳,這個時間戳稱爲zxid(Zookeeper Transaction Id)。而讀請求只會相對於更新有序,也就是讀請求的返回結果中會帶有這個zookeeper最新的zxid。
1、文件系統
2、通知機制
Zookeeper提供一個多層級的節點命名空間(節點稱爲znode)。與文件系統不一樣的是,這些節點均可以設置關聯的數據,而文件系統中只有文件節點能夠存放數據而目錄節點不行。
Zookeeper爲了保證高吞吐和低延遲,在內存中維護了這個樹狀的目錄結構,這種特性使得Zookeeper不能用於存放大量的數據,每一個節點的存放數據上限爲1M。
ZAB協議是爲分佈式協調服務Zookeeper專門設計的一種支持崩潰恢復的原子廣播協議。
ZAB協議包括兩種基本的模式:崩潰恢復和消息廣播。
當整個zookeeper集羣剛剛啓動或者Leader服務器宕機、重啓或者網絡故障致使不存在過半的服務器與Leader服務器保持正常通訊時,全部進程(服務器)進入崩潰恢復模式,首先選舉產生新的Leader服務器,而後集羣中Follower服務器開始與新的Leader服務器進行數據同步,當集羣中超過半數機器與該Leader服務器完成數據同步以後,退出恢復模式進入消息廣播模式,Leader服務器開始接收客戶端的事務請求生成事物提案來進行事務請求處理。
·
PERSISTENT-持久節點
除非手動刪除,不然節點一直存在於Zookeeper上
·
EPHEMERAL-臨時節點
臨時節點的生命週期與客戶端會話綁定,一旦客戶端會話失效(客戶端與zookeeper鏈接斷開不必定會話失效),那麼這個客戶端建立的全部臨時節點都會被移除。
·
PERSISTENT_SEQUENTIAL-持久順序節點
基本特性同持久節點,只是增長了順序屬性,節點名後邊會追加一個由父節點維護的自增整型數字。
·
EPHEMERAL_SEQUENTIAL-臨時順序節點
基本特性同臨時節點,增長了順序屬性,節點名後邊會追加一個由父節點維護的自增整型數字。
Zookeeper容許客戶端向服務端的某個Znode註冊一個Watcher監聽,當服務端的一些指定事件觸發了這個Watcher,服務端會向指定客戶端發送一個事件通知來實現分佈式的通知功能,而後客戶端根據Watcher通知狀態和事件類型作出業務上的改變。
工做機制:
· 客戶端註冊watcher
· 服務端處理watcher
· 客戶端回調watcher
Watcher特性總結:
1.
一次性
不管是服務端仍是客戶端,一旦一個Watcher被觸發,Zookeeper都會將其從相應的存儲中移除。這樣的設計有效的減輕了服務端的壓力,否則對於更新很是頻繁的節點,服務端會不斷的向客戶端發送事件通知,不管對於網絡仍是服務端的壓力都很是大。
2.
客戶端串行執行
客戶端Watcher回調的過程是一個串行同步的過程。
3. 輕量
· Watcher通知很是簡單,只會告訴客戶端發生了事件,而不會說明事件的具體內容。
· 客戶端向服務端註冊Watcher的時候,並不會把客戶端真實的Watcher對象實體傳遞到服務端,僅僅是在客戶端請求中使用boolean類型屬性進行了標記。
watcher event異步發送watcher的通知事件從server發送到client是異步的,這就存在一個問題,不一樣的客戶端和服務器之間經過socket進行通訊,因爲網絡延遲或其餘因素致使客戶端在不通的時刻監聽到事件,因爲Zookeeper自己提供了ordering guarantee,即客戶端監聽事件後,纔會感知它所監視znode發生了變化。因此咱們使用Zookeeper不能指望可以監控到節點每次的變化。Zookeeper只能保證最終的一致性,而沒法保證強一致性。
註冊watcher getData、exists、getChildren
觸發watcher create、delete、setData
當一個客戶端鏈接到一個新的服務器上時,watch將會被以任意會話事件觸發。當與一個服務器失去鏈接的時候,是沒法接收到watch的。而當client從新鏈接時,若是須要的話,全部先前註冊過的watch,都會被從新註冊。一般這是徹底透明的。只有在一個特殊狀況下,watch可能會丟失:對於一個未建立的znode的exist watch,若是在客戶端斷開鏈接期間被建立了,而且隨後在客戶端鏈接上以前又刪除了,這種狀況下,這個watch事件可能會被丟失。
1. 調用getData()/getChildren()/exist()三個API,傳入Watcher對象
2. 標記請求request,封裝Watcher到WatchRegistration
3. 封裝成Packet對象,發服務端發送request
4. 收到服務端響應後,將Watcher註冊到ZKWatcherManager中進行管理
5. 請求返回,完成註冊。
1.
服務端接收Watcher並存儲
接收到客戶端請求,處理請求判斷是否須要註冊Watcher,須要的話將數據節點的節點路徑和ServerCnxn(ServerCnxn表明一個客戶端和服務端的鏈接,實現了Watcher的process接口,此時能夠當作一個Watcher對象)存儲在WatcherManager的WatchTable和watch2Paths中去。
2.
Watcher觸發
以服務端接收到 setData() 事務請求觸發NodeDataChanged事件爲例:
·
封裝WatchedEvent
將通知狀態(SyncConnected)、事件類型(NodeDataChanged)以及節點路徑封裝成一個WatchedEvent對象
·
查詢Watcher
從WatchTable中根據節點路徑查找Watcher
· 沒找到;說明沒有客戶端在該數據節點上註冊過Watcher
· 找到;提取並從WatchTable和Watch2Paths中刪除對應Watcher(從這裏能夠看出Watcher在服務端是一次性的,觸發一次就失效了)
調用process方法來觸發Watcher
這裏process主要就是經過ServerCnxn對應的TCP鏈接發送Watcher事件通知。
客戶端SendThread線程接收事件通知,交由EventThread線程回調Watcher。客戶端的Watcher機制一樣是一次性的,一旦被觸發後,該Watcher就失效了。
目前在Linux/Unix文件系統中使用,也是使用最普遍的權限控制方式。是一種粗粒度的文件系統權限控制模式。
包括三個方面:
· 權限模式(Scheme)
o IP:從IP地址粒度進行權限控制
o Digest:最經常使用,用相似於 username:password 的權限標識來進行權限配置,便於區分不一樣應用來進行權限控制
o World:最開放的權限控制方式,是一種特殊的digest模式,只有一個權限標識「world:anyone」
o Super:超級用戶
·
受權對象
受權對象指的是權限賦予的用戶或一個指定實體,例如IP地址或是機器燈。
· 權限 Permission
o CREATE:數據節點建立權限,容許受權對象在該Znode下建立子節點
o DELETE:子節點刪除權限,容許受權對象刪除該數據節點的子節點
o READ:數據節點的讀取權限,容許受權對象訪問該數據節點並讀取其數據內容或子節點列表等
o WRITE:數據節點更新權限,容許受權對象對該數據節點進行更新操做
o ADMIN:數據節點管理權限,容許受權對象對該數據節點進行ACL相關設置操做
3.2.0版本後,添加了 Chroot特性,該特性容許每一個客戶端爲本身設置一個命名空間。若是一個客戶端設置了Chroot,那麼該客戶端對服務器的任何操做,都將會被限制在其本身的命名空間下。
經過設置Chroot,可以將一個客戶端應用於Zookeeper服務端的一顆子樹相對應,在那些多個應用公用一個Zookeeper進羣的場景下,對實現不一樣應用間的相互隔離很是有幫助。
分桶策略:將相似的會話放在同一區塊中進行管理,以便於Zookeeper對會話進行不一樣區塊的隔離處理以及同一區塊的統一處理。
分配原則:每一個會話的「下次超時時間點」(ExpirationTime)
計算公式:
ExpirationTime_ = currentTime + sessionTimeout
ExpirationTime1 = (ExpirationTime_ / ExpirationInrerval + ) * ExpirationInterval , ExpirationInterval 是指 Zookeeper 會話超時檢查時間間隔,默認 tickTime
· 事務請求的惟一調度和處理者,保證集羣事務處理的順序性
· 集羣內部各服務的調度者
· 處理客戶端的非事務請求,轉發事務請求給Leader服務器
· 參與事務請求Proposal的投票
· 參與Leader選舉投票
3.3.0版本之後引入的一個服務器角色,在不影響集羣事務處理能力的基礎上提高集羣的非事務處理能力
· 處理客戶端的非事務請求,轉發事務請求給Leader服務器
· 不參與任何形式的投票
服務器具備四種狀態,分別是LOOKING、FOLLOWING、LEADING、OBSERVING。
· LOOKING:尋找Leader狀態。當服務器處於該狀態時,它會認爲當前集羣中沒有Leader,所以須要進入Leader選舉狀態。
· FOLLOWING:跟隨者狀態。代表當前服務器角色是Follower。
· LEADING:領導者狀態。代表當前服務器角色是Leader。
· OBSERVING:觀察者狀態。代表當前服務器角色是Observer。
Leader選舉是保證分佈式數據一致性的關鍵所在。當Zookeeper集羣中的一臺服務器出現如下兩種狀況之一時,須要進入Leader選舉。
(1) 服務器初始化啓動。
(2) 服務器運行期間沒法和Leader保持鏈接。
下面就兩種狀況進行分析講解。
1. 服務器啓動時期的Leader選舉
若進行Leader選舉,則至少須要兩臺機器,這裏選取3臺機器組成的服務器集羣爲例。在集羣初始化階段,當有一臺服務器Server1啓動時,其單獨沒法進行和完成Leader選舉,當第二臺服務器Server2啓動時,此時兩臺機器能夠相互通訊,每臺機器都試圖找到Leader,因而進入Leader選舉過程。選舉過程以下
(1) 每一個Server發出一個投票。因爲是初始狀況,Server1和Server2都會將本身做爲Leader服務器來進行投票,每次投票會包含所推舉的服務器的myid和ZXID,使用(myid, ZXID)來表示,此時Server1的投票爲(1, 0),Server2的投票爲(2, 0),而後各自將這個投票發給集羣中其餘機器。
(2) 接受來自各個服務器的投票。集羣的每一個服務器收到投票後,首先判斷該投票的有效性,如檢查是不是本輪投票、是否來自LOOKING狀態的服務器。
(3) 處理投票。針對每個投票,服務器都須要將別人的投票和本身的投票進行PK,PK規則以下
· 優先檢查ZXID。ZXID比較大的服務器優先做爲Leader。
· 若是ZXID相同,那麼就比較myid。myid較大的服務器做爲Leader服務器。
對於Server1而言,它的投票是(1, 0),接收Server2的投票爲(2, 0),首先會比較二者的ZXID,均爲0,再比較myid,此時Server2的myid最大,因而更新本身的投票爲(2, 0),而後從新投票,對於Server2而言,其無須更新本身的投票,只是再次向集羣中全部機器發出上一次投票信息便可。
(4) 統計投票。每次投票後,服務器都會統計投票信息,判斷是否已經有過半機器接受到相同的投票信息,對於Server1、Server2而言,都統計出集羣中已經有兩臺機器接受了(2, 0)的投票信息,此時便認爲已經選出了Leader。
(5) 改變服務器狀態。一旦肯定了Leader,每一個服務器就會更新本身的狀態,若是是Follower,那麼就變動爲FOLLOWING,若是是Leader,就變動爲LEADING。
2. 服務器運行時期的Leader選舉
在Zookeeper運行期間,Leader與非Leader服務器各司其職,即使當有非Leader服務器宕機或新加入,此時也不會影響Leader,可是一旦Leader服務器掛了,那麼整個集羣將暫停對外服務,進入新一輪Leader選舉,其過程和啓動時期的Leader選舉過程基本一致。假設正在運行的有Server1、Server2、Server3三臺服務器,當前Leader是Server2,若某一時刻Leader掛了,此時便開始Leader選舉。選舉過程以下
(1) 變動狀態。Leader掛後,餘下的非Observer服務器都會講本身的服務器狀態變動爲LOOKING,而後開始進入Leader選舉過程。
(2) 每一個Server會發出一個投票。在運行期間,每一個服務器上的ZXID可能不一樣,此時假定Server1的ZXID爲123,Server3的ZXID爲122;在第一輪投票中,Server1和Server3都會投本身,產生投票(1, 123),(3, 122),而後各自將投票發送給集羣中全部機器。
(3) 接收來自各個服務器的投票。與啓動時過程相同。
(4) 處理投票。與啓動時過程相同,此時,Server1將會成爲Leader。
(5) 統計投票。與啓動時過程相同。
(6) 改變服務器的狀態。與啓動時過程相同。
2.2 Leader選舉算法分析
在3.4.0後的Zookeeper的版本只保留了TCP版本的FastLeaderElection選舉算法。當一臺機器進入Leader選舉時,當前集羣可能會處於如下兩種狀態
· 集羣中已經存在Leader。
· 集羣中不存在Leader。
對於集羣中已經存在Leader而言,此種狀況通常都是某臺機器啓動得較晚,在其啓動以前,集羣已經在正常工做,對這種狀況,該機器試圖去選舉Leader時,會被告知當前服務器的Leader信息,對於該機器而言,僅僅須要和Leader機器創建起鏈接,並進行狀態同步便可。而在集羣中不存在Leader狀況下則會相對複雜,其步驟以下
(1) 第一次投票。不管哪一種致使進行Leader選舉,集羣的全部機器都處於試圖選舉出一個Leader的狀態,即LOOKING狀態,LOOKING機器會向全部其餘機器發送消息,該消息稱爲投票。投票中包含了SID(服務器的惟一標識)和ZXID(事務ID),(SID, ZXID)形式來標識一次投票信息。假定Zookeeper由5臺機器組成,SID分別爲1、2、3、4、5,ZXID分別爲9、9、9、8、8,而且此時SID爲2的機器是Leader機器,某一時刻,1、2所在機器出現故障,所以集羣開始進行Leader選舉。在第一次投票時,每臺機器都會將本身做爲投票對象,因而