《OD面試》Java面試題整理


一、面試考察點

1 主語言本身
2 數據庫
3 算法
4 Spring/SpringMVC/MyBatis
5 項目經驗
1)項目涉及到的技術點深挖:
(1)考察候選人技術深度 
(2)看候選人遇到問題總結學習及解決能力
(3)講述自己的項目,並在中間穿插着問題(學會引導性的回答問題,抓住自己項目的亮點,引導面試官和自己聊比較擅長的技術,不擅長的技術可委婉表示不太熟悉,以免面試官問太多自己卻答不上來反而不好)
6 加分項
1)計算機系統原理
2)網絡通信協議(TCP/IP,HTTP)
3)數據結構與算法
4)著名開源項目的源碼
5)很棒的開源項目
6)個人博客
7 與職位相關的內容
1)資損防控
2)金融知識
 

二、面試題

優秀面試題博客整理:

(1)【常見面試問題總結目錄】

(2)Java虛擬機學習總結目錄

(3)Java 7 並發編程實戰手冊目錄

(4)[經典排序算法][集錦]

(5)【劍指Offer學習】【所有面試題匯總】

(6)【LeetCode-面試算法經典-Java實現】【所有題目目錄索引】

 

1. Java基礎

1.1 Java集合框架

參考:

關於Java集合的小抄

Java集合框架源碼分析

1.1.1 List

1.1.1.1 ArrayList

1)概述

以數組實現。節約空間,但數組有容量限制。超出限制時會增加50%容量,用System.arraycopy()復制到新的數組。因此最好能給出數組大小的預估值。默認第一次插入元素時創建大小為10的數組。

按數組下標訪問元素-get(i)、set(i,e) 的性能很高,這是數組的基本優勢。

如果按下標插入元素、刪除元素-add(i,e)、 remove(i)、remove(e),則要用System.arraycopy()來復制移動部分受影響的元素,性能就變差了。

越是前面的元素,修改時要移動的元素越多。直接在數組末尾加入元素-常用的add(e),刪除最后一個元素則無影響。

1.1.1.2 LinkedList

1)概述

以雙向鏈表實現。鏈表無容量限制,但雙向鏈表本身使用了更多空間,每插入一個元素都要構造一個額外的Node對象,也需要額外的鏈表指針操作。

按下標訪問元素-get(i)、set(i,e) 要悲劇的部分遍歷鏈表將指針移動到位 (如果i>數組大小的一半,會從末尾移起)。

插入、刪除元素時修改前后節點的指針即可,不再需要復制移動。但還是要部分遍歷鏈表的指針才能移動到下標所指的位置。

只有在鏈表兩頭的操作-add()、addFirst()、removeLast()或用iterator()上的remove()倒能省掉指針的移動。

Apache Commons 有個TreeNodeList,里面是棵二叉樹,可以快速移動指針到位。

1.1.1.3 CopyOnWriteArrayList

1)CopyOnWriteArrayList概述

並發優化的ArrayList。基於不可變對象策略,在修改時先復制出一個數組快照來修改,改好了,再讓內部指針指向新數組。

因為對快照的修改對讀操作來說不可見,所以讀讀之間不互斥,讀寫之間也不互斥,只有寫寫之間要加鎖互斥。但復制快照的成本昂貴,典型的適合讀多寫少的場景。

雖然增加了addIfAbsent(e)方法,會遍歷數組來檢查元素是否已存在,性能可想像的不會太好。

2)CopyOnWriteArrayList可以用於什么應用場景?
        答:CopyOnWriteArrayList(免鎖容器)的好處之一是當多個迭代器同時遍歷和修改這個列表時,不會拋出ConcurrentModificationException。在CopyOnWriteArrayList中,寫入將導致創建整個底層數組的副本,而源數組將保留在原地,使得復制的數組在被修改時,讀取操作可以安全地執行。

1.1.1.4 List的總結

無論哪種實現,按值返回下標contains(e), indexOf(e), remove(e) 都需遍歷所有元素進行比較,性能可想像的不會太好。

沒有按元素值排序的SortedList。除了CopyOnWriteArrayList,再沒有其他線程安全又並發優化的實現如ConcurrentLinkedList。

湊合着用Set與Queue中的等價類時,會缺少一些List特有的方法如get(i)。如果更新頻率較高,或數組較大時,還是得用Collections.synchronizedList(list),對所有操作用同一把鎖來保證線程安全。

1.1.2 Map

1.1.2.1 HashMap工作原理及實現

參考: Java HashMap工作原理及實現

Java HashMap中在resize()時候的rehash,即再哈希法的理解

1)定義

基於Map接口實現、允許null鍵/值、非同步、不保證有序(比如插入的順序)、也不保證序不隨時間變化。

2)Java的HashMap工作原理

哈希表是由數組+鏈表組成的,我接下來解釋的是最常用的一種方法—— 拉鏈法,我們可以理解為“鏈表的數組”。

HashMap有一個叫做Entry的內部類,它用來存儲key-value對。
上面的Entry對象是存儲在一個叫做table的Entry數組中。table的索引在邏輯上叫做“桶”(bucket),它存儲了鏈表的第一個元素。
key的hashcode()方法用來找到Entry對象所在的桶。如果兩個key有相同的hash值,他們會被放在table數組的同一個桶里面。
key的equals()方法用來確保key的唯一性。
value對象的equals()和hashcode()方法根本一點用也沒有。

3)兩個重要的參數

在HashMap中有兩個很重要的參數,容量(Capacity)和負載因子(Load factor)

Capacity就是bucket的大小,Load factor就是bucket填滿程度的最大比例。如果對迭代性能要求很高的話不要把capacity設置過大,也不要把load factor設置過小。當bucket中的entries的數目大於capacity*load factor時就需要調整bucket的大小為當前的2倍。

4)put和get的實現

通過hash的方法,通過put和get存儲和獲取對象。

存儲對象時,我們將K/V傳給put方法時,它調用hashCode計算hash從而得到bucket位置,進一步存儲,HashMap會根據當前bucket的占用情況自動調整容量(超過Load Facotr則resize為原來的2倍)。

獲取對象時,我們將K傳給get,它調用hashCode計算hash從而得到bucket位置,並進一步調用equals()方法確定鍵值對。如果發生碰撞的時候,Hashmap通過鏈表將產生碰撞沖突的元素組織起來,在Java 8中,如果一個bucket中碰撞沖突的元素超過某個限制(默認是8),則使用紅黑樹來替換鏈表,從而提高速度。

通過對key的hashCode()進行hashing,並計算下標( n-1 & hash),從而獲得buckets的位置。如果產生碰撞,則利用key.equals()方法去鏈表或樹中去查找對應的節點。

5)HashMap的擴容機制(resize的實現)

當put時,如果發現目前的bucket占用程度已經超過了Load Factor所希望的比例,那么就會發生resize。在resize的過程,簡單的說就是把bucket擴充為2倍,之后重新計算index,把節點再放到新的bucket中。

當超過限制的時候會resize,然而又因為我們使用的是2次冪的擴展(指長度擴為原來2倍),所以,元素的位置要么是在原位置,要么是在原位置再移動2次冪的位置。

6)你知道hash的實現嗎?為什么要這樣實現?

在Java 1.8的實現中,是通過hashCode()的高16位異或低16位實現的:(h = k.hashCode()) ^ (h >>> 16),主要是從速度、功效、質量來考慮的,這么做可以在bucket的n比較小的時候,也能保證考慮到高低bit都參與到hash的計算中,同時不會有太大的開銷。

7)如果HashMap的大小超過了負載因子(load factor)定義的容量,怎么辦?

如果超過了負載因子(默認0.75),則會重新resize一個原來長度兩倍的HashMap,並且重新調用hash方法。

8)關於HashMap的總結

以Entry[]數組實現的哈希桶數組,用Key的哈希值取模桶數組的大小可得到數組下標。

插入元素時,如果兩條Key落在同一個桶(比如哈希值1和17取模16后都屬於第一個哈希桶),Entry用一個next屬性實現多個Entry以單向鏈表存放,后入桶的Entry將next指向桶當前的Entry。

查找哈希值為17的key時,先定位到第一個哈希桶,然后以鏈表遍歷桶里所有元素,逐個比較其key值。

當Entry數量達到桶數量的75%時(很多文章說使用的桶數量達到了75%,但看代碼不是),會成倍擴容桶數組,並重新分配所有原來的Entry,所以這里也最好有個預估值。

取模用位運算(hash & (arrayLength-1))會比較快,所以數組的大小永遠是2的N次方, 你隨便給一個初始值比如17會轉為32。默認第一次放入元素時的初始值是16。

iterator()時順着哈希桶數組來遍歷,看起來是個亂序。

在JDK8里,新增默認為8的閥值,當一個桶里的Entry超過閥值,就不以單向鏈表而以紅黑樹來存放以加快Key的查找速度。

9)怎樣讓HashMap同步

Map m = Collections.synchronizeMap(hashMap);

10)HashMap怎么實現,自己實現HashMap會注意哪些問題,怎么實現一個hashMap一分鍾以后key過期

簡易緩存實現(java語言版)

 

1.1.2.2 Hashtable

參考:HashMap vs Hashtable

1)HashMap和HashTable 區別,HashTable線程安全嗎?

HashMap是Hashtable的輕量級實現(非線程安全的實現),他們都完成了Map接口,主要區別在於HashMap允許空(null)鍵值(key),由於非線程安全,效率上可能高於Hashtable。

HashMap允許將null作為一個entry的key或者value,而Hashtable不允許。

HashMap把Hashtable的contains方法去掉了,改成containsvalue和containsKey。因為contains方法容易讓人引起誤解。

Hashtable繼承自Dictionary類,而HashMap是Java1.2引進的Map interface的一個實現。

最大的不同是,Hashtable的方法是Synchronize的,而HashMap不是,在多個線程訪問Hashtable時,不需要自己為它的方法實現同步,而HashMap 就必須為之提供外同步。

Hashtable和HashMap采用的hash/rehash算法都大概一樣,所以性能不會有很大的差別。

1.1.2.3 LinkedHashMap

擴展HashMap,每個Entry增加雙向鏈表,號稱是最占內存的數據結構。

支持iterator()時按Entry的插入順序來排序(如果設置accessOrder屬性為true,則所有讀寫訪問都排序)。

插入時,Entry把自己加到Header Entry的前面去。如果所有讀寫訪問都要排序,還要把前后Entry的before/after拼接起來以在鏈表中刪除掉自己,所以此時讀操作也是線程不安全的了。

1.1.2.4 TreeMap

以紅黑樹實現,紅黑樹又叫自平衡二叉樹:

對於任一節點而言,其到葉節點的每一條路徑都包含相同數目的黑結點。

上面的規定,使得樹的層數不會差的太遠,使得所有操作的復雜度不超過 O(lgn),但也使得插入,修改時要復雜的左旋右旋來保持樹的平衡。

支持iterator()時按Key值排序,可按實現了Comparable接口的Key的升序排序,或由傳入的Comparator控制。可想象的,在樹上插入/刪除元素的代價一定比HashMap的大。

支持SortedMap接口,如firstKey(),lastKey()取得最大最小的key,或sub(fromKey, toKey), tailMap(fromKey)剪取Map的某一段。

TreeMap 是一個有序的key-value集合,它是通過紅黑樹實現的。
TreeMap 繼承於AbstractMap,所以它是一個Map,即一個key-value集合。
TreeMap 實現了NavigableMap接口,意味着它支持一系列的導航方法。比如返回有序的key集合。
TreeMap 實現了Cloneable接口,意味着它能被克隆。
TreeMap 實現了java.io.Serializable接口,意味着它支持序列化。
TreeMap基於紅黑樹(Red-Black tree)實現。該映射根據其鍵的自然順序進行排序,或者根據創建映射時提供的 Comparator 進行排序,具體取決於使用的構造方法。
TreeMap的基本操作 containsKey、get、put 和 remove 的時間復雜度是 log(n) 。
另外,TreeMap是非同步的。 它的iterator 方法返回的迭代器是fail-fast的。

1.1.2.5 EnumMap

EnumMap的原理是,在構造函數里要傳入枚舉類,那它就構建一個與枚舉的所有值等大的數組,按Enum. ordinal()下標來訪問數組。性能與內存占用俱佳。

美中不足的是,因為要實現Map接口,而 V get(Object key)中key是Object而不是泛型K,所以安全起見,EnumMap每次訪問都要先對Key進行類型判斷,在JMC里錄得不低的采樣命中頻率。

1.1.2.6 ConcurrentHashMap

參考: 

1)聊聊並發(四)——深入分析ConcurrentHashMap

2)探索 ConcurrentHashMap 高並發性的實現機制

3)ConcurrentHashMap之實現細節

4)並發優化的HashMap。

在JDK5里的經典設計,默認16把寫鎖(可以設置更多),有效分散了阻塞的概率。數據結構為Segment[],每個Segment一把鎖。Segment里面才是哈希桶數組。Key先算出它在哪個Segment里,再去算它在哪個哈希桶里。

