1. 程式人生 > >JAVA面試題05-ConcurrentHashMap的實現

JAVA面試題05-ConcurrentHashMap的實現

1.HashMap是執行緒安全的麼?併發環境下有什麼替代方案?

1.1 HashMap不是執行緒安全。
1.2 併發環境下替代方案有HashTable(所有方法加synchronized), ConcurrentHashMap(用分段鎖實現執行緒安全)。

2.ConcurrentHashMap是如何實現執行緒安全的?

2.1 JDK1.7中是用Segment(extends ReentrantLock)來實現。

    /**
     * The segments, each of which is a specialized hash table.
     */
    final
Segment<K,V>[] segments;//ConcurrentHashMap 有一個Segment陣列,也就是說他裡面有很多鎖

來看看Segment是何方聖神

    //原來就是一把鎖
    static final class Segment<K,V> extends ReentrantLock implements Serializable {


        /**
         * The per-segment table. Elements are accessed via
         * entryAt/setEntryAt providing volatile semantics.
         */
transient volatile HashEntry<K,V>[] table;//這裡的結構就和HashMap 差不多了

來看看怎麼put的

    public V put(K key, V value) {
        Segment<K,V> s;
        if (value == null)
            throw new NullPointerException();
        int hash = hash(key);
        int j = (hash >>> segmentShift) & segmentMask;
        if
((s = (Segment<K,V>)UNSAFE.getObject // nonvolatile; recheck (segments, (j << SSHIFT) + SBASE)) == null) // in ensureSegment s = ensureSegment(j); return s.put(key, hash, value, false);//哈哈,原來是根據hash獲取到元素要放在哪個Segment中,然後呼叫了Segment的put方法 }

那看看Segment的put是什麼鬼

final V put(K key, int hash, V value, boolean onlyIfAbsent) {
            HashEntry<K,V> node = tryLock() ? null ://重點 先tryLock獲取鎖
                scanAndLockForPut(key, hash, value);
            V oldValue;
            try {
                HashEntry<K,V>[] tab = table;
                int index = (tab.length - 1) & hash;
                HashEntry<K,V> first = entryAt(tab, index);
                for (HashEntry<K,V> e = first;;) {
                    if (e != null) {
                        K k;
                        if ((k = e.key) == key ||
                            (e.hash == hash && key.equals(k))) {
                            oldValue = e.value;
                            if (!onlyIfAbsent) {
                                e.value = value;
                                ++modCount;
                            }
                            break;
                        }
                        e = e.next;
                    }
                    else {
                        if (node != null)
                            node.setNext(first);
                        else
                            node = new HashEntry<K,V>(hash, key, value, first);
                        int c = count + 1;
                        if (c > threshold && tab.length < MAXIMUM_CAPACITY)
                            rehash(node);
                        else
                            setEntryAt(tab, index, node);
                        ++modCount;
                        count = c;
                        oldValue = null;
                        break;
                    }
                }
            } finally {
                unlock();//重點 釋放鎖
            }
            return oldValue;
        }

好了,總結一下

JDK1.7中ConcurrentHashMap是用一個二維陣列連結串列來實現的。其實就是一個Segment的陣列,每個Segment又是一個數組連結串列,而且是一個鎖,每次對同一個Segment中元素進行寫操作的時候,會鎖住整個Segment。
那和HashTable比較一下,有什麼好處呢?
好處就是,ConcurrentHashMap寫操作只會鎖一段(鎖住Segment中所有元素),對不同Segment元素的操作不會互相阻塞,而HashTable用的是synchronized,會鎖住整個物件,相當於一個HashTable上的操作都是並行的,連get方法都會阻塞其他操作。
換個說法吧,一個HashTable只有一把鎖,最多隻有一個執行緒獲取到鎖。
ConcurrentHashMap有很多把鎖(比如16),那麼此時最多支援16個併發(一個併發一把鎖,人人有份,不用搶),當然了,最理想的場景是16個併發操作的Segment都不一樣。

2.2 JDK1.8如何實現執行緒安全的

改進一:取消segments欄位,直接採用transient volatile HashEntry<K,V>[] table儲存資料,採用table陣列元素作為鎖,從而實現了對每一行資料進行加鎖,進一步減少併發衝突的概率。

改進二:將原先table陣列+單向連結串列的資料結構,變更為table陣列+單向連結串列+紅黑樹的結構。對於hash表來說,最核心的能力在於將key hash之後能均勻的分佈在陣列中。如果hash之後雜湊的很均勻,那麼table陣列中的每個佇列長度主要為0或者1。但實際情況並非總是如此理想,雖然ConcurrentHashMap類預設的載入因子為0.75,但是在資料量過大或者運氣不佳的情況下,還是會存在一些佇列長度過長的情況,如果還是採用單向列表方式,那麼查詢某個節點的時間複雜度為O(n);因此,對於個數超過8(預設值)的列表,jdk1.8中採用了紅黑樹的結構,那麼查詢的時間複雜度可以降低到O(logN),可以改進效能。

為了說明以上2個改動,看一下put操作是如何實現的。

final V putVal(K key, V value, boolean onlyIfAbsent) {
    if (key == null || value == null) throw new NullPointerException();
    int hash = spread(key.hashCode());
    int binCount = 0;
    for (Node<K,V>[] tab = table;;) {
        Node<K,V> f; int n, i, fh;
        // 如果table為空,初始化;否則,根據hash值計算得到陣列索引i,如果tab[i]為空,直接新建節點Node即可。注:tab[i]實質為連結串列或者紅黑樹的首節點。
        if (tab == null || (n = tab.length) == 0)
            tab = initTable();
        else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
            if (casTabAt(tab, i, null,
                         new Node<K,V>(hash, key, value, null)))
                break;                   // no lock when adding to empty bin
        }
        // 如果tab[i]不為空並且hash值為MOVED,說明該連結串列正在進行transfer操作,返回擴容完成後的table。
        else if ((fh = f.hash) == MOVED)
            tab = helpTransfer(tab, f);
        else {
            V oldVal = null;
            // 針對首個節點進行加鎖操作,而不是segment,進一步減少執行緒衝突
            synchronized (f) {
                if (tabAt(tab, i) == f) {
                    if (fh >= 0) {
                        binCount = 1;
                        for (Node<K,V> e = f;; ++binCount) {
                            K ek;
                            // 如果在連結串列中找到值為key的節點e,直接設定e.val = value即可。
                            if (e.hash == hash &&
                                ((ek = e.key) == key ||
                                 (ek != null && key.equals(ek)))) {
                                oldVal = e.val;
                                if (!onlyIfAbsent)
                                    e.val = value;
                                break;
                            }
                            // 如果沒有找到值為key的節點,直接新建Node並加入連結串列即可。
                            Node<K,V> pred = e;
                            if ((e = e.next) == null) {
                                pred.next = new Node<K,V>(hash, key,
                                                          value, null);
                                break;
                            }
                        }
                    }
                    // 如果首節點為TreeBin型別,說明為紅黑樹結構,執行putTreeVal操作。
                    else if (f instanceof TreeBin) {
                        Node<K,V> p;
                        binCount = 2;
                        if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
                                                       value)) != null) {
                            oldVal = p.val;
                            if (!onlyIfAbsent)
                                p.val = value;
                        }
                    }
                }
            }
            if (binCount != 0) {
                // 如果節點數>=8,那麼轉換連結串列結構為紅黑樹結構。
                if (binCount >= TREEIFY_THRESHOLD)
                    treeifyBin(tab, i);
                if (oldVal != null)
                    return oldVal;
                break;
            }
        }
    }
    // 計數增加1,有可能觸發transfer操作(擴容)。
    addCount(1L, binCount);
    return null;
}



