學習Java基礎(chǔ)知識,打通面試關(guān)九~ConcurrentHashMap

在上一篇文章中我們說到了Map集合中的一部分內(nèi)容,還有并發(fā)包中的Map并沒有說到,現(xiàn)在我們來說下并發(fā)包中的Map~ConcurrentHashMap。

Java8之前的ConcurrentHashMap 實現(xiàn)

在前期中ConcurrentHashMap的基本實現(xiàn)思路:

  • ConcurrentHashMap 采用的是分段鎖的設(shè)計方案,只有在同一個分段內(nèi)的數(shù)據(jù)才會存在競爭關(guān)系,這就不會造成對一個Map 進行整體的鎖,根據(jù)不同的分段進行不同的鎖,在這里分段鎖被稱為Segment。在內(nèi)部實現(xiàn)的是一個Entry數(shù)組 。而每一個數(shù)組中的元素又是一個鏈表構(gòu)成。
  • 鎖在這里使用的ReentrantLock ,Segment 繼承ReentrantLock。在該Map中的HashEntry 的value以及next都被volatile修飾,這樣能保證數(shù)據(jù)的可見性。
  • ConcurrentHashMap 中并發(fā)度默認是16 但a是我們可以在構(gòu)造函數(shù)的時候進行設(shè)置。設(shè)置時該值需要設(shè)置成2的冪數(shù)值。不符合規(guī)則的數(shù)值設(shè)置 會自動的調(diào)整成符規(guī)則的設(shè)置的數(shù)值。
  • ConcurrentHashMap 也存在擴容的問題,這個跟HashMap類似,但是不是針對的整個ConcurrentHashMap,而是單獨對Segment進行擴容。也會遇到同樣的操作錯誤。
  • size 計算容易時出錯,特別是在并發(fā)實現(xiàn)的時候?qū)⒚總€Segment的大小進行相加,在相加的過程中可能還會進行數(shù)據(jù)的插入,那么得到的結(jié)果就是不準確的偏小。

Java8的ConcurrentHashMap實現(xiàn)

在實現(xiàn)上放棄的Segment 的實現(xiàn),采用了Node +CAS + Synchronized 來保證并發(fā)的安全。

  • 雖然在java8中Segment還存在,但是結(jié)構(gòu)上不再使用,采用Lazy-load的形式,這樣避免了初始化的開銷。
  • 數(shù)據(jù)可見性采用了volatile ,所慚怍采用了CAS并且部分還實現(xiàn)了無鎖的操作。
  • ConcurrentHashMap 中操作的時候key value 不能是null 這樣會出現(xiàn)操作問題。
  • 初始化方法時使用的initTable,在調(diào)用的時候進行參數(shù)的設(shè)置。主要是設(shè)置sizeCtl該屬性,如果發(fā)現(xiàn)有競爭性的初識化,那么就自旋等待恢復。
/**
     * Initializes table, using the size recorded in sizeCtl.
     */
    private final Node<K,V>[] initTable() {
        Node<K,V>[] tab; int sc;
        while ((tab = table) == null || tab.length == 0) {
                //sizeCtl表示有其他線程正在進行初始化操作,把線程掛起。對于table的初始化工作,只能有一個線程在進行。
            if ((sc = sizeCtl) < 0)
                Thread.yield(); // lost initialization race; just spin
            else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {//利用CAS方法把sizectl的值置為-1 表示本線程正在進行初始化
                try {
                    if ((tab = table) == null || tab.length == 0) {
                        int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
                        @SuppressWarnings("unchecked")
                        Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
                        table = tab = nt;
                        sc = n - (n >>> 2);//相當于0.75*n 設(shè)置一個擴容的閾值
                    }
                } finally {
                    sizeCtl = sc;
                }
                break;
            }
        }
        return tab;
    }
  • 擴容,在jdk1.7的版本中我們知道擴容容易有很大的性能問題,那么在1.8怎么解決呢?
  1. 擴容分為了兩部分,構(gòu)建新的nextTable ,容量是原先的兩倍,單線程操作。
  2. 進行數(shù)據(jù)復制到新的nextTable中,這里是多線程操作。
  3. 整體 遍歷每個節(jié)點,然后復制的過程
  • size的大小計算,這個計算在新版本中,只是一個估值。雖然計算的是很費周折。一般該值還是很穩(wěn)定的。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容