也沒有讀鎖,因為put/remove動作是個原子動作(比如put的整個過程是一個對數組元素/Entry 指針的賦值操作),讀操作不會看到一個更新動作的中間狀態。

但在JDK8里,Segment[]的設計被拋棄了,改為精心設計的,只在需要鎖的時候加鎖。

支持ConcurrentMap接口,如putIfAbsent(key,value)與相反的replace(key,value)與以及實現CAS的replace(key, oldValue, newValue)。

5)分段加鎖 
從ConcurrentHashMap代碼中可以看出,它引入了一個“分段鎖”的概念,具體可以理解為把一個大的Map拆分成N個小的HashTable,根據key.hashCode()來決定把key放到哪個HashTable中。
在ConcurrentHashMap中,就是把Map分成了N個Segment,put和get的時候,都是現根據key.hashCode()算出放到哪個Segment中。

6)synchronizedMap和ConcurrentHashMap有什么區別?       

答:java5中新增了ConcurrentMap接口和它的一個實現類ConcurrentHashMap。ConcurrentHashMap提供了和Hashtable以及SynchronizedMap中所不同的鎖機制。比起synchronizedMap來,它提供了好得多的並發性。多個讀操作幾乎總可以並發地執行,同時進行的讀和寫操作通常也能並發地執行,而同時進行的寫操作仍然可以不時地並發進行(相關的類也提供了類似的多個讀線程的並發性,但是,只允許有一個活動的寫線程)。Hashtable中采用的鎖機制是一次鎖住整個hash表,從而同一時刻只能由一個線程對其進行操作;而ConcurrentHashMap中則是一次鎖住一個桶。ConcurrentHashMap默認將hash表分為16個桶,諸如get,put,remove等常用操作只鎖當前需要用到的桶。這樣,原來只能一個線程進入,現在卻能同時有16個寫線程執行,並發性能的提升是顯而易見的。前面說到的16個線程指的是寫線程,而讀操作大部分時候都不需要用到鎖。只有在size等操作時才需要鎖住整個hash表。

  在迭代方面,ConcurrentHashMap使用了一種不同的迭代方式。在這種迭代方式中,當iterator被創建后集合再發生改變就不再是拋出ConcurrentModificationException,取而代之的是在改變時new新的數據從而不影響原有的數據 ,iterator完成后再將頭指針替換為新的數據 ,這樣iterator線程可以使用原來老的數據,而寫線程也可以並發的完成改變。

1.1.2.7 ConcurrentSkipListMap

JDK6新增的並發優化的SortedMap,以SkipList結構實現。Concurrent包選用它是因為它支持基於CAS的無鎖算法,而紅黑樹則沒有好的無鎖算法。

原理上,可以想象為多個鏈表組成的N層樓,其中的元素從稀疏到密集,每個元素有往右與往下的指針。從第一層樓開始遍歷,如果右端的值比期望的大,那就往下走一層,繼續往前走。

典型的空間換時間。每次插入,都要決定在哪幾層插入,同時,要決定要不要多蓋一層樓。

它的size()同樣不能隨便調,會遍歷來統計。

1.1.3 Set

所有Set幾乎都是內部用一個Map來實現, 因為Map里的KeySet就是一個Set,而value是假值,全部使用同一個Object即可。

Set的特征也繼承了那些內部的Map實現的特征。

HashSet:內部是HashMap。

LinkedHashSet:內部是LinkedHashMap。

TreeSet:內部是TreeMap的SortedSet。

ConcurrentSkipListSet:內部是ConcurrentSkipListMap的並發優化的SortedSet。

CopyOnWriteArraySet:內部是CopyOnWriteArrayList的並發優化的Set,利用其addIfAbsent()方法實現元素去重,如前所述該方法的性能很一般。

好像少了個ConcurrentHashSet,本來也該有一個內部用ConcurrentHashMap的簡單實現,但JDK偏偏沒提供。Jetty就自己簡單封了一個,Guava則直接用java.util.Collections.newSetFromMap(new ConcurrentHashMap()) 實現。

1.1.4 Queue

Queue是在兩端出入的List,所以也可以用數組或鏈表來實現。

普通隊列

1.1.4.1 LinkedList

是的,以雙向鏈表實現的LinkedList既是List,也是Queue。

1.1.4.2 ArrayDeque

以循環數組實現的雙向Queue。大小是2的倍數,默認是16。

為了支持FIFO,即從數組尾壓入元素(快),從數組頭取出元素(超慢),就不能再使用普通ArrayList的實現了,改為使用循環數組。

有隊頭隊尾兩個下標:彈出元素時,隊頭下標遞增;加入元素時,隊尾下標遞增。如果加入元素時已到數組空間的末尾,則將元素賦值到數組[0],同時隊尾下標指向0,再插入下一個元素則賦值到數組[1],隊尾下標指向1。如果隊尾的下標追上隊頭,說明數組所有空間已用完,進行雙倍的數組擴容。

1.1.4.3 PriorityQueue

用平衡二叉最小堆實現的優先級隊列,不再是FIFO,而是按元素實現的Comparable接口或傳入Comparator的比較結果來出隊,數值越小,優先級越高,越先出隊。但是注意其iterator()的返回不會排序。

平衡最小二叉堆,用一個簡單的數組即可表達,可以快速尋址,沒有指針什么的。最小的在queue[0] ,比如queue[4]的兩個孩子,會在queue[2*4+1] 和 queue[2*(4+1)],即queue[9]和queue[10]。

入隊時,插入queue[size],然后二叉地往上比較調整堆。

出隊時,彈出queue[0],然后把queque[size]拿出來二叉地往下比較調整堆。

初始大小為11,空間不夠時自動50%擴容。

線程安全的隊列

1.1.4.4 ConcurrentLinkedQueue/Deque

無界的並發優化的Queue,基於鏈表,實現了依賴於CAS的無鎖算法。

ConcurrentLinkedQueue的結構是單向鏈表和head/tail兩個指針,因為入隊時需要修改隊尾元素的next指針,以及修改tail指向新入隊的元素兩個CAS動作無法原子,所以需要的特殊的算法。

線程安全的阻塞隊列

BlockingQueue,一來如果隊列已空不用重復的查看是否有新數據而會阻塞在那里,二來隊列的長度受限,用以保證生產者與消費者的速度不會相差太遠。當入隊時隊列已滿,或出隊時隊列已空,不同函數的效果見下表:

  立刻報異常 立刻返回布爾 阻塞等待 可設定等待時間
入隊 add(e) offer(e) put(e) offer(e, timeout, unit)
出隊 remove() poll() take() poll(timeout, unit)
查看 element() peek()

 

1.1.4.5 ArrayBlockingQueue

定長的並發優化的BlockingQueue,也是基於循環數組實現。有一把公共的鎖與notFull、notEmpty兩個Condition管理隊列滿或空時的阻塞狀態。

1.1.4.6 LinkedBlockingQueue/Deque

可選定長的並發優化的BlockingQueue,基於鏈表實現,所以可以把長度設為Integer.MAX_VALUE成為無界無等待的。

利用鏈表的特征,分離了takeLock與putLock兩把鎖,繼續用notEmpty、notFull管理隊列滿或空時的阻塞狀態。

1.1.4.7 PriorityBlockingQueue

無界的PriorityQueue,也是基於數組存儲的二叉堆(見前)。一把公共的鎖實現線程安全。因為無界,空間不夠時會自動擴容,所以入列時不會鎖,出列為空時才會鎖。

1.1.4.8 DelayQueue

內部包含一個PriorityQueue,同樣是無界的,同樣是出列時才會鎖。一把公共的鎖實現線程安全。元素需實現Delayed接口,每次調用時需返回當前離觸發時間還有多久,小於0表示該觸發了。

pull()時會用peek()查看隊頭的元素,檢查是否到達觸發時間。ScheduledThreadPoolExecutor用了類似的結構。

同步隊列

SynchronousQueue同步隊列本身無容量,放入元素時,比如等待元素被另一條線程的消費者取走再返回。JDK線程池里用它。

JDK7還有個LinkedTransferQueue,在普通線程安全的BlockingQueue的基礎上,增加一個transfer(e) 函數,效果與SynchronousQueue一樣。

 

1.2 多線程

參考:

Java 並發編程:核心理論

Java中並發問題整理

Java線程池--原理及源碼分析

1.2.1 線程安全、並發、線程安全的容器

1.2.1.1 線程安全

當多個線程訪問一個對象時,如果不用考慮這些線程在運行時的環境下的調度和交替執行,

也不需要進行額外的同步,或者調用其他協作,這個情況下,線程就是安全的。

1.2.1.2 並發

並發和並行從宏觀上來講都是同時處理多路請求的概念。但並發和並行又有區別,並行是指兩個或者多個事件在同一時刻發生;而並發是指兩個或多個事件在同一時間間隔內發生。

在操作系統中,並發是指一個時間段中有幾個程序都處於已啟動運行到運行完畢之間,且這幾個程序都是在同一個處理機上運行,但任一個時刻點上只有一個程序在處理機上運行。

①程序與計算不再一一對應,一個程序副本可以有多個計算
②並發程序之間有相互制約關系,直接制約體現為一個程序需要另一個程序的計算結果,間接制約體現為多個程序競爭某一資源,如處理機、緩沖區等。
③並發程序在執行中是走走停停,斷續推進的。

1.2.1.3 線程安全的容器

參考:Java並發:線程安全的容器:同步和並發

多線程環境正確發布共享數據的方法之一就是線程安全容器。

線程安全的容器是由鎖保護的域,將數據放入線程安全的容器中,可以保障其被安全地發布給所有從這個容器訪問它的線程。

1)同步容器類

JDK1.0開始有兩個很老的同步容器類:Vector和HashTable。

JDK1.2之后Collections工具類中添加了一些工廠方法返回類似的同步封裝器類:

public static <T> Collection<T>synchronizedCollection(Collection<T> c)

static<T> List<T> synchronizedList(List<T> list) //包裝ArrayList、LinkedList

static<T> Set<T> synchronizedSet(Set<T> s)  //包裝HashSet

static<K,V> Map<K,V> synchronizedMap(Map<K,V> m) //包裝HashMap

static <T> SortedSet<T> synchronizedSortedSet(SortedSet<T>s) //包裝TreeSet

static <K,V> SortedMap<K,V>synchronizedSortedMap(SortedMap<K,V> m) //包裝TreeMap

實現方式:

將它們的狀態封裝起來,並對每一個公有方法進行同步。

其中Vector就是Object[]+synchronized方法,Hashtable是HashtableEntry[]+synchronized方法。而synchronizedXXX()方法返回的同步封裝器類更是簡單地將傳進來的Collection的所有方法封裝為synchronized方法而已。 

缺點:

1、  通過同步方法將訪問操作串行化,導致並發環境下效率低下

2、  復合操作(迭代、條件運算如沒有則添加等)非線程安全,需要客戶端代碼來實現加鎖。

2)並發容器類

並發容器出現的最大的需求就是提升同步容器類的性能!

可以對比(非並發容器類)看看,將單線程版本和並發版本做一個比較。

1、HashMap和HashSet的並發版本

1.1 ConcurrentHashMap<K, V>(HashMap的並發版本)

版本:JDK5

目標:代替Hashtable、synchronizedMap,支持復合操作

原理:采用一種更加細粒度的加鎖機制“分段鎖”,任意數量讀取線程可以並發讀取,任意數量的讀取線程和一個寫線程可以並發訪問,一定數量的寫入線程可以並發訪問。並發環境下ConcurrentHashMap帶來了更高的吞吐量,而在單線程環境下只損失了很小的性能。

1.2 CopyOnWriteArraySet<E>(HashSet的並發版本)

版本:JDK5

目標:代替synchronizedSet

原理:CopyOnWriteArraySet基於CopyOnWriteArrayList實現,其唯一的不同是在add時調用的是CopyOnWriteArrayList的addIfAbsent方法,其遍歷當前Object數組,如Object數組中已有了當前元素,則直接返回,如果沒有則放入Object數組的尾部,並返回。

2、TreeMap和TreeSet的並發版本

ConcurrentSkipListMap<K, V>(TreeMap的並發版本)

版本:JDK6

目標:代替synchronizedSortedMap(TreeMap)

原理:Skip list(跳表)是一種可以代替平衡樹的數據結構,默認是按照Key值升序的。Skip list讓已排序的數據分布在多層鏈表中,以0-1隨機數決定一個數據的向上攀升與否,通過"空間來換取時間"的一個算法。ConcurrentSkipListMap提供了一種線程安全的並發訪問的排序映射表。內部是SkipList(跳表)結構實現,在理論上能夠在O(log(n))時間內完成查找、插入、刪除操作。

ConcurrentSkipListSet<E>(TreeSet的並發版本)

版本:JDK6

目標:代替synchronizedSortedSet