時間過得真快,不寫這個部落格,還不知道1.8改動這麼大,逝者如斯夫,不捨晝夜,吾將上下而求索。

相關推薦

JAVA試題05-ConcurrentHashMap實現

1.HashMap是執行緒安全的麼?併發環境下有什麼替代方案? 1.1 HashMap不是執行緒安全。 1.2 併發環境下替代方案有HashTable(所有方法加synchronized), ConcurrentHashMap(用分段鎖實現執行緒安全)。

java試題05

error .config prototype finall get方法 all ssh error: com 1.寫一個冒泡排序的算法 升序排列: int[] nums = {5,6,9,10,20,30,28,27,15}; for(int i = 0;i<n

《阿里巴巴Java Spring Boot 2.0開發實戰課程》05課:三層MVC網站與架構分層誤區、Java試題

《阿里巴巴Java Spring Boot 2.0開發實戰課程》05課本期分享專家:徐雷—阿里特邀Java講師,MongoDB講師 本期分享主題:三層架構MVC網站與分層架構誤區、Java面試題 國內系統架構設計的文章和書籍。經常會搞錯分層的概念,本課程進行了講解。還有關於model概念的解析,以及Jav

Java試題 實現單例模式

package com.coderli.interview; /** * 常見面試題:實現單例模式 <br> * 《劍指offer》,第二章,面試題2 <br> * 這裡給出五種寫法和對應的評論 * * @author lihzh * @date 2014年8月12日 上午11:10

Java試題】spring+springMVC+mybatis原理及實現機制(持續更新)

本文將持續更新,主要講解SSM框架的底層原理和實現機制等 1.什麼是IOC? IOC即Inverse of Control,它包括兩個內容:控制與反轉 那到底什麼東西的“控制”被“反轉”了呢?對於軟體而言,即是某一個介面具體實現類的選擇控制權從呼叫類中移除,轉交給第三

Anonymous Inner Class(匿名內部類)是否可以繼承其它類?是否可以實現介面?【Java試題

回答:匿名內部類在實現時必須藉助一個藉口或者一個抽象類或者一個普通類來構造,從這過層次上講匿名內部類是實現了介面或者繼承了類,但是不能通過extends或implement關鍵詞來繼承類或實現介面。

Java試題】之分頁功能的實現

以下內容是根據網上內容以及傳智播客教學整理而來,侵刪。 分頁的實現可分為兩大類:一、資料在Java程式碼中進行分頁,然後取得當前頁資料;二、在資料庫中直接取得當前頁資料。通常面試官都希望聽到後者,因為那才是高效的方法。你如果想讓面試官覺得你的能力高的話你就先否定他的問

java試題java中的單例設計模式及兩種實現方法的程式碼舉例

java面試時經常會問到關於單例設計模式,因為它能考察的知識點較多且在開發中經常用到。那我就來說一說我對於單例設計模式的一些淺見。首先,在Java中,什麼是單例呢?就是保證類在記憶體中只有一個物件。那麼

JAVA試題04-HashMap的實現

1.HashMap基於什麼樣的資料結構實現的? 答:從結構實現來講,HashMap是陣列+連結串列+紅黑樹(JDK1.8增加了紅黑樹部分)實現的,如下如所示。 2.HashMap put一個元素的過程。 2.1.根據key計算hashcode 2.2

一道Java試題實現複製、刪除、剪下檔案(資料夾的實現)的snippet

Java面試題是叫寫出檔案複製、刪除和剪下的,所以昨天晚上就花了大概一個小時寫出這個完整的snippet(當然如果只要思路的話就好辦了)。 這個snippet中的刪除檔案(資料夾)部分有點意思,一般在Windows下目錄過長的話,目錄就刪除不了。而這個程式可以解決這個因

【劍指offer Java試題2:實現Singleton模式

題目:設計一個類,我們只能生成該類的一個例項。 //餓漢式 public static class Singleton01{ //預先初始化static變數 private final static Singleton01

java試題:陣列的常用演算法實現

package com.bxh.array; public class ArrayTest { private static int max(int m,int n) { return m>n?m:n; } private static int min(

阿裏JAVA試題剖析:一般實現分布式鎖都有哪些方式?使用 Redis 如何設計分布式鎖?

自己 ini 單位 nts ast 客戶端 this 失敗 獲取 面試原題 一般實現分布式鎖都有哪些方式?使用 redis 如何設計分布式鎖?使用 zk 來設計分布式鎖可以嗎?這兩種分布式鎖的實現方式哪種效率比較高? 面試官心理分析 其實一般問問題,都是這麽問的,先問問你

JAVA試題 手寫ArrayList的實現,在筆試中過關斬將?

面試官Q1:可以手寫一個ArrayList的簡單實現嗎? 我們都知道ArrayList是基於陣列實現,如果讓你實現JDK原始碼ArrayList中add()、remove()、get()方法,你知道如何實現嗎?這一節,我們不看原始碼,我們想想如何簡單的實現ArrayList幾個基本方法?  

Java試題 從原始碼角度分析HashSet實現原理?

面試官:請問HashSet有哪些特點? 應聘者:HashSet實現自set介面,set集合中元素無序且不能重複; 面試官:那麼HashSet 如何保證元素不重複? 應聘者:因為HashSet底層是基於HashMap實現的,當你new一個HashSet時候,實際上是new了一個map,執行add方法時,實

Java試題和解答(三)

增加 自旋 println class 答案 logs 聯網 get link 1、這段代碼大多數情況下運行正常,但是某些情況下會出問題。什麽時候會出現什麽問題?如何修正? public class MyStack { private List<S

java試題

調用 strong 同步鎖 記錄 沒有 拋出異常 數據 sleep wait sleep()和wait()的區別  sleep是線程類的方法,它會讓出cpu去執行其他線程,當指定時間過後,會從新回到此線程上,但是雖然讓出了CPU ,並不會釋放對象鎖,   wait是obje

java試題

情況 減少 元素 pro pin 內存大小 java_opts req -xms 個人的一點參考總結,如有雷同,純屬巧合! 1、hashmap的實現原理以及hashtable的線程安全是怎麽實現的?HashMap其實也是一個線性的數組實現的,所以可以理解為其存儲數據的容

java試題-java基礎

runtime 都是 缺點 子類 true 大數 virtual 過程 面向連接 1.1java與其他語言相比,有什麽優點和缺點?   首先,java與c、c++相比,java是一種完全的面對對象的語言,雖然他的底層(運行時庫)使用c語言開發的,可是並不依賴於c,因為jav

Java(試題):字符串截取

int lan out 試題 void trace 題目 replace odi 在Java中,字符串“abcd”與字符串“ab你好”的長度是一樣,都是四個字符。 但對應的字節數不同,一個漢字占兩個字節。 定義一個方法,按照指定的字節數來取子串。 如:對於“ab你好”,如果