知了堂Java|Java基礎面試題(四)



14.并發(fā)集合和普通集合如何區(qū)別??
并發(fā)集合常見的有 ConcurrentHashMap、ConcurrentLinkedQueue、ConcurrentLinkedDeque 等。 并發(fā)集合位 于 java.util.concurrent 包下,是 jdk1.5 之后才有的,主要作者是 Doug Lea(http://baik e.baidu.com/view/3141057.htm)完成的。?
在 java 中有普通集合、同步(線程安全)的集合、并發(fā)集合。普通集合通常性能最高,但是不保證多 線程的安全性和并發(fā)的可靠性。線程安全集合僅僅是給集合添加了synchronized 同步鎖,嚴重犧牲了 性能,而且對并發(fā)的效率就更低了,并發(fā)集合則通過復雜的策略不僅保證了多線程的安全又提高的并發(fā) 時的效率。?
參考閱讀:
ConcurrentHashMap 是線程安全的 HashMap 的實現(xiàn),默認構(gòu)造同樣有 initialCapacity 和 loadFactor 屬性,不過還多了一個 concurrencyLevel 屬性,三屬性默認值分別為 16、0.75 及 16。其內(nèi)部使用鎖 分段技術,維持這鎖Segment 的數(shù)組,在 Segment 數(shù)組中又存放著 Entity[]數(shù)組,內(nèi)部 hash 算法將 數(shù)據(jù)較均勻分布在不同鎖中。
put 操作:并沒有在此方法上加上 synchronized?
首先對 key.hashcode 進行 hash 操作,得到 key 的 hash 值。
hash操作的算法和 map也不同,根據(jù)此 hash 值計算并獲取其對應的數(shù)組中的 Segment對象(繼承自 ReentrantLock),接著調(diào)用此 Segment 對象的 put 方法來完成當前操作。
ConcurrentHashMap 基于 concurrencyLevel 劃分出了多個 Segment 來對 key-value 進行存儲,從而 避免每次 put 操作都得鎖住整個數(shù)組。在默認的情況下,最佳情況下可允許 16 個線程并發(fā)無阻塞的操 作集合對象,盡可能地減少并發(fā)時的阻塞現(xiàn)象。
get(key)操作:
首先對 key.hashCode 進行 hash 操作,基于其值找到對應的 Segment 對象,調(diào)用其 get 方法完成當 前操作。而 Segment 的 get 操作首先通過 hash 值和對象數(shù)組大小減 1 的值進行按位與操作來獲取數(shù) 組上對應位置的HashEntry。在這個步驟中,可能會因為對象數(shù)組大小的改變,以及數(shù)組上對應位置的 HashEntry 產(chǎn)生不一致性,那么 ConcurrentHashMap 是如何保證的?
對象數(shù)組大小的改變只有在 put 操作時有可能發(fā)生,由于 HashEntry 對象數(shù)組對應的變量是 volatile 類 型的,因此可以保證如 HashEntry 對象數(shù)組大小發(fā)生改變,讀操作可看到最新的對象數(shù)組大小。
在獲取到了 HashEntry 對象后,怎么能保證它及其 next 屬性構(gòu)成的鏈表上的對象不會改變呢?這點 ConcurrentHashMap 采用了一個簡單的方式,即 HashEntry 對象中的 hash、key、next 屬性都是 final 的,這也就意味著沒辦法插入一個 HashEntry 對象到基于 next 屬性構(gòu)成的鏈表中間或末尾。這樣 就可以保證當獲取到 HashEntry對象后,其基于 next 屬性構(gòu)建的鏈表是不會發(fā)生變化的。
ConcurrentHashMap 默認情況下采用將數(shù)據(jù)分為 16 個段進行存儲,并且 16 個段分別持有各自不同的 鎖Segment,鎖僅用于 put 和 remove 等改變集合對象的操作,基于 volatile 及 HashEntry 鏈表的不 變性實現(xiàn)了讀取的不加鎖。這些方式使得ConcurrentHashMap 能夠保持極好的并發(fā)支持,尤其是對于 讀遠比插入和刪除頻繁的 Map而言,而它采用的這些方法也可謂是對于 Java 內(nèi)存模型、并發(fā)機制深刻掌握的體現(xiàn)
15.動靜態(tài)代理的區(qū)別,什么場景使用?
靜態(tài)代理通常只代理一個類,動態(tài)代理是代理一個接口下的多個實現(xiàn)類。?
靜態(tài)代理事先知道要代理的是什么,而動態(tài)代理不知道要代理什么東西,只有在運行時才知道。?
動態(tài)代理是實現(xiàn) JDK 里的 InvocationHandler 接口的 invoke 方法,但注意的是代理的是接口,也就是 你的業(yè)務類必須要實現(xiàn)接口,通過 Proxy 里的 newProxyInstance 得到代理對象。?
還有一種動態(tài)代理 CGLIB,代理的是類,不需要業(yè)務類繼承接口,通過派生的子類來實現(xiàn)代理。通過在 運行時,動態(tài)修改字節(jié)碼達到修改類的目的。?
AOP 編程就是基于動態(tài)代理實現(xiàn)的,比如著名的 Spring 框架、Hibernate 框架等等都是動態(tài)代理的使用例子。
16.寫一個 ArrayList 的動態(tài)代理類的代碼
今天的分享就到這里啦,持續(xù)關注我們,學習更多Java干貨知識。?