原理:內部基於ConcurrentSkipListMap實現!

3、ArrayList和LinkedList的並發版本

CopyOnWriteArrayList<E>(ArrayList的並發版本)

目標:代替Vector、synchronizedList

原理:CopyOnWriteArrayList的核心思想是利用高並發往往是讀多寫少的特性,對讀操作不加鎖,對寫操作,先復制一份新的集合,在新的集合上面修改,然后將新集合賦值給舊的引用,並通過volatile 保證其可見性,當然寫操作的鎖是必不可少的了。

ConcurrentLinkedQueue<E>(LinkedList的並發版本)

目標:代替Vector、synchronizedList

特點:基於鏈表實現的FIFO隊列,特別注意單線程環境中LinkedList除了可以用作鏈表,也可用作隊列,並發版本也一樣

阻塞隊列:BlockingQueue

版本:JDK1.5

特點:拓展了Queue,增加了可阻塞的插入和獲取等操作

實現類

LinkedBlockingQueue:基於鏈表實現的可阻塞的FIFO隊列

ArrayBlockingQueue:基於數組實現的可阻塞的FIFO隊列

PriorityBlockingQueue:按優先級排序的隊列

原理:通過ReentrantLock實現線程安全,通過Condition實現阻塞和喚醒。

 

 

1.2.2 核心理論

1)共享性

數據共享性是線程安全的主要原因之一。如果所有的數據只是在線程內有效,那就不存在線程安全性問題,這也是我們在編程的時候經常不需要考慮線程安全的主要原因之一。但是,在多線程編程中,數據共享是不可避免的。最典型的場景是數據庫中的數據,為了保證數據的一致性,我們通常需要共享同一個數據庫中數據,即使是在主從的情況下,訪問的也同一份數據,主從只是為了訪問的效率和數據安全,而對同一份數據做的副本。

2)互斥性

資源互斥是指同時只允許一個訪問者對其進行訪問,具有唯一性和排它性。我們通常允許多個線程同時對數據進行讀操作,但同一時間內只允許一個線程對數據進行寫操作。所以我們通常將鎖分為共享鎖和排它鎖,也叫做讀鎖和寫鎖。如果資源不具有互斥性,即使是共享資源,我們也不需要擔心線程安全。例如,對於不可變的數據共享,所有線程都只能對其進行讀操作,所以不用考慮線程安全問題。但是對共享數據的寫操作,一般就需要保證互斥性,上述例子中就是因為沒有保證互斥性才導致數據的修改產生問題。Java 中提供多種機制來保證互斥性,最簡單的方式是使用Synchronized。

3)原子性

原子性就是指對數據的操作是一個獨立的、不可分割的整體。換句話說,就是一次操作,是一個連續不可中斷的過程,數據不會執行的一半的時候被其他線程所修改。保證原子性的最簡單方式是操作系統指令,就是說如果一次操作對應一條操作系統指令,這樣肯定可以能保證原子性。但是很多操作不能通過一條指令就完成。例如,對long類型的運算,很多系統就需要分成多條指令分別對高位和低位進行操作才能完成。還比如,我們經常使用的整數 i++ 的操作,其實需要分成三個步驟:(1)讀取整數 i 的值;(2)對 i 進行加一操作;(3)將結果寫回內存。

這個過程在多線程下就可能出現如下現象:

這也是代碼段一執行的結果為什么不正確的原因。對於這種組合操作,要保證原子性,最常見的方式是加鎖,如Java中的Synchronized或Lock都可以實現,代碼段二就是通過Synchronized實現的。除了鎖以外,還有一種方式就是CAS(Compare And Swap),即修改數據之前先比較與之前讀取到的值是否一致,如果一致,則進行修改,如果不一致則重新執行,這也是樂觀鎖的實現原理。不過CAS在某些場景下不一定有效,比如另一線程先修改了某個值,然后再改回原來值,這種情況下,CAS是無法判斷的(ABA問題)。

4)可見性

要理解可見性,需要先對JVM的內存模型有一定的了解,JVM的內存模型與操作系統類似,如圖所示:

  

從這個圖中我們可以看出,每個線程都有一個自己的工作內存(相當於CPU高級緩沖區,這么做的目的還是在於進一步縮小存儲系統與CPU之間速度的差異,提高性能),對於共享變量,線程每次讀取的是工作內存中共享變量的副本,寫入的時候也直接修改工作內存中副本的值,然后在某個時間點上再將工作內存與主內存中的值進行同步。這樣導致的問題是,如果線程1對某個變量進行了修改,線程2卻有可能看不到線程1對共享變量所做的修改。

5)有序性

為了提高性能,編譯器和處理器可能會對指令做重排序。重排序可以分為三種:

(1)編譯器優化的重排序。編譯器在不改變單線程程序語義的前提下,可以重新安排語句的執行順序。

(2)指令級並行的重排序。現代處理器采用了指令級並行技術(Instruction-Level Parallelism, ILP)來將多條指令重疊執行。如果不存在數據依賴性,處理器可以改變語句對應機器指令的執行順序。
(3)內存系統的重排序。由於處理器使用緩存和讀/寫緩沖區,這使得加載和存儲操作看上去可能是在亂序執行。

1.2.3 java中線程安全的5個級別

(1)不可改變 final

從內部類中訪問本地變量,需要被聲明為最終類型。

(2)絕對安全

絕對安全其實很難描述,比如Vector是安全的。但是在多線程的情況下,它也是不安全的。

(3)相對安全

相對安全其實就是我們一般意義上的線程安全。

它需要保證對這個對象的單獨操作是安全的。但是對於特定的順序,需要一些方法保證線程安全。

 (4)線程兼容

這就是我們常見的情況,需要使用synchronized等手段來保證線程安全。

(5)線程對立

比較極端的情況,就是無論怎么加鎖,代碼無法並發運行。一種情況就是死鎖。

1.2.4 如何實現線程安全

(1)互斥同步

保持共享數據在同一時刻只被一個線程使用。

互斥是手段,同步是目的。

在java中最常見的就是synchronized方法。

synchronized標記的代碼,會生成monitorenter & monitorexit  2段代碼。

這是java編譯器自動生成的,不會有遺漏。使用其他鎖,lock & unlock成對出現,但是

開發者有時候會容易疏忽這個操作,尤其在catch代碼里面忘記調用unlock,將是一個隱患。

java.util.concurrent 下面有不少同步的方法。ReentrantLock也是一個可以的方法,在1.5以前,性能

遠由於synchronized。但是在1.6, java還是把synchronized做了很大的提升。原因就是synchronized使用的

代碼已經遠遠大於ReentrantLock,並且引入ReentrantLock,可能會令需要開發者混淆。所以ReentrantLock可以認為是

一道開胃小菜而已。

(2)非阻塞同步

互斥同步是一種阻塞同步,但是有些情況下,我們不需要互斥,只要能夠同步就可以。

java.util.concurrent.atomic.AtomicInteger

(3)無同步

同步只是保證共享數據的手段,如果2個線程沒有共享數據,也就不需要同步。

1.2.5 synchronized、lock、volatile

1.2.5.1 synchronized

1)synchronized的主要作用

(1)確保線程互斥的訪問同步代碼

(2)保證共享變量的修改能夠及時可見

(3)有效解決重排序問題。

2)語法上的主要用法

(1)修飾普通方法

(2)修飾靜態方法

(3)修飾代碼塊

3)實現原理

(1)synchronized的語義底層是通過一個monitor的對象來完成,其實wait/notify等方法也依賴於monitor對象,這就是為什么只有在同步的塊或者方法中才能調用wait/notify等方法,否則會拋出java.lang.IllegalMonitorStateException的異常的原因。

monitorenter :

每個對象有一個監視器鎖(monitor)。當monitor被占用時就會處於鎖定狀態,線程執行monitorenter指令時嘗試獲取monitor的所有權,過程如下:

  • 如果monitor的進入數為0,則該線程進入monitor,然后將進入數設置為1,該線程即為monitor的所有者。
  • 如果線程已經占有該monitor,只是重新進入,則進入monitor的進入數加1.
  • 如果其他線程已經占用了monitor,則該線程進入阻塞狀態,直到monitor的進入數為0,再重新嘗試獲取monitor的所有權。

monitorexit:

執行monitorexit的線程必須是objectref所對應的monitor的所有者。

指令執行時,monitor的進入數減1,如果減1后進入數為0,那線程退出monitor,不再是這個monitor的所有者。其他被這個monitor阻塞的線程可以嘗試去獲取這個 monitor 的所有權。 

(2)方法的同步並沒有通過指令monitorenter和monitorexit來完成(理論上其實也可以通過這兩條指令來實現),不過相對於普通方法,其常量池中多了ACC_SYNCHRONIZED標示符。JVM就是根據該標示符來實現方法的同步的:當方法調用時,調用指令將會檢查方法的 ACC_SYNCHRONIZED 訪問標志是否被設置,如果設置了,執行線程將先獲取monitor,獲取成功之后才能執行方法體,方法執行完后再釋放monitor。在方法執行期間,其他任何線程都無法再獲得同一個monitor對象。 其實本質上沒有區別,只是方法的同步是一種隱式的方式來實現,無需通過字節碼來完成。

4)當一個線程進入某個對象的一個synchronized的實例方法后,其它線程是否可進入此對象的其它方法?       

 答:A、一個線程在訪問一個對象的同步方法時,另一個線程可以同時訪問這個對象的非同步方法
      B、 一個線程在訪問一個對象的同步方法時,另一個線程不能同時訪問這個同步方法。

1.2.5.2 volatile

volatile關鍵字就是Java中提供的另一種解決可見性和有序性問題的方案。對於原子性,需要強調一點,也是大家容易誤解的一點:對volatile變量的單次讀/寫操作可以保證原子性的,如long和double類型變量,但是並不能保證i++這種操作的原子性,因為本質上i++是讀、寫兩次操作。

volatile的使用

1)防止重排序

先要了解對象的構造過程,實例化一個對象其實可以分為三個步驟:

(1)分配內存空間。

(2)初始化對象。

(3)將內存空間的地址賦值給對應的引用。

但是由於操作系統可以對指令進行重排序,所以上面的過程也可能會變成如下過程:

(1)分配內存空間。

(2)將內存空間的地址賦值給對應的引用。

(3)初始化對象

  如果是這個流程,多線程環境下就可能將一個未初始化的對象引用暴露出來,從而導致不可預料的結果。因此,為了防止這個過程的重排序,我們需要將變量設置為volatile類型的變量。

有序性實現原理:

happen-before規則:

  • 同一個線程中的,前面的操作 happen-before 后續的操作。(即單線程內按代碼順序執行。但是,在不影響在單線程環境執行結果的前提下,編譯器和處理器可以進行重排序,這是合法的。換句話說,這一是規則無法保證編譯重排和指令重排)。
  • 監視器上的解鎖操作 happen-before 其后續的加鎖操作。(Synchronized 規則)
  • 對volatile變量的寫操作 happen-before 后續的讀操作。(volatile 規則)
  • 線程的start() 方法 happen-before 該線程所有的后續操作。(線程啟動規則)
  • 線程所有的操作 happen-before 其他線程在該線程上調用 join 返回成功后的操作。
  • 如果 a happen-before b,b happen-before c,則a happen-before c(傳遞性)。

2)實現可見性

可見性問題主要指一個線程修改了共享變量值,而另一個線程卻看不到。引起可見性問題的主要原因是每個線程擁有自己的一個高速緩存區——線程工作內存。volatile關鍵字能有效的解決這個問題。

可見性實現原理:

在前文中已經提及過,線程本身並不直接與主內存進行數據的交互,而是通過線程的工作內存來完成相應的操作。這也是導致線程間數據不可見的本質原因。因此要實現volatile變量的可見性,直接從這方面入手即可。對volatile變量的寫操作與普通變量的主要區別有兩點:

(1)修改volatile變量時會強制將修改后的值刷新的主內存中。

(2)修改volatile變量后會導致其他線程工作內存中對應的變量值失效。因此,再讀取該變量值的時候就需要重新從讀取主內存中的值。

通過這兩個操作,就可以解決volatile變量的可見性問題。

3)保證原子性

volatile只能保證對單次讀/寫的原子性。

因為long和double兩種數據類型的操作可分為高32位和低32位兩部分,因此普通的long或double類型讀/寫可能不是原子的。因此,鼓勵大家將共享的long和double變量設置為volatile類型,這樣能保證任何情況下對long和double的單次讀/寫操作都具有原子性。

i++其實是一個復合操作,包括三步驟:

  (1)讀取i的值。

  (2)對i加1。

  (3)將i的值寫回內存。

volatile是無法保證這三個操作是具有原子性的,我們可以通過AtomicInteger或者Synchronized來保證+1操作的原子性。

1.2.5.3 lock

