接着上篇繼續更新。html
/*請尊重做者勞動成果,轉載請標明原文連接:*/java
/* http://www.javashuo.com/article/p-bdqzuguw-gu.html * /設計模式
題目一:接口和抽象類有什麼區別?數組
通常回答:
接口是對行爲的抽象,它是抽象方法的集合,利用接口能夠達到 API 定義和實現分離的目的。接口,不能實例化;不能包含任何很是量成員,任何 field 都是隱含着 public static final 的意義;同時,沒有非靜態方法實現,也就是說要麼是抽象方法,要麼是靜態方法。Java 標準類庫中,定義了很是多的接口,好比 java.util.List。
抽象類是不能實例化的類,用 abstract 關鍵字修飾 class,其目的主要是代碼重用。除了不能實例化,形式上和通常的 Java 類並無太大區別,能夠有一個或者多個抽象方法,也能夠沒有抽象方法。抽象類大多用於抽取相關 Java 類的共用方法實現或者是共同成員變量,而後經過繼承的方式達到代碼複用的目的。Java 標準庫中,好比 collection 框架,不少通用部分就被抽取成爲抽象類,例如 java.util.AbstractList。
Java 類實現 interface 使用 implements 關鍵詞,繼承 abstract class 則是使用 extends 關鍵詞,咱們能夠參考 Java 標準庫中的 ArrayList。
下面再給出一種擴展回答:
1.語法層面上的區別
1)抽象類能夠提供成員方法的實現細節,而接口中只能存在public abstract 方法;
2)抽象類中的成員變量能夠是各類類型的,而接口中的成員變量只能是public static final類型的;
3)一個類只能繼承一個抽象類,而一個類卻能夠實現多個接口。
2.設計層面上的區別
1)抽象類是對一種事物的抽象,即對類抽象,而接口是對行爲的抽象。抽象類是對整個類總體進行抽象,包括屬性、行爲,可是接口倒是對類局部(行爲)進行抽象。舉個簡單的例子,飛機和鳥是不一樣類的事物,可是它們都有一個共性,就是都會飛。那麼在設計的時候,能夠將飛機設計爲一個類Airplane,將鳥設計爲一個類Bird,可是不能將 飛行 這個特性也設計爲類,所以它只是一個行爲特性,並非對一類事物的抽象描述。此時能夠將 飛行 設計爲一個接口Fly,包含方法fly( ),而後Airplane和Bird分別根據本身的須要實現Fly這個接口。而後至於有不一樣種類的飛機,好比戰鬥機、民用飛機等直接繼承Airplane便可,對於鳥也是相似的,不一樣種類的鳥直接繼承Bird類便可。從這裏能夠看出,繼承是一個 "是否是"的關係,而 接口 實現則是 "有沒有"的關係。若是一個類繼承了某個抽象類,則子類一定是抽象類的種類,而接口實現則是有沒有、具有不具有的關係,好比鳥是否能飛(或者是否具有飛行這個特色),能飛行則能夠實現這個接口,不能飛行就不實現這個接口。
2)設計層面不一樣,抽象類做爲不少子類的父類,它是一種模板式設計。而接口是一種行爲規範,它是一種輻射式設計。什麼是模板式設計?最簡單例子,你們都用過ppt裏面的模板,若是用模板A設計了ppt B和ppt C,ppt B和ppt C公共的部分就是模板A了,若是它們的公共部分須要改動,則只須要改動模板A就能夠了,不須要從新對ppt B和ppt C進行改動。而輻射式設計,好比某個電梯都裝了某種報警器,一旦要更新報警器,就必須所有更新。也就是說對於抽象類,若是須要添加新的方法,能夠直接在抽象類中添加具體的實現,子類能夠不進行變動;而對於接口則不行,若是接口進行了變動,則全部實現這個接口的類都必須進行相應的改動。
下面看一個網上流傳最普遍的例子:門和警報的例子:門都有open( )和close( )兩個動做,此時咱們能夠定義經過抽象類和接口來定義這個抽象概念:
1 abstract class Door {
2 public abstract void open();
3 public abstract void close();
4 }
或者:
1 interface Door {
2 public abstract void open();
3 public abstract void close();
4 }
可是如今若是咱們須要門具備報警alarm( )的功能,那麼該如何實現?下面提供兩種思路:
1)將這三個功能都放在抽象類裏面,可是這樣一來全部繼承於這個抽象類的子類都具有了報警功能,可是有的門並不必定具有報警功能;
2)將這三個功能都放在接口裏面,須要用到報警功能的類就須要實現這個接口中的open( )和close( ),也許這個類根本就不具有open( )和close( )這兩個功能,好比火災報警器。
從這裏能夠看出, Door的open() 、close()和alarm()根本就屬於兩個不一樣範疇內的行爲,open()和close()屬於門自己固有的行爲特性,而alarm()屬於延伸的附加行爲。所以最好的解決辦法是單獨將報警設計爲一個接口,包含alarm()行爲,Door設計爲單獨的一個抽象類,包含open和close兩種行爲。再設計一個報警門繼承Door類和實現Alarm接口。
1 interface Alram {
2 void alarm();
3 }
4
5 abstract class Door {
6 void open();
7 void close();
8 }
9
10 class AlarmDoor extends Door implements Alarm {
11 void oepn() {
12 //....
13 }
14 void close() {
15 //....
16 }
17 void alarm() {
18 //....
19 }
20 }
題目二:如何保證集合是線程安全的?安全
通常回答:
Java 提供了不一樣層面的線程安全支持。在傳統集合框架內部,除了 Hashtable 等同步容器,還提供了所謂的同步包裝器(Synchronized Wrapper),咱們能夠調用 Collections 工具類提供的包裝方法,來獲取一個同步的包裝容器(如 Collections.synchronizedMap),可是它們都是利用很是粗粒度的同步方式,在高併發狀況下,性能比較低下。
另外,更加廣泛的選擇是利用併發包提供的線程安全容器類,它提供了:
各類併發容器,好比 ConcurrentHashMap、CopyOnWriteArrayList。
各類線程安全隊列(Queue/Deque),如 ArrayBlockingQueue、SynchronousQueue。
各類有序容器的線程安全版本等。
具體保證線程安全的方式,包括有從簡單的 synchronize 方式,到基於更加精細化的,好比基於分離鎖實現的 ConcurrentHashMap 等併發實現等。具體選擇要看開發的場景需求,整體來講,併發包內提供的容器通用場景,遠優於早期的簡單同步實現。
接下來繼續擴展下HashMap和ConcurrentHashMap的實現。由於JAVA7和JAVA8的實現區別很大,下面分別從JAVA7和JAVA8簡單介紹下其實現。
Java7 HashMap
HashMap 是最簡單的,一來咱們很是熟悉,二來就是它不支持併發操做,因此源碼也很是簡單。
首先,咱們用下面這張圖來介紹 HashMap 的結構。
大方向上,HashMap 裏面是一個數組,而後數組中每一個元素是一個單向鏈表。
上圖中,每一個綠色的實體是嵌套類 Entry 的實例,Entry 包含四個屬性:key, value, hash 值和用於單向鏈表的 next。
Java7 ConcurrentHashMap
ConcurrentHashMap 和 HashMap 思路是差很少的,可是由於它支持併發操做,因此要複雜一些。
整個 ConcurrentHashMap 由一個個 Segment 組成,Segment 表明」部分「或」一段「的意思,因此不少地方都會將其描述爲分段鎖。注意,行文中,我不少地方用了「槽」來表明一個 segment。
簡單理解就是,ConcurrentHashMap 是一個 Segment 數組,Segment 經過繼承 ReentrantLock 來進行加鎖,因此每次須要加鎖的操做鎖住的是一個 segment,這樣只要保證每一個 Segment 是線程安全的,也就實現了全局的線程安全。
concurrencyLevel:並行級別、併發數、Segment 數,怎麼翻譯不重要,理解它。默認是 16,也就是說 ConcurrentHashMap 有 16 個 Segments,因此理論上,這個時候,最多能夠同時支持 16 個線程併發寫,只要它們的操做分別分佈在不一樣的 Segment 上。這個值能夠在初始化的時候設置爲其餘值,可是一旦初始化之後,它是不能夠擴容的。
再具體到每一個 Segment 內部,其實每一個 Segment 很像以前介紹的 HashMap,不過它要保證線程安全,因此處理起來要麻煩些。
Java8 HashMap
Java8 對 HashMap 進行了一些修改,最大的不一樣就是利用了紅黑樹,因此其由 數組+鏈表+紅黑樹 組成。
根據 Java7 HashMap 的介紹,咱們知道,查找的時候,根據 hash 值咱們可以快速定位到數組的具體下標,可是以後的話,須要順着鏈表一個個比較下去才能找到咱們須要的,時間複雜度取決於鏈表的長度,爲 O(n)。
爲了下降這部分的開銷,在 Java8 中,當鏈表中的元素超過了 8 個之後,會將鏈表轉換爲紅黑樹,在這些位置進行查找的時候能夠下降時間複雜度爲 O(logN)。
來一張圖簡單示意一下吧:
注意,上圖是示意圖,主要是描述結構,不會達到這個狀態的,由於這麼多數據的時候早就擴容了。
下面,咱們仍是用代碼來介紹吧,我的感受,Java8 的源碼可讀性要差一些,不過精簡一些。
Java7 中使用 Entry 來表明每一個 HashMap 中的數據節點,Java8 中使用 Node,基本沒有區別,都是 key,value,hash 和 next 這四個屬性,不過,Node 只能用於鏈表的狀況,紅黑樹的狀況須要使用 TreeNode。
咱們根據數組元素中,第一個節點數據類型是 Node 仍是 TreeNode 來判斷該位置下是鏈表仍是紅黑樹的。
Java8 ConcurrentHashMap
Java7 中實現的 ConcurrentHashMap 說實話仍是比較複雜的,Java8 對 ConcurrentHashMap 進行了比較大的改動。建議讀者能夠參考 Java8 中 HashMap 相對於 Java7 HashMap 的改動,對於 ConcurrentHashMap,Java8 也引入了紅黑樹。
說實話,Java8 ConcurrentHashMap 源碼真心不簡單,最難的在於擴容,數據遷移操做不容易看懂。
咱們先用一個示意圖來描述下其結構:
結構上和 Java8 的 HashMap 基本上同樣,不過它要保證線程安全性,因此在源碼上確實要複雜一些。
題目三:談談你知道的設計模式?併發
大體按照模式的應用目標分類,設計模式能夠分爲建立型模式、結構型模式和行爲型模式。
建立型模式,是對對象建立過程的各類問題和解決方案的總結,包括各類工廠模式(Factory、Abstract Factory)、單例模式(Singleton)、構建器模式(Builder)、原型模式(ProtoType)。
結構型模式,是針對軟件設計結構的總結,關注於類、對象繼承、組合方式的實踐經驗。常見的結構型模式,包括橋接模式(Bridge)、適配器模式(Adapter)、裝飾者模式(Decorator)、代理模式(Proxy)、組合模式(Composite)、外觀模式(Facade)、享元模式(Flyweight)等。
行爲型模式,是從類或對象之間交互、職責劃分等角度總結的模式。比較常見的行爲型模式有策略模式(Strategy)、解釋器模式(Interpreter)、命令模式(Command)、觀察者模式(Observer)、迭代器模式(Iterator)、模板方法模式(Template Method)、訪問者模式(Visitor)。
關於設計模式就再也不擴展了,童鞋們感興趣的話,能夠對每種設計模式的使用場景進行思考總結。