1)什么是可重入鎖(ReentrantLock)? 

      答:  java.util.concurrent.lock 中的 Lock 框架是鎖定的一個抽象,它允許把鎖定的實現作為 Java 類,而不是作為語言的特性來實現。這就為 Lock 的多種實現留下了空間,各種實現可能有不同的調度算法、性能特性或者鎖定語義。 ReentrantLock 類實現了 Lock ,它擁有與 synchronized 相同的並發性和內存語義,但是添加了類似鎖投票、定時鎖等候和可中斷鎖等候的一些特性。此外,它還提供了在激烈爭用情況下更佳的性能。(換句話說,當許多線程都想訪問共享資源時,JVM 可以花更少的時候來調度線程,把更多時間用在執行線程上。)
     reentrant 鎖意味着什么呢?簡單來說,它有一個與鎖相關的獲取計數器,如果擁有鎖的某個線程再次得到鎖,那么獲取計數器就加1,然后鎖需要被釋放兩次才能獲得真正釋放。這模仿了 synchronized 的語義;如果線程進入由線程已經擁有的監控器保護的 synchronized 塊,就允許線程繼續進行,當線程退出第二個(或者后續)synchronized 塊的時候,不釋放鎖,只有線程退出它進入的監控器保護的第一個 synchronized 塊時,才釋放鎖。

2)synchronized和java.util.concurrent.locks.Lock的異同?

         答:Lock 和 synchronized 有一點明顯的區別 —— lock 必須在 finally 塊中釋放。否則,如果受保護的代碼將拋出異常,鎖就有可能永遠得不到釋放!這一點區別看起來可能沒什么,但是實際上,它極為重要。忘記在 finally 塊中釋放鎖,可能會在程序中留下一個定時炸彈,當有一天炸彈爆炸時,您要花費很大力氣才有找到源頭在哪。而使用同步,JVM 將確保鎖會獲得自動釋放。

1.2.5.4 內存屏障

為了實現volatile可見性和happen-befor的語義。JVM底層是通過一個叫做“內存屏障”的東西來完成。內存屏障,也叫做內存柵欄,是一組處理器指令,用於實現對內存操作的順序限制。下面是完成上述規則所要求的內存屏障:

Required barriers 2nd operation
1st operation Normal Load Normal Store Volatile Load Volatile Store
Normal Load       LoadStore
Normal Store       StoreStore
Volatile Load LoadLoad LoadStore LoadLoad LoadStore
Volatile Store     StoreLoad StoreStore

(1)LoadLoad 屏障
執行順序:Load1—>Loadload—>Load2
確保Load2及后續Load指令加載數據之前能訪問到Load1加載的數據。

(2)StoreStore 屏障
執行順序:Store1—>StoreStore—>Store2
確保Store2以及后續Store指令執行前,Store1操作的數據對其它處理器可見。

(3)LoadStore 屏障
執行順序: Load1—>LoadStore—>Store2
確保Store2和后續Store指令執行前,可以訪問到Load1加載的數據。

(4)StoreLoad 屏障
執行順序: Store1—> StoreLoad—>Load2
確保Load2和后續的Load指令讀取之前,Store1的數據對其他處理器是可見的。

1.2.6 java鎖類型,各自的特性

鎖的狀態總共有四種:無鎖狀態、偏向鎖、輕量級鎖和重量級鎖。隨着鎖的競爭,鎖可以從偏向鎖升級到輕量級鎖,再升級的重量級鎖(但是鎖的升級是單向的,也就是說只能從低到高升級,不會出現鎖的降級)。JDK 1.6中默認是開啟偏向鎖和輕量級鎖的,我們也可以通過-XX:-UseBiasedLocking來禁用偏向鎖。鎖的狀態保存在對象的頭文件中,以32位的JDK為例:

鎖狀態

25 bit

4bit

1bit

2bit

23bit

2bit

是否是偏向鎖

鎖標志位

輕量級鎖

指向棧中鎖記錄的指針

00

重量級鎖

指向互斥量(重量級鎖)的指針

10

GC標記

11

偏向鎖

線程ID

Epoch

對象分代年齡

1

01

無鎖

對象的hashCode

對象分代年齡

0

01

  “輕量級”是相對於使用操作系統互斥量來實現的傳統鎖而言的。但是,首先需要強調一點的是,輕量級鎖並不是用來代替重量級鎖的,它的本意是在沒有多線程競爭的前提下,減少傳統的重量級鎖使用產生的性能消耗。在解釋輕量級鎖的執行過程之前,先明白一點,輕量級鎖所適應的場景是線程交替執行同步塊的情況,如果存在同一時間訪問同一鎖的情況,就會導致輕量級鎖膨脹為重量級鎖。

1.2.7 鎖優化

1)自旋鎖

自旋鎖有時候會白白的耗用處理器的資源,但是沒有任何實際效果。

適應性自旋(Adaptive Spinning):從輕量級鎖獲取的流程中我們知道當線程在獲取輕量級鎖的過程中執行CAS操作失敗時,是要通過自旋來獲取重量級鎖的。問題在於,自旋是需要消耗CPU的,如果一直獲取不到鎖的話,那該線程就一直處在自旋狀態,白白浪費CPU資源。解決這個問題最簡單的辦法就是指定自旋的次數,例如讓其循環10次,如果還沒獲取到鎖就進入阻塞狀態。但是JDK采用了更聰明的方式——適應性自旋,簡單來說就是線程如果自旋成功了,則下次自旋的次數會更多,如果自旋失敗了,則自旋的次數就會減少。

2)鎖消除(Lock Elimination)

鎖消除即刪除不必要的加鎖操作。根據代碼逃逸技術,如果判斷到一段代碼中,堆上的數據不會逃逸出當前線程,那么可以認為這段代碼是線程安全的,不必要加鎖。

如果代碼不可能存在共享數據需要同步,編譯器就會把鎖拿掉。

3)鎖粗化(Lock Coarsening)

鎖粗化的概念應該比較好理解,就是將多次連接在一起的加鎖、解鎖操作合並為一次,將多個連續的鎖擴展成一個范圍更大的鎖。

原則上鎖的互斥模塊盡可能的小,但是如果對於同一對象,反復的lock & unlock 尤其是循環體中。會帶來很大的性能損失。

優點

缺點

適用場景

偏向鎖

加鎖和解鎖不需要額外的消耗,和執行非同步方法比僅存在納秒級的差距。

如果線程間存在鎖競爭,會帶來額外的鎖撤銷的消耗。

適用於只有一個線程訪問同步塊場景。

輕量級鎖

競爭的線程不會阻塞,提高了程序的響應速度。

如果始終得不到鎖競爭的線程使用自旋會消耗CPU。

追求響應時間。

同步塊執行速度非常快。

重量級鎖

線程競爭不使用自旋,不會消耗CPU。

線程阻塞,響應時間緩慢。

追求吞吐量。

同步塊執行速度較長。

1.2.8 線程的狀態

Java中線程中狀態可分為五種:New(新建狀態),Runnable(就緒狀態),Running(運行狀態),Blocked(阻塞狀態),Dead(死亡狀態)。

  New:新建狀態,當線程創建完成時為新建狀態,即new Thread(...),還沒有調用start方法時,線程處於新建狀態。

  Runnable:就緒狀態,當調用線程的的start方法后,線程進入就緒狀態,等待CPU資源。處於就緒狀態的線程由Java運行時系統的線程調度程序(thread scheduler)來調度。

  Running:運行狀態,就緒狀態的線程獲取到CPU執行權以后進入運行狀態,開始執行run方法。

  Blocked:阻塞狀態,線程沒有執行完,由於某種原因(如,I/O操作等)讓出CPU執行權,自身進入阻塞狀態。

  Dead:死亡狀態,線程執行完成或者執行過程中出現異常,線程就會進入死亡狀態。

  這五種狀態之間的轉換關系如下圖所示:

 

1.2.9 線程間的協作

參考:

1)wait方法

(1)wait()方法的作用是將當前運行的線程掛起(即讓其進入阻塞狀態),直到notify或notifyAll方法來喚醒線程.

(2)wait(long timeout),該方法與wait()方法類似,唯一的區別就是在指定時間內,如果沒有notify或notifAll方法的喚醒,也會自動喚醒。

(3)至於wait(long timeout,long nanos),本意在於更精確的控制調度時間,不過從目前版本來看,該方法貌似沒有完整的實現該功能

wait方法的使用必須在同步的范圍內,否則就會拋出IllegalMonitorStateException異常,wait方法的作用就是阻塞當前線程等待notify/notifyAll方法的喚醒,或等待超時后自動喚醒。

2)notify/notifyAll方法

void notify() Wakes up a single thread that is waiting on this object's monitor.
void notifyAll() Wakes up all threads that are waiting on this object's monitor.

  有了對wait方法原理的理解,notify方法和notifyAll方法就很容易理解了。既然wait方式是通過對象的monitor對象來實現的,所以只要在同一對象上去調用notify/notifyAll方法,就可以喚醒對應對象monitor上等待的線程了。notify和notifyAll的區別在於前者只能喚醒monitor上的一個線程,對其他線程沒有影響,而notifyAll則喚醒所有的線程。

最后,有兩點點需要注意:

(1)調用wait方法后,線程是會釋放對monitor對象的所有權的。

(2)一個通過wait方法阻塞的線程,必須同時滿足以下兩個條件才能被真正執行:

  •     線程需要被喚醒(超時喚醒或調用notify/notifyll)。
  •     線程喚醒后需要競爭到鎖(monitor)。

notify()是喚醒一個線程。只喚醒一個。

notifyAll()是喚醒全部線程,但是注意是一個個喚醒,喚醒一個再喚下一個。

隨機喚醒和喚醒全部。

3)sleep方法

這組方法跟上面方法的最明顯區別是:這幾個方法都位於Thread類中,而上面三個方法都位於Object類中。

sleep方法的作用是讓當前線程暫停指定的時間(毫秒),sleep方法是最簡單的方法,在上述的例子中也用到過,比較容易理解。唯一需要注意的是其與wait方法的區別。最簡單的區別是,wait方法依賴於同步,而sleep方法可以直接調用。而更深層次的區別在於sleep方法只是暫時讓出CPU的執行權,並不釋放鎖。而wait方法則需要釋放鎖。

通過sleep方法實現的暫停,程序是順序進入同步塊的,只有當上一個線程執行完成的時候,下一個線程才能進入同步方法,sleep暫停期間一直持有monitor對象鎖,其他線程是不能進入的。而wait方法則不同,當調用wait方法后,當前線程會釋放持有的monitor對象鎖,因此,其他線程還可以進入到同步方法,線程被喚醒后,需要競爭鎖,獲取到鎖之后再繼續執行。

這個結果的區別很明顯,通過sleep方法實現的暫停,程序是順序進入同步塊的,只有當上一個線程執行完成的時候,下一個線程才能進入同步方法,sleep暫停期間一直持有monitor對象鎖,其他線程是不能進入的。而wait方法則不同,當調用wait方法后,當前線程會釋放持有的monitor對象鎖,因此,其他線程還可以進入到同步方法,線程被喚醒后,需要競爭鎖,獲取到鎖之后再繼續執行。

sleep()和 wait()有什么區別?

定時等待和在監視器上等待,不同范疇。

4)yield方法

yield方法的作用是暫停當前線程,以便其他線程有機會執行,不過不能指定暫停的時間,並且也不能保證當前線程馬上停止。yield方法只是將Running狀態轉變為Runnable狀態。

通過yield方法來實現兩個線程的交替執行。不過請注意:這種交替並不一定能得到保證。

/**
     * A hint to the scheduler that the current thread is willing to yield
     * its current use of a processor. The scheduler is free to ignore this
     * hint.
     *
     * <p> Yield is a heuristic attempt to improve relative progression
     * between threads that would otherwise over-utilise a CPU. Its use
     * should be combined with detailed profiling and benchmarking to
     * ensure that it actually has the desired effect.
     *
     * <p> It is rarely appropriate to use this method. It may be useful
     * for debugging or testing purposes, where it may help to reproduce
     * bugs due to race conditions. It may also be useful when designing
     * concurrency control constructs such as the ones in the
     * {@link java.util.concurrent.locks} package.
*/

這段話主要說明了三個問題:

  •   調度器可能會忽略該方法。
  •   使用的時候要仔細分析和測試,確保能達到預期的效果。
  •   很少有場景要用到該方法,主要使用的地方是調試和測試。 

5)join方法

void join() Waits for this thread to die.
void join(long millis) Waits at most millis milliseconds for this thread to die.
void join(long millis, int nanos) Waits at most millis milliseconds plus nanos nanoseconds for this thread to die.

  join方法的作用是父線程等待子線程執行完成后再執行,換句話說就是將異步執行的線程合並為同步的線程。JDK中提供三個版本的join方法,其實現與wait方法類似,join()方法實際上執行的join(0),而join(long millis, int nanos)也與wait(long millis, int nanos)的實現方式一致,暫時對納秒的支持也是不完整的。

6)問題:wait/notify/notifyAll方法的作用是實現線程間的協作,那為什么這三個方法不是位於Thread類中,而是位於Object類中?

位於Object中,也就相當於所有類都包含這三個方法(因為Java中所有的類都繼承自Object類)。

要回答這個問題,還是得回過來看wait方法的實現原理,大家需要明白的是,wait等待的到底是什么東西?如果對上面內容理解的比較好的話,我相信大家應該很容易知道wait等待其實是對象monitor,由於Java中的每一個對象都有一個內置的monitor對象,自然所有的類都理應有wait/notify方法。

1.2.9 ThreadLocal

參考:徹底理解ThreadLocal

1)什么是ThreadLocal

早在JDK 1.2的版本中就提供java.lang.ThreadLocal,ThreadLocal為解決多線程程序的並發問題提供了一種新的思路。使用這個工具類可以很簡潔地編寫出優美的多線程程序。

當使用ThreadLocal維護變量時,ThreadLocal為每個使用該變量的線程提供獨立的變量副本,所以每一個線程都可以獨立地改變自己的副本,而不會影響其它線程所對應的副本。

從線程的角度看,目標變量就象是線程的本地變量,這也是類名中“Local”所要表達的意思。

2)Thread同步機制的比較

對於多線程資源共享的問題,同步機制采用了“以時間換空間”的方式,而ThreadLocal采用了“以空間換時間”的方式。前者僅提供一份變量,讓不同的線程排隊訪問,而后者為每一個線程都提供了一份變量,因此可以同時訪問而互不影響。

3)java.lang.ThreadLocal<T>的具體實現

(1)set

getMap和createMap

(2)get方法

(3)setInitialValue方法

4)jvm怎么讓多線程new 對象時候  內存不用加鎖

預先分配一定量的內存給線程。

1.2.10 java多線程api及jdk1.5以后new api

1)concurrent包下面,都用過什么?

Executor                  :具體Runnable任務的執行者。線程池
ExecutorService           :一個線程池管理者,其實現類有多種,我會介紹一部分。我們能把Runnable,Callable提交到池中讓其調度。
Semaphore                 :一個計數信號量
ReentrantLock             :一個可重入的互斥鎖定 Lock,功能類似synchronized,但要強大的多。
Future                    :是與Runnable,Callable進行交互的接口,比如一個線程執行結束后取返回的結果等等,還提供了cancel終止線程。
BlockingQueue             :阻塞隊列。
CompletionService         : ExecutorService的擴展,可以獲得線程執行結果的
CountDownLatch            :一個同步輔助類,在完成一組正在其他線程中執行的操作之前,它允許一個或多個線程一直等待。 
CyclicBarrier             :一個同步輔助類,它允許一組線程互相等待,直到到達某個公共屏障點 
Future                    :Future 表示異步計算的結果。
ScheduledExecutorService :一個 ExecutorService,可安排在給定的延遲后運行或定期執行的命令

2)線程池用過嗎?

線程池是一種多線程處理形式,處理過程中將任務添加到隊列,然后在創建線程后自動啟動這些任務。

線程池線程都是后台線程。每個線程都使用默認的堆棧大小,以默認的優先級運行,並處於多線程單元中。

如果某個線程在托管代碼中空閑(如正在等待某個事件),則線程池將插入另一個輔助線程來使所有處理器保持繁忙。

如果所有線程池線程都始終保持繁忙,但隊列中包含掛起的工作,則線程池將在一段時間后創建另一個輔助線程但線程的數目永遠不會超過最大值。

超過最大值的線程可以排隊,但他們要等到其他線程完成后才啟動。

3)java中有幾種方法可以創建一個線程?

4)如何停止一個正在運行的線程?

(1)public void Thread.interrupt() // 無返回值
  • 如果該線程正阻塞於Object類的wait()wait(long)wait(long, int)方法,或者Thread類的join()join(long)join(long, int)sleep(long)sleep(long, int)方法,則該線程的中斷狀態將被清除,並收到一個java.lang.InterruptedException
  • 如果該線程正阻塞於interruptible channel上的I/O操作,則該通道將被關閉,同時該線程的中斷狀態被設置,並收到一個java.nio.channels.ClosedByInterruptException
  • 如果該線程正阻塞於一個java.nio.channels.Selector操作,則該線程的中斷狀態被設置,它將立即從選擇操作返回,並可能帶有一個非零值,就好像調用java.nio.channels.Selector.wakeup()方法一樣。
  • 如果上述條件都不成立,則該線程的中斷狀態將被設置。
(2)public boolean Thread.isInterrupted() // 有返回值
檢測當前線程是否已經中斷,是則返回true,否則false。中斷狀態不受該方法的影響。如果中斷調用時線程已經不處於活動狀態,則返回false。
(3)public static boolean Thread.interrupted() // 靜態,有返回值
檢測當前線程是否已經中斷,是則返回true,否則false,並清除中斷狀態。換言之,如果該方法被連續調用兩次,第二次必將返回false,除非在第一次與第二次的瞬間線程再次被中斷。如果中斷調用時線程已經不處於活動狀態,則返回false。
 

1.3 JVM

1.3.1 內存模型

1.3.1.1 jvm內存模型

1)jvm內存模型

2)Java 內存模型及GC原理

3)jdk1.8的JVM內存模型

4)jdk不同版本之間的特性

5)JVM分為哪些區,每一個區干嗎的?

多數 JVM 將內存區域划分為方法區 , Heap(堆) , Program Counter Register(程序計數器) , VM Stack(Java虛擬機棧),Native Method Stack  ( 本地方法棧 )
方法區:線程共享區域,Object Class Data(加載類的類定義數據)  是存儲在方法區的。除此之外, 常量 、 靜態變量 、JIT(即時編譯器)編譯后的代碼也都在方法區。
程序計數器是一塊較小的內存區域,作用可以看做是當前線程執行的字節碼的位置指示器。分支、循環、跳轉、異常處理和線程恢復等基礎功能都需要依賴這個計算器來完成,不多說。
Java虛擬機棧:Java虛擬機棧是線程私有,生命周期與線程相同,描述Java方法執行的內存模型,(每個方法執行的同時創建幀棧(Strack Frame)用於存儲局部變量表,操作數棧,動態鏈接、方法出口等信息)
Heap(堆)在:虛擬機管理的內存中最大的一塊,線程共享的內存區域,JVM啟動時候創建,專門用來保存對象的實例。
本地方法棧:與VM Strack相似,VM Strack為JVM提供執行JAVA方法的服務,Native Method Stack則為JVM提供使用native 方法的服務。

1.3.1.2 內存映射原理、好處

1)認真分析mmap:是什么 為什么 怎么用

2)內存映射文件原理探索

3)內存映射的原理

1.3.2 GC垃圾回收

1)Java系列筆記(3) - Java 內存區域和GC機制

2)JVM內存回收機制簡述

JVM垃圾回收機制

3)集中垃圾回收算法

4)JVM如何GC,新生代,老年代大對象,永久代,都存儲哪些東西?

采用分代收集算法,新生代采用復制算法,老年代采用標記整理算法。
新生代– 新創建的對象,
舊生代 – 經過多次垃圾回收沒有被回收的對象或者大對象
持久代– JVM使用的內存,包含類信息等

5)GC用的引用可達性分析算法中,哪些對象可作為GC Roots對象?

younggc來說,gcroot的對象包括:
(1)所有老年代對象
(2)所有全局對象
(3)所有jni句柄
(4)所有上鎖對象
(5)jvmti持有的對象
(6)代碼段code_cache
(7)所有classloader,及其加載的class
(8)所有字典
(9)flat_profiler和management
(10)最重要的,所有運行中線程棧上的引用類型變量。 

1.3.3 類加載機制

參考:

阿里java一面試題+解答

1)classloader結構,是否可以自己定義一個java.lang.String類,為什么? 雙親代理機制。

2)了解哪些osgi的框架?

3)OSGi用過哪些?類加載器結構如何,如何在一個bundle中加載另外一個bundle中的一個類?

4)jdk類加載器

5)tomcat中的類加載器

6)jboss的類加載器

7)JVM如何加載一個類的過程?

類從被加載到虛擬機內存中開始,到卸載出內存為止,它的整個生命周期包括:加載、驗證、准備、解析、初始化、使用和卸載七個階段。

其中類加載的過程包括了加載、驗證、准備、解析、初始化五個階段。在這五個階段中,加載、驗證、准備和初始化這四個階段發生的順序是確定的,而解析階段則不一定,它在某些情況下可以在初始化階段之后開始,這是為了支持 Java 語言的運行時綁定(也成為動態綁定或晚期綁定)。

另外注意這里的幾個階段是按順序開始,而不是按順序進行或完成,因為這些階段通常都是互相交叉地混合進行的,通常在一個階段執行的過程中調用或激活另一個階段。

8)雙親委派模型中有哪些方法?

雙親委派模型要求除了頂層的啟動類加載器外,其余的類加載器都應有自己的父類加載器。這些類加載器的父子關系不是以繼承的關系實現,而都是使用組合關系來復用父加載器的代碼。

雙親委派模型的工作過程:如果一個類加載器收到了類加載的請求,它首先不會自己去嘗試加載這個類,而是把這個請求委派給父類加載器去完成,每個層次的類加載器都是如此。因此所有的加載請求最終都應該傳達到頂層的啟動類加載器中,只有當父加載器反饋無法完成這個加載請求(它的搜索范圍中沒有找到所需的類)時,子加載器才會嘗試自己去加載。
findLoadedClass(name);  loadClass(name, false);  findClass(name);  resolveClass(c);  

9)JVM類加載替換jdk的jar

http://zoeminghong.github.io/2016/05/31/jvm20160531/

class卸載、熱替換和Tomcat的熱部署的分析

Java類加載器ClassLoader總結

Java ClassLoader原理分析

深入分析Java ClassLoader原理

ClassLoader

10)線程上下文類加載器(ContextClassLoader)

Java類加載器之線程上下文類加載器(ContextClassLoader)

1.3.4 JVM調優

1)有沒有做過jvm內存調優,如何做的,舉例子,用過哪些工具?

2)現在有一個進程掛起了,如何用工具查出原因?

3)用什么工具調試程序?JConsole,用過嗎?

JConsole 是一個內置 Java 性能分析器,
    概述: Displays overview information about the Java VM and monitored values.
    內存: 顯示內存使用信息
    線程: 顯示線程使用信息
    類: 顯示類裝載信息
    *VM摘要:*顯示java VM信息
    MBeans: 顯示 MBeans.

 

1.3.5 虛擬機

1)常見虛擬機類型

 

1.4 JavaIO

參考:也談IO模型

1.4.0 文件IO流

參考:

Java IO學習筆記(三):字節流與字符流

深入回顧Java輸入輸出流

1)知道有哪些流嗎?字節流與字符流有什么不一樣?它們間是怎么轉換的呢?

字節流在操作時本身不會用到緩沖區(內存),是文件本身直接操作的,而字符流在操作時使用了緩沖區,通過緩沖區再操作文件,如圖12-6所示。

 

2)Java中的I/O流類總結圖

1.4.1 同步、異步、阻塞、非阻塞

1)同步異步與阻塞非阻塞

IO的兩個重要步驟:發起IO請求,和實際的IO操作。

(1)同步和異步的區別

在unix網絡編程的定義里異步和非異步概念的區別就是實際的IO操作是否阻塞。如果不是就是異步,如果是就是同步。

同步就是指一個線程要等待上一個線程執行完之后才開始執行當前的線程。
異步是指一個線程去執行,它的下一個線程不必等待它執行完就開始執行。

同步和異步是針對應用程序和內核的交互而言的,同步指的是用戶進程觸發IO操作並等待或者輪詢的去查看IO操作是否就緒,而異步是指用戶進程觸發IO操作以后便開始做自己的事情,而當IO操作已經完成的時候會得到IO完成的通知。

(2)阻塞和非阻塞的區別

而阻塞和非阻塞的區別在於發起IO請求的時候是否會阻塞,如果會就是阻塞,不會就是非阻塞。

阻塞和非阻塞是針對於進程在訪問數據的時候,根據IO操作的就緒狀態來采取的不同方式,說白了是一種讀取或者寫入操作函數的實現方式,阻塞方式下讀取或者寫入函數將一直等待,而非阻塞方式下,讀取或者寫入函數會立即返回一個狀態值。  

同步和異步是目的,阻塞和非阻塞是實現方式。

編號 名詞 解釋 舉例
1 同步 指的是用戶進程觸發IO操作並等待或者輪詢的去查看IO操作是否就緒 自己上街買衣服,自己親自干這件事,別的事干不了。
2 異步 異步是指用戶進程觸發IO操作以后便開始做自己的事情,而當IO操作已經完成的時候會得到IO完成的通知(異步的特點就是通知) 告訴朋友自己合適衣服的尺寸,大小,顏色,讓朋友委托去賣,然后自己可以去干別的事。(使用異步IO時,Java將IO讀寫委托給OS處理,需要將數據緩沖區地址和大小傳給OS)
3 阻塞 所謂阻塞方式的意思是指, 當試圖對該文件描述符進行讀寫時, 如果當時沒有東西可讀,或者暫時不可寫, 程序就進入等待 狀態, 直到有東西可讀或者可寫為止 去公交站充值,發現這個時候,充值員不在(可能上廁所去了),然后我們就在這里等待,一直等到充值員回來為止。(當然現實社會,可不是這樣,但是在計算機里確實如此。)
4 非阻塞 非阻塞狀態下, 如果沒有東西可讀, 或者不可寫, 讀寫函數馬上返回, 而不會等待, 銀行里取款辦業務時,領取一張小票,領取完后我們自己可以玩玩手機,或者與別人聊聊天,當輪我們時,銀行的喇叭會通知,這時候我們就可以去了。

2)組合方式的IO類型

(1)同步阻塞IO(JAVA BIO):
    同步並阻塞,服務器實現模式為一個連接一個線程,即客戶端有連接請求時服務器端就需要啟動一個線程進行處理,如果這個連接不做任何事情會造成不必要的線程開銷,當然可以通過線程池機制改善。
(2)同步非阻塞IO(Java NIO) : 同步非阻塞,服務器實現模式為一個請求一個線程,即客戶端發送的連接請求都會注冊到多路復用器上,多路復用器輪詢到連接有I/O請求時才啟動一個線程進行處理。用戶進程也需要時不時的詢問IO操作是否就緒,這就要求用戶進程不停的去詢問。
(3)異步阻塞IO(Java NIO):
   此種方式下是指應用發起一個IO操作以后,不等待內核IO操作的完成,等內核完成IO操作以后會通知應用程序,這其實就是同步和異步最關鍵的區別,同步必須等待或者主動的去詢問IO是否完成,那么為什么說是阻塞的呢?因為此時是通過select系統調用來完成的,而select函數本身的實現方式是阻塞的,而采用select函數有個好處就是它可以同時監聽多個文件句柄(如果從UNP的角度看,select屬於同步操作。因為select之后,進程還需要讀寫數據),從而提高系統的並發性! 
(4)異步非阻塞IO(Java AIO(NIO.2)): 
   在此種模式下,用戶進程只需要發起一個IO操作然后立即返回,等IO操作真正的完成以后,應用程序會得到IO操作完成的通知,此時用戶進程只需要對數據進行處理就好了,不需要進行實際的IO讀寫操作,因為真正的IO讀取或者寫入操作已經由內核完成了。

3)BIO、NIO、AIO適用場景分析

(1)BIO方式適用於連接數目比較小且固定的架構,這種方式對服務器資源要求比較高,並發局限於應用中,JDK1.4以前的唯一選擇,但程序直觀簡單易理解。 

(2)NIO方式適用於連接數目多且連接比較短(輕操作)的架構,比如聊天服務器,並發局限於應用中,編程比較復雜,JDK1.4開始支持。 

(3)AIO方式使用於連接數目多且連接比較長(重操作)的架構,比如相冊服務器,充分調用OS參與並發操作,編程比較復雜,JDK7開始支持。 

1.4.2 Java BIO

1.4.3 Java NIO

1)NIO/AIO的區別

2)nio是否了解,阻塞之后通知機制是怎樣的?

主動的去詢問IO是否完成。

1.4.3 Java AIO

1.4.4 讀取大文件

1)java 分次讀取大文件的三種方法

(1)在內存中讀取

文件的所有行都被存放在內存中,當文件足夠大時很快就會導致程序拋出OutOfMemoryError 異常。

(2)使用文件流讀取

使用java.util.Scanner類掃描文件的內容,一行一行連續地讀取。

(3)使用Apache Common IO流

使用Commons IO庫實現,利用該庫提供的自定義LineIterator。

(4)RandomAccessFile 操作動態文件

分析日志系統,多線程操作同一文件更高效,方便。

(5)分段讀取文件

2)Linux讀寫超過2G的文件

linux默認環境下打開、讀、寫超過2G的文件會返回錯誤。

看到了linux的ext2文件大小的限制。根據ext2的結構一共是三級索引,文件最大可達12KB+256KB+64MB+16GB。呵呵,linux操作系統是32位操作系統文件大小只能是4G。呵呵
好像以前的版本均有2G限制。

如何解決linux下文件大小的限制:

在linux下,用fwrite等C API函數來寫文件時,會有一個文件大小的限制,一般是2G。

在linux下fopen對要打開的文件大小是有限制的,限制來源於off_t這個類型大小的限制:
“lseek(2)  uses  the type off_t.  This is a 32-bit signed type on 32-bit architectures, unless one compiles with
#define _FILE_OFFSET_BITS 64
in which case it is a 64-bit signed type”摘自man
對於32位程序,fopen無法打開大於2G的文件,但可用下面的方法突破限制:
(1)編譯成64位程序
(2)使用fopen64,fseek64
(3)文件開頭加上#define _FILE_OFFSET_BITS 64 

3)jdk5,6的build in的zip都不支持4G以上的zip文件,jdk7支持

5000多萬的文件,txt文件超過4G,zip大約為600多MB。

http://blogs.oracle.com/xuemingshen/entry/zip64_support_for_4g_zipfile 
jdk 5, 6 的build in 的zip 都不支持 4G 以上的 zip文件,不過 jdk 7 支持。 
如果不用最新的jdk 7的話,你可以用第三方的zip庫的, 
你可以看看這個符不符合你的要求 http://commons.apache.org/compress/index.html 

1.5 序列化

1.5.1 transient關鍵字

參考:

在Java中如何使用transient

Java transient關鍵字使用小記

1)當對象被序列化時(寫入字節序列到目標文件)時,transient阻止實例中那些用此關鍵字聲明的變量持久化;當對象被反序列化時(從源文件讀取字節序列進行重構),這樣的實例變量值不會被持久化和恢復。

1.5.2 序列化的原理

參考:

Java 序列化的高級認識

Java 序列化 (Serializable) 的作用

JAVA中SERIALVERSIONUID的解釋

Java中序列化的serialVersionUID作用

1)Java 的序列化做什么用的,序列化id會出現哪些問題?

(1)序列化就是將一個對象的狀態(各個屬性量)保存起來,然后在適當的時候再獲得。

(2)虛擬機是否允許反序列化,不僅取決於類路徑和功能代碼是否一致,一個非常重要的一點是兩個類的序列化 ID 是否一致(就是 private static final long serialVersionUID = 1L)。 

1.6 異常

1)java中有哪些異常?這些異常怎么避免呢?假如是空指針,怎么避免它呢?

 

2. 玩好JDK

參考:http://www.zuoxiaolong.com/html/article_232.html

2.1 Java運行時環境,即JVM

2.2 Java的基礎類庫

1)JDK6的包列表

java.applet 
java.awt 
java.awt.color 
java.awt.datatransfer 
java.awt.dnd 
java.awt.event 
java.awt.font 
java.awt.geom 
java.awt.im 
java.awt.im.spi 
java.awt.image 
java.awt.image.renderable 
java.awt.print 
java.beans 
java.beans.beancontext 
java.io 
java.lang 
java.lang.annotation 
java.lang.instrument 
java.lang.management 
java.lang.ref 
java.lang.reflect 
java.math 
java.net 
java.nio 
java.nio.channels 
java.nio.channels.spi 
java.nio.charset 
java.nio.charset.spi 
java.rmi 
java.rmi.activation 
java.rmi.dgc 
java.rmi.registry 
java.rmi.server 
java.security 
java.security.acl 
java.security.cert 
java.security.interfaces 
java.security.spec 
java.sql 
java.text 
java.text.spi 
java.util 
java.util.concurrent 
java.util.concurrent.atomic 
java.util.concurrent.locks 
java.util.jar 
java.util.logging 
java.util.prefs 
java.util.regex 
java.util.spi 
java.util.zip 
javax.accessibility 
javax.activation 
javax.activity 
javax.annotation 
javax.annotation.processing 
javax.crypto 
javax.crypto.interfaces 
javax.crypto.spec 
javax.imageio 
javax.imageio.event 
javax.imageio.metadata 
javax.imageio.plugins.bmp 
javax.imageio.plugins.jpeg 
javax.imageio.spi 
javax.imageio.stream 
javax.jws 
javax.jws.soap 
javax.lang.model 
javax.lang.model.element 
javax.lang.model.type 
javax.lang.model.util 
javax.management 
javax.management.loading 
javax.management.modelmbean 
javax.management.monitor 
javax.management.openmbean 
javax.management.relation 
javax.management.remote 
javax.management.remote.rmi 
javax.management.timer 
javax.naming 
javax.naming.directory 
javax.naming.event 
javax.naming.ldap 
javax.naming.spi 
javax.net 
javax.net.ssl 
javax.print 
javax.print.attribute 
javax.print.attribute.standard 
javax.print.event 
javax.rmi 
javax.rmi.CORBA 
javax.rmi.ssl 
javax.script 
javax.security.auth 
javax.security.auth.callback 
javax.security.auth.kerberos 
javax.security.auth.login 
javax.security.auth.spi 
javax.security.auth.x500 
javax.security.cert 
javax.security.sasl 
javax.sound.midi 
javax.sound.midi.spi 
javax.sound.sampled 
javax.sound.sampled.spi 
javax.sql 
javax.sql.rowset 
javax.sql.rowset.serial 
javax.sql.rowset.spi 
javax.swing 
javax.swing.border 
javax.swing.colorchooser 
javax.swing.event 
javax.swing.filechooser 
javax.swing.plaf 
javax.swing.plaf.basic 
javax.swing.plaf.metal 
javax.swing.plaf.multi 
javax.swing.plaf.synth 
javax.swing.table 
javax.swing.text 
javax.swing.text.html 
javax.swing.text.html.parser 
javax.swing.text.rtf 
javax.swing.tree 
javax.swing.undo 
javax.tools 
javax.transaction 
javax.transaction.xa 
javax.xml 
javax.xml.bind 
javax.xml.bind.annotation 
javax.xml.bind.annotation.adapters 
javax.xml.bind.attachment 
javax.xml.bind.helpers 
javax.xml.bind.util 
javax.xml.crypto 
javax.xml.crypto.dom 
javax.xml.crypto.dsig 
javax.xml.crypto.dsig.dom 
javax.xml.crypto.dsig.keyinfo 
javax.xml.crypto.dsig.spec 
javax.xml.datatype 
javax.xml.namespace 
javax.xml.parsers 
javax.xml.soap 
javax.xml.stream 
javax.xml.stream.events 
javax.xml.stream.util 
javax.xml.transform 
javax.xml.transform.dom 
javax.xml.transform.sax 
javax.xml.transform.stax 
javax.xml.transform.stream 
javax.xml.validation 
javax.xml.ws 
javax.xml.ws.handler 
javax.xml.ws.handler.soap 
javax.xml.ws.http 
javax.xml.ws.soap 
javax.xml.ws.spi 
javax.xml.ws.wsaddressing 
javax.xml.xpath 
org.ietf.jgss 
org.omg.CORBA 
org.omg.CORBA_2_3 
org.omg.CORBA_2_3.portable 
org.omg.CORBA.DynAnyPackage 
org.omg.CORBA.ORBPackage 
org.omg.CORBA.portable 
org.omg.CORBA.TypeCodePackage 
org.omg.CosNaming 
org.omg.CosNaming.NamingContextExtPackage 
org.omg.CosNaming.NamingContextPackage 
org.omg.Dynamic 
org.omg.DynamicAny 
org.omg.DynamicAny.DynAnyFactoryPackage 
org.omg.DynamicAny.DynAnyPackage 
org.omg.IOP 
org.omg.IOP.CodecFactoryPackage 
org.omg.IOP.CodecPackage 
org.omg.Messaging 
org.omg.PortableInterceptor 
org.omg.PortableInterceptor.ORBInitInfoPackage 
org.omg.PortableServer 
org.omg.PortableServer.CurrentPackage 
org.omg.PortableServer.POAManagerPackage 
org.omg.PortableServer.POAPackage 
org.omg.PortableServer.portable 
org.omg.PortableServer.ServantLocatorPackage 
org.omg.SendingContext 
org.omg.stub.java.rmi 
org.w3c.dom 
org.w3c.dom.bootstrap 
org.w3c.dom.events 
org.w3c.dom.ls 
org.xml.sax 
org.xml.sax.ext 
org.xml.sax.helpers

2)精讀源碼

java.io
java.lang
java.util

精讀源碼,這是要求最高的級別。這三個包都是你最常用。lang包、io包讀寫文件,util包數據結構。

3)深刻理解

java.lang.reflect
java.net
javax.net.*
java.nio.*
java.util.concurrent.*

reflect代表反射,net代表網絡IO,nio代表非阻塞IO,concurrent代表並發。

反射你要了解清楚的話,你是不是要搞明白JVM的類加載機制?

網絡IO要搞清楚的話,你是不是要清楚TCP/IP和HTTP、HTTPS?

包括並發包,如果你要搞清楚的話,是不是要了解並發的相關知識?

4)會用即可

java.lang.annotation
javax.annotation.*
java.lang.ref
java.math
java.rmi.*
javax.rmi.*
java.security.*
javax.security.*
java.sql
javax.sql.*
javax.transaction.*
java.text
javax.xml.*
org.w3c.dom.*
org.xml.sax.*
javax.crypto.*
javax.imageio.*
javax.jws.*
java.util.jar
java.util.logging
java.util.prefs
java.util.regex
java.util.zip

5)請無視它

swing、awt

2.3 Java開發工具,即JDK

2.3.1 JDK內置工具

http://blog.csdn.net/fenglibing/article/details/6411953

(1)javah

http://www.hollischuang.com/archives/1107

(2)jps

http://www.hollischuang.com/archives/105

(3)jstack

http://www.cnblogs.com/nexiyi/p/java_thread_jstack.html

http://www.hollischuang.com/archives/110

性能分析之-- JAVA Thread Dump 分析綜述

Java線程Dump分析工具--jstack

(4)jstat

http://www.cnblogs.com/ListenWind/p/5230118.html

http://www.hollischuang.com/archives/481

(5)jmap

http://www.hollischuang.com/archives/303 

(6)jinfo

http://www.hollischuang.com/archives/1094

(7)jconsole

(8)jvisualvm

(9)jhat

http://www.hollischuang.com/archives/1047

(10)jdb

2.3.2 JVM故障分析

1)JVM性能調優之生成堆的dump文件

2)三個實例演示 Java Thread Dump 日志分析

3)用“逐步排除”的方法定位Java服務線上“系統性”故障

4)jvm內存調優用過哪些工具,jstat做什么用的?如何dump出當前線程狀態?

2.4 看源碼

1)看過哪些源碼

2.5 String

1)java中String類為什么要設計成final,StringBuffer 、StringBuilder的區別

2)String里面hashcode的實現方式

3. 數據結構與算法

參考:

程序員如何准備面試中的算法

一語中的:

(1)大部分的面試題都在圍繞一個點:基於各種數據結構上的增刪改查。

3.1 字符串

3.2 數組

3.3 鏈表

3.4 樹

1)什么是二叉平衡樹,如何插入節點,刪除節點,說出關鍵步驟。

它是一 棵空樹或它的左右兩個子樹的高度差的絕對值不超過1,也就是說AVL樹每個節點的平衡因子只可能是-1、0和1(左子樹高度減去右子樹高度)。並且左右兩個子樹都是一棵平衡二叉樹。
而調整平衡則需要旋轉:一共分為四種情況,左左,左右,右右,右左。
左右子孫搞對差不超過1,四種情況雙旋轉

3.5 圖

3.6 排序

1)快速排序,過程,復雜度?

快速排序(Quicksort)是對冒泡排序的一種改進。
通過一趟排序將要排序的數據分割成獨立的兩部分,其中一部分的所有數據都比另外一部分的所有數據都要小,然后再按此方法對這兩部分數據分別進行快速排序,整個排序過程可以遞歸進行,以此達到整個數據變成有序序列。
nln(n)

3.7 遞歸

3.8 動態規划

3.9 位操作

3.10 概率問題

3.11 排列組合

3.12 哈希算法

1)常用的hash算法有哪些?

常見的Hash算法有MD5和SHA 但是廣義的Hash算法,是指大范圍到小范圍的映射。如果按照你那個定義的話,那也算啊。算是廣義的hash算法。

2)什么是一致性哈希?

一致性哈希算法是一種分布式哈希(DHT)實現算法,設計目標是為了解決因特網中的熱點(Hot spot)問題,初衷和CARP十分類似。一致性哈希修正了CARP使用的簡 單哈希算法帶來的問題,使得分布式哈希(DHT)可以在P2P環境中真正得到應用。 
一致性hash算法提出了在動態變化的Cache環境中,判定哈希算法好壞的四個定義:
(1)平衡性(Balance):平衡性是指哈希的結果能夠盡可能分布到所有的緩沖中去,這樣可以使得所有的緩沖空間都得到利用。很多哈希算法都能夠滿足這一條件。
(2)單調性(Monotonicity):單調性是指如果已經有一些內容通過哈希分派到了相應的緩沖中,又有新的緩沖加入到系統中。哈希的結果應能夠保證原有已分配的內容可以被映射到原有的或者新的緩沖中去,而不會被映射到舊的緩沖集合中的其他緩沖區。 
 (3)分散性(Spread):在分布式環境中,終端有可能看不到所有的緩沖,而是只能看到其中的一部分。當終端希望通過哈希過程將內容映射到緩沖上時,由於不同終端所見的緩沖范圍有可能不同,從而導致哈希的結果不一致,最終的結果是相同的內容被不同的終端映射到不同的緩沖區中。這種情況顯然是應該避免的,因為它導致相同內容被存儲到不同緩沖中去,降低了系統存儲的效率。分散性的定義就是上述情況發生的嚴重程度。好的哈希算法應能夠盡量避免不一致的情況發生,也就是盡量降低分散性。 
(4)負載(Load):負載問題實際上是從另一個角度看待分散性問題。既然不同的終端可能將相同的內容映射到不同的緩沖區中,那么對於一個特定的緩沖區而言,也可能被不同的用戶映射為不同 的內容。與分散性一樣,這種情況也是應當避免的,因此好的哈希算法應能夠盡量降低緩沖的負荷。

3) 分而治之:hash映射 + hash_map統計 + 排序

3.20 《劍指offer》

1)《劍指offer》所有面試題匯總

2)《劍指Offer》套路匯總

3.21 《程序員編程藝術:面試和算法心得》

1)《程序員編程藝術:面試和算法心得》

3.29 算法牛人

1)9個offer,12家公司,35場面試,從微軟到谷歌,應屆計算機畢業生的2012求職之路

2)一個谷歌程序員的算法學習之路

3)程序員必讀書單

4)程序員如何快速准備面試中的算法

5)面試之算法篇—匯總

3.30 手寫代碼

1)寫代碼,實現一個線程池 寫一個並發只支持qps200的接口 

3.99 算法題

1)IP地址排序:

對一個IP排序 對一堆IP排序去重復

2)用並發的方式去做

(1)找出交易額最多的10個省份

(2)還有一個是有0到100的數字,找出相加為100的數字對

(3)求兩個數組交集

3)鏈表筆試題

如何判斷一個單鏈表是否有環?

如果有兩個頭結點指針,一個走的快,一個走的慢,那么若干步以后,快的指針總會超過慢的指針一圈。

4)面試常見算法-排序查找算法

5)二叉樹

程序基本功之遍歷二叉樹

輕松搞定面試中的二叉樹題目

面試常備題---二叉樹總結篇

6)時間復雜度和空間復雜度

算法的時間復雜度和空間復雜度

算法時間復雜度的計算 [整理]

算法的時間復雜度和空間復雜度-總結

 

4. 數據庫

4.1 事務

1)事務隔離級別

隔離等級 臟讀 不可重復讀 幻讀
讀未提交RU Yes Yes Yes
讀已提交RC No Yes Yes
可重復讀RR No No Yes
串行化 No No No

2)mysql 事務隔離級別 

Mysql事務隔離級別與樂觀鎖的問題

3)oracle 事務隔離級別

4.2 索引

1)mysql索引原理

2)mysql 索引類型

3)數據庫中的索引的結構?什么情況下適合建索引?

4)聚集索引和非聚集索引的區別

聚集索引和非聚集索引(整理)

4.3 鎖

參考:

數據庫鎖

並發編程(四):也談談數據庫的鎖機制

1)樂觀鎖、悲觀鎖的區別

參考:

深入理解樂觀鎖與悲觀鎖

Java並發編程經典題目

數據庫悲觀鎖和樂觀鎖

深入理解樂觀鎖與悲觀鎖

樂觀鎖的一種實現方式——CAS

樂觀鎖與悲觀鎖的區別

2)如何理解分布式鎖?

分布式鎖是控制分布式系統之間同步訪問共享資源的一種方式。在分布式系統中,常常需要協調他們的動作。如果不同的系統或是同一個系統的不同主機之間共享了一個或一組資源,那么訪問這些資源的時候,往往需要互斥來防止彼此干擾來保證一致性,在這種情況下,便需要使用到分布式鎖。

3)操作系統什么情況下會死鎖?

產生死鎖的原因:一是系統提供的資源數量有限,不能滿足每個進程的使用;二是多道程序運行時,進程推進順序不合理。  
產生死鎖的必要條件是:1、互斥條件;2、不可剝奪條件(不可搶占);3、部分分配;4、循環等待。  

 4.4 主鍵

1)數據庫設計中主鍵id設計的原則

4.5 SQL

1)PreparedStatement 優點

2)SQL 批處理的優點

3)where having區別

4)join 查詢的幾種方式

5)sql優化,具體場景、優化過程

6)mysql.oralce 優化策略

(1)索引

(2)用具體的字段列表代替“*"

(3)exists 代替 in

(4)盡量避免在where子句中對字段使用函數

(5)能用 between 就不要用 in 了

(6)避免在 where 子句中使用 or 來連接條件

(7)盡量避免在 where 子句中對字段進行 null 值判斷

(8)盡量避免在 where 子句中使用!=或<>操作符

(9)盡量避免全表掃描,首先應考慮在 where 及 order by 涉及的列上建立索引

物化視圖:

create materialized view mv_name refresh force on demand start with sysdate next

to_date(concat(to_char( sysdate+1,'dd-mm-yyyy'),'10:25:00'),'dd-mm-yyyy hh24:mi:ss') as select * from table_name --這個物化視圖在每天10:25進行刷新

4.6 數據庫高可用

1)mysql 主從分離,分庫分表

4.7 數據庫設計

1)數據庫中的范式有哪些?

第一范式:確保每列的原子性.
    如果每列(或者每個屬性)都是不可再分的最小數據單元(也稱為最小的原子單元),則滿足第一范式.
    例如:顧客表(姓名、編號、地址、……)其中"地址"列還可以細分為國家、省、市、區等。
第二范式:在第一范式的基礎上更進一層,目標是確保表中的每列都和主鍵相關.
    如果一個關系滿足第一范式,並且除了主鍵以外的其它列,都依賴於該主鍵,則滿足第二范式.
    例如:訂單表(訂單編號、產品編號、定購日期、價格、……),"訂單編號"為主鍵,"產品編號"和主鍵列沒有直接的關系,即"產品編號"列不依賴於主鍵列,應刪除該列。
第三范式:在第二范式的基礎上更進一層,目標是確保每列都和主鍵列直接相關,而不是間接相關.
    如果一個關系滿足第二范式,並且除了主鍵以外的其它列都不依賴於主鍵列,則滿足第三范式.
    為了理解第三范式,需要根據Armstrong公里之一定義傳遞依賴。假設A、B和C是關系R的三個屬性,如果A-〉B且B-〉C,則從這些函數依賴中,可以得出A-〉C,如上所述,依賴A-〉C是傳遞依賴。
例如:訂單表(訂單編號,定購日期,顧客編號,顧客姓名,……),初看該表沒有問題,滿足第二范式,每列都和主鍵列"訂單編號"相關,再細看你會發現"顧客姓名"和"顧客編號"相關,"顧客編號"和"訂單編號"又相關,最后經過傳遞依賴,"顧客姓名"也和"訂單編號"相關。為了滿足第三范式,應去掉"顧客姓名"列,放入客戶表中。 

5. 設計模式

5.1 設計模式

1)常用設計模式

設計模式主要分三個類型:創建型、結構型和行為型。

2)單例模式的線程安全問題

如何創建單例模式?說了雙重檢查,他說不是線程安全的。如何高效的創建的一個高效的單例?
單例設計模式的延遲加載模式必須加入同步,因此降低了系統性能,為了解決該問題,可以作如下改進:

 public class StaticSingleton{
  private StaticSingleton(){
   System.out.println("StaticSingleton is create");
  }

  private static class SingletonHolder{
   private static StaticSingleton instance = new StaticSingleton();
   
  }

  public static StaticSingleton getInstance(){
   return SingletonHolder.instance;
  }
 }

在該實現中,單例模式使用內部類來維護單例的實例,當StaticSingleton被加載時,其內部類並不會被初始化,故可以確保當StaticSingleton類被載入JVM時,不會初始化單例類,而當調用getInstance()方法時,才會加載StaticSingleton,從而初始化instance,同時,由於實例的建立是在類加載時完成,故天生對線程友好,getInstance()方法也不需要使用同步關鍵字。因此,該實現方式同時兼備延遲加載和非延遲加載的優點。
 注意:序列化和反序列化可能會破壞單例,一般來說,對單例進行序列化和反序列化的場景並不多見,但如果存在,就要多加注意。

3)了解哪些設計模式,6個設計原則分別是什么?每種設計原則體現的設計模式是哪個?

六大原則:

開閉原則、里氏代換原則、依賴倒轉原則、接口隔離原則、最少知道原則、合成復用原則。

4)關於設計模式看了哪些書?書名是什么?

5)java的io庫的類結構圖所用到的設計模式如何體現

6)畫出自己設計過的設計模式如何體現,畫出結構圖,並進行講解。

5.2 UML

1)uml模型圖畫過哪些? 類圖中類之間的關系有哪些,區別分別是什么?

2)畫uml中類圖時候用過一種虛線么?做什么用的? 

3)uml設計類圖如何畫,類之間關系以及區別

 

6. JavaEE/框架

6.1 Spring

1)Bean的加載過程

spring加載過程,源碼帶你理解從初始化到bean注入

2)Spring IOC概念和原理

(1)【第三章】 DI 之 3.3 更多DI的知識 ——跟我學spring3   地址:http://si shuo k.com/forum/blogPost/list/2453.html 

(2)spring如何不需要配置文件加載bean定義

使用自動注入。可按bean名稱或者bean類型自動注入。

3)AOP

(1)spring aop 用了什么設計原則,自動注入配置是做什么用的。

面向切面編程,代理模式。

(2)Spring AOP嵌套調用的問題

Spring AOP嵌套調用的問題 (同一類方法內調用切面切不到)

SpringAOP嵌套調用的解決辦法

Spring事務處理時自我調用的解決方案 嵌套AOP

Spring事務處理時自我調用的解決方案及一些實現方式的風險

4)Spring 事務

參考:

Spring的事務管理實現原理初探

深入理解 Spring 事務原理

spring源碼分析之——spring 事務管理實現方式

Spring 事務管理原理探究

(1)Spring事務的實現原理

Spring事務的本質其實就是數據庫對事務的支持,沒有數據庫的事務支持,spring是無法提供事務功能的。

(2)Spring 幾種事務傳播特性

(3)注解式事務和編程式事務的區別

參考:

全面分析 Spring 的編程式事務管理及聲明式事務管理

通過 Spring 提供的事務管理 API,我們可以在代碼中靈活控制事務的執行。在底層,Spring 仍然將事務操作委托給底層的持久化框架來執行。

Spring 的聲明式事務管理在底層是建立在 AOP 的基礎之上的。其本質是對方法前后進行攔截,然后在目標方法開始之前創建或者加入一個事務,在執行完目標方法之后根據執行情況提交或者回滾事務。

聲明式事務最大的優點就是不需要通過編程的方式管理事務,這樣就不需要在業務邏輯代碼中摻雜事務管理的代碼,只需在配置文件中做相關的事務規則聲明(或通過等價的基於標注的方式),便可以將事務規則應用到業務邏輯中。因為事務管理本身就是一個典型的橫切邏輯,正是 AOP 的用武之地。Spring 開發團隊也意識到了這一點,為聲明式事務提供了簡單而強大的支持。

基於 <tx> 命名空間和基於 @Transactional 的事務聲明方式各有優缺點。基於 <tx> 的方式,其優點是與切點表達式結合,功能強大。利用切點表達式,一個配置可以匹配多個方法,而基於 @Transactional 的方式必須在每一個需要使用事務的方法或者類上用 @Transactional 標注,盡管可能大多數事務的規則是一致的,但是對 @Transactional 而言,也無法重用,必須逐個指定。另一方面,基於 @Transactional 的方式使用起來非常簡單明了,沒有學習成本。開發人員可以根據需要,任選其中一種使用,甚至也可以根據需要混合使用這兩種方式。

編程式事務需要你在代碼中直接加入處理事務的邏輯,可能需要在代碼中顯式調用beginTransaction()、commit()、rollback()等事務管理相關的方法,如在執行a方法時候需要事務處理,你需要在a方法開始時候開啟事務,處理完后。在方法結束時候,關閉事務.
聲明式的事務的做法是在a方法外圍添加注解或者直接在配置文件中定義,a方法需要事務處理,在spring中會通過配置文件在a方法前后攔截,並添加事務.
二者區別.編程式事務侵入性比較強,但處理粒度更細.
 

6.2 MyBatis

1)ibatis in操作以及一個屬性的作用

6.3 Servlet/Filter/SpringMVC

1)servlet/filter作用原理配置

2)servlet生命周期

Servlet的生命周期是由Servlet的容器來控制的,它可以分為3個階段;初始化,運行,銷毀。

初始化階段:

(1)Servlet容器加載servlet類,把servlet類的.class文件中的數據讀到內存中。

(2)然后Servlet容器創建一個ServletConfig對象。ServletConfig對象包含了Servlet的初始化配置信息。

(3)Servlet容器創建一個servlet對象。

(4)Servlet容器調用servlet對象的init方法進行初始化。

運行階段:

當servlet容器接收到一個請求時,servlet容器會針對這個請求創建servletRequest和servletResponse對象。

然后調用service方法。並把這兩個參數傳遞給service方法。Service方法通過servletRequest對象獲得請求的信息。並處理該請求。再通過servletResponse對象生成這個請求的響應結果。然后銷毀servletRequest和servletResponse對象。我們不管這個請求是post提交的還是get提交的,最終這個請求都會由service方法來處理。

銷毀階段:

當Web應用被終止時,servlet容器會先調用servlet對象的destrory方法,然后再銷毀servlet對象,同時也會銷毀與servlet對象相關聯的servletConfig對象。我們可以在destroy方法的實現中,釋放servlet所占用的資源,如關閉數據庫連接,關閉文件輸入輸出流等。

在這里該注意的地方:

在servlet生命周期中,servlet的初始化和和銷毀階段只會發生一次,而service方法執行的次數則取決於servlet被客戶端訪問的次數。

3)springmvc 源碼了解  

6.4 Session

1)session共享機制

2)session 原理

3)分布式集群怎么做session

6.5 HTTP協議

1)http協議,返回碼,301與302區別

2)http 1.0 1.1版本的區別

3)怎么解決跨域訪問

出於安全考慮,HTML的同源策略不允許JavaScript進行跨域操作, 直接發送跨域 AJAX 會得到如下錯誤。

解決跨域訪問的方法:

(1)一類是Hack,比如通過titlenavigation等對象傳遞信息,JSONP可以說是一個最優秀的Hack。

(2)另一類是HTML5支持,一個是Access-Control-Allow-Origin響應頭,一個是window.postMessage

(3)跨域資源共享(CORS)

  • 原理:服務器設置Access-Control-Allow-OriginHTTP響應頭之后,瀏覽器將會允許跨域請求
  • 限制:瀏覽器需要支持HTML5,可以支持POST,PUT等方法

6.6 TCP/IP協議

1)TCP如何保證可靠傳輸?三次握手過程?

TCP用三次握手和滑動窗口機制來保證傳輸的可靠性和進行流量控制。
第一次握手:
客戶端發送一個TCP的SYN標志位置1的包指明客戶打算連接的服務器的端口,以及初始序號X,保存在包頭的序列號(Sequence Number)字段里。
第二次握手:
服務器發回確認包(ACK)應答。即SYN標志位和ACK標志位均為1同時,將確認序號(Acknowledgement Number)設置為客戶的I S N加1以.即X+1。
第三次握手.
客戶端再次發送確認包(ACK) SYN標志位為0,ACK標志位為1.並且把服務器發來ACK的序號字段+1,放在確定字段中發送給對方.並且在數據段放寫ISN的+1

2)TCP和UDP區別?

TCP---傳輸控制協議,提供的是面向連接、可靠的字節流服務。當客戶和服務器彼此交換數據前,必須先在雙方之間建立一個TCP連接,之后才能傳輸數據。TCP提供超時重發,丟棄重復數據,檢驗數據,流量控制等功能,保證數據能從一端傳到另一端。
UDP---用戶數據報協議,是一個簡單的面向數據報的運輸層協議。UDP不提供可靠性,它只是把應用程序傳給IP層的數據報發送出去,但是並不能保證它們能到達目的地。由於UDP在傳輸數據報前不用在客戶和服務器之間建立一個連接,且沒有超時重發等機制,故而傳輸速度很快

3)滑動窗口算法?

我們可以大概看一下上圖的模型:
1. 首先是AB之間三次握手建立TCP連接。在報文的交互過程中,A將自己的緩沖區大小(窗口大小)3發送給B,B同理,這樣雙方就知道了對端的窗口大小。
2. A開始發送數據,A連續發送3個單位的數據,因為他知道B的緩沖區大小。在這一波數據發送完后,A就不能再發了,需等待B的確認。
3. A發送過來的數據逐漸將緩沖區填滿。
4. 這時候緩沖區中的一個報文被進程讀取,緩沖區有了一個空位,於是B向A發送一個ACK,這個報文中指示窗口大小為1。
A收到B發過來的ACK消息,並且知道B將窗口大小調整為1,因此他只發送了一個單位的數據並且等待B的下一個確認報文。
5. 如此反復。

6.7 應用服務器

1)談談你最熟悉的應用服務器

2)Tomcat的配置

6.8 操作系統

1)進程間通信有哪幾種方式?

# 管道( pipe ):管道是一種半雙工的通信方式,數據只能單向流動,而且只能在具有親緣關系的進程間使用。進程的親緣關系通常是指父子進程關系。
# 有名管道 (named pipe) : 有名管道也是半雙工的通信方式,但是它允許無親緣關系進程間的通信。
# 信號量( semophore ) : 信號量是一個計數器,可以用來控制多個進程對共享資源的訪問。它常作為一種鎖機制,防止某進程正在訪問共享資源時,其他進程也訪問該資源。因此,主要作為進程間以及同一進程內不同線程之間的同步手段。
# 消息隊列( message queue ) : 消息隊列是由消息的鏈表,存放在內核中並由消息隊列標識符標識。消息隊列克服了信號傳遞信息少、管道只能承載無格式字節流以及緩沖區大小受限等缺點。
# 信號 ( sinal ) : 信號是一種比較復雜的通信方式,用於通知接收進程某個事件已經發生。
# 共享內存( shared memory ) :共享內存就是映射一段能被其他進程所訪問的內存,這段共享內存由一個進程創建,但多個進程都可以訪問。共享內存是最快的 IPC 方式,它是針對其他進程間通信方式運行效率低而專門設計的。它往往與其他通信機制,如信號兩,配合使用,來實現進程間的同步和通信。
# 套接字( socket ) : 套接字也是一種進程間通信機制,與其他通信機制不同的是,它可用於不同及其間的進程通信。

7. 測試

1)做過應用相關性能測試的,舉個例子,實際項目中怎么使用的。

 

8. SOA

參考:

淺析分布式系統

8.1 RPC框架

1)dubbo 原理、序列化方式

Dubbo是一個分布式服務框架,提供高性能和透明化的RPC遠程服務調用方案

節點角色說明:

Provider: 暴露服務的服務提供方。

Consumer: 調用遠程服務的服務消費方。

Registry: 服務注冊與發現的注冊中心。

Monitor: 統計服務的調用次調和調用時間的監控中心。

Container: 服務運行容器。

2)RPC的原理

3)zookeeper 原理

4)對微服務的理解

5)Spring Boot的使用

8.2 消息系統

8.3 分布式緩存

1)redis數據類型

String,Hash,List,Set,Sorted set

8.4 分布式文件系統

8.5 分布式事務

參考:

分布式系統事務一致性解決方案

保證分布式系統數據一致性的6種方案

分布式事務演進

分布式事務框架學習實踐心得

如何用消息系統避免分布式事務?

分布式系統的事務處理

1)分布式事務的實現方式

提供回滾接口、本地消息表、MQ(非事務消息)、MQ(事務消息)

8.6 分布式鎖

參考:

分布式鎖的幾種實現方式~

8.7 搜索引擎

1)ElasticSearch 的優點

8.8 負載均衡

1)nginx作用和在項目中的使用場景

2)負載均衡算法

8.9 大數據開發

1)了解hadoop嗎?說說hadoop的組件有哪些?hdfs,hive,hbase,zookeeper。說下mapreduce編程模型。

 

9. 運維

9.1 運維發布

9.2 高可用、高並發

9.3 多機房多站點

9.4 Linux

1)說出Linux常用命令

pwd 顯示當前目錄

ls 查看目錄下的內容

cd 改變所在目錄

cat 顯示文件的內容  

grep 在文件中查找某字符

cp 復制文件  

touch 創建文件

mv 移動文件  

rm 刪除文件

rmdir 刪除目錄

2)Linux系統是什么版本、內核,線上問題怎么排查

3)Linux下如何進行進程調度的?

在Linux中,進程的運行時間不可能超過分配給他們的時間片,他們采用的是搶占式多任務處理,所以進程之間的掛起和繼續運行無需彼此之間的協作。
4種,臨界區、互斥量、信號量、

4)70個經典的 Shell 腳本面試問題

10. 安全

1)密碼控件

2)數字證書

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM