1. 程式人生 > >如何使用執行緒安全的HashMap

如何使用執行緒安全的HashMap

HashMap為什麼執行緒不安全

導致HashMap執行緒不安全的原因可能有兩種:

1、當多個執行緒同時使用put方法新增元素的時候,正巧存在兩個put的key發生了碰撞(根據hash值計算的bucket一樣),那麼根據HashMap的儲存原理,這兩個key會新增多陣列的同一個位置,這樣一定會導致其中一個執行緒put的資料被覆蓋丟失

2、當多個執行緒同時檢測到元素個數超過雜湊表的size*loadFloat的時候,這樣會發生多個執行緒同時對Node陣列進行擴容的操作(java1.8中HashMap使用Node實體來存放內容),都在重新計算元素位置以及拷貝資料,但最後只能有一個執行緒能成功的將擴容後的陣列賦值給table,也就是說其他執行緒的都會丟失,並且各自執行緒的資料也會丟失。關於hashMap執行緒不安全這一點,《java併發程式設計的藝術》一書中是這麼描述的:“hashMap在併發執行put操作會引起死迴圈,導致CPU利用率接近100%。因為多執行緒會導致HashMap的Node連結串列形成環資料結構,一旦形成環資料結構,Node的next節點永遠不為空,就會在獲取Node時產生死迴圈”。由此可知,HashMap的死迴圈是發生在擴容時。關於hashMap的死迴圈可參看一下文章:

何如使用執行緒安全的HashMap
實現執行緒安全的方式有三種,分別是使用HashTable、Collections.SynchronizeMap、ConcurrentHashMap。

我們先分別來看看三種資料結構的部分原始碼:

hashtable

   public synchronized V get(Object key) {
        Entry<?,?> tab[] = table;
        int hash = key.hashCode();
        int index = (hash & 0x7FFFFFFF) % tab.length;
        for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) {
            if ((e.hash == hash) && e.key.equals(key)) {
                return (V)e.value;
            }
        }
        return null;
    }
public synchronized V put(K key, V value) { // Make sure the value is not null if (value == null) { throw new NullPointerException(); } // Makes sure the key is not already in the hashtable. Entry<?,?> tab[] = table; int hash = key.hashCode(); int index = (hash & 0x7FFFFFFF) % tab.length; @SuppressWarnings("unchecked") Entry<K,V> entry = (Entry<K,V>)tab[index]; for(; entry != null ; entry = entry.next) { if ((entry.hash == hash) && entry.key.equals(key)) { V old = entry.value; entry.value = value; return old; } } addEntry(hash, key, value, index); return null; } publicsynchronized
V remove(Object key) { Entry<?,?> tab[] = table; int hash = key.hashCode(); int index = (hash & 0x7FFFFFFF) % tab.length; @SuppressWarnings("unchecked") Entry<K,V> e = (Entry<K,V>)tab[index]; for(Entry<K,V> prev = null ; e != null ; prev = e, e = e.next) { if ((e.hash == hash) && e.key.equals(key)) { modCount++; if (prev != null) { prev.next = e.next; } else { tab[index] = e.next; } count--; V oldValue = e.value; e.value = null; return oldValue; } } return null; }

SynchronizeMap

        public V put(K key, V value) {
            synchronized (mutex) {return m.put(key, value);}
        }
        public V remove(Object key) {
            synchronized (mutex) {return m.remove(key);}
        }
        public void putAll(Map<? extends K, ? extends V> map) {
            synchronized (mutex) {m.putAll(map);}
        }
        public void clear() {
            synchronized (mutex) {m.clear();}
        }

通過這部分原始碼可知,HashTable、Collections.SynchronizeMap都是通過對讀寫進行加鎖操作來保證執行緒的安全,一個執行緒進行讀寫資料,其餘執行緒等等,可想而知,效能可想而知。

ConcurrentHashMap

在java8中,ConcurrentHashMap摒棄了Segment,啟用了一種新的演算法實現CAS。關於ConcurrentHashMap工作機制請參考深入淺出ConcurrentHashMap1.8

下面我們通過一些程式碼來驗證他們之前的執行緒安全以及效率問題:

package com.iresearch.idata.common.util.demo;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutionException;

/**
 * @author: htc
 * @date: Created in 18:18 2017/12/20.
 */
public class ConcurrentMapWithMap {
    private static Map<String, Long> mapWordCounts = new HashMap<>();
    private static ConcurrentMap<String, Long> concurrentMapWordCounts = new ConcurrentHashMap<>();
    public static Logger logger = LoggerFactory.getLogger(ConcurrentMapWithMap.class);
    public static int count = 0;

    public static long mapIncrease(String word) {
        Long oldValue = mapWordCounts.get(word);
        Long newValue = (oldValue == null) ? 1L : oldValue + 1;
        mapWordCounts.put(word, newValue);
        return newValue;
    }


    public static long ConcurrentMapIncrease(String word) {
        Long oldValue, newValue;
        while (true) {
            oldValue = concurrentMapWordCounts.get(word);
            if (oldValue == null) {
                // Add the word firstly, initial the value as 1
                newValue = 1L;
                if (concurrentMapWordCounts.putIfAbsent(word, newValue) == null) {
                    break;
                }
            } else {
                newValue = oldValue + 1;
                if (concurrentMapWordCounts.replace(word, oldValue, newValue)) {
                    break;
                }
            }
        }
        return newValue;
    }

    public static void mapWordCount() throws InterruptedException, ExecutionException {
        new Thread(new Runnable() {
            @Override
            public void run() {
                int count = 0;
                while (count++ < 10000) {
                    logger.info("mapIncrease num is " + ConcurrentMapWithMap.mapIncrease("work"));
                }
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                int count = 0;
                while (count++ < 10000) {
                    logger.info("mapIncrease num is " + ConcurrentMapWithMap.mapIncrease("work"));
                }
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                int count = 0;
                while (count++ < 10000) {
                    logger.info("mapIncrease num is " + ConcurrentMapWithMap.mapIncrease("work"));
                }
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                int count = 0;
                while (count++ < 10000) {
                    logger.info("mapIncrease num is " + ConcurrentMapWithMap.mapIncrease("work"));
                }
            }
        }).start();
    }

    public static void concurrentWordCount() throws InterruptedException, ExecutionException {
        new Thread(new Runnable() {
            @Override
            public void run() {
                int count = 0;
                while (count++ < 10000) {
                    logger.info("mapIncrease num is " + ConcurrentMapWithMap.ConcurrentMapIncrease("work"));
                }
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                int count = 0;
                while (count++ < 10000) {
                    logger.info("mapIncrease num is " + ConcurrentMapWithMap.ConcurrentMapIncrease("work"));
                }
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                int count = 0;
                while (count++ < 10000) {
                    logger.info("mapIncrease num is " + ConcurrentMapWithMap.ConcurrentMapIncrease("work"));
                }
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                int count = 0;
                while (count++ < 10000) {
                    logger.info("mapIncrease num is " + ConcurrentMapWithMap.ConcurrentMapIncrease("work"));
                }
            }
        }).start();
    }

    public static void main(String[] args) throws InterruptedException, ExecutionException {
        ConcurrentMapWithMap.mapWordCount();
        Thread.sleep(10000);
        //多執行緒累加,每次都少於40000,故執行緒不安全
        logger.info("final count map" + ConcurrentMapWithMap.mapWordCounts.get("work"));
        ConcurrentMapWithMap.concurrentWordCount();
        Thread.sleep(10000);
        //多執行緒累加,每次都是40000
        logger.info("final count concurrentMap" + ConcurrentMapWithMap.concurrentMapWordCounts.get("work"));
    }
}


相關推薦

關於hashmap和hashtable的區別,及如何使hashmap變得執行安全?(除了synchronized)---concurrentHashmap

我們都知道hashmap是執行緒不安全的,而效率也比較高,他允許我們存入null鍵及null值; 而 hashtable 是執行緒安全的,其效率比較低,不允許我們存入null鍵和null值; 除了非同步及允許使用null值,hashmap與hashtable基本相同; 那麼為什麼hash

1000-ms-HashMap 執行安全安全問題

問題: HashMap是否是執行緒安全 詳解 http://www.importnew.com/21396.html 有原始碼分析 和程式碼效能比較 CHM效能最好 HashMap不是執行緒安全的;Hashtable執行緒安全,但效率低,因為是Hashtable是使用synchronized的,所有執行緒

java中為什麼Hashtable是執行安全的,而HashMap執行安全的?還有ArrayList為什麼是執行安全的,Vector是執行安全的??

文章目錄 一、HashMap解析 二、Hashtable解析 三、Collections.synchronizedMap()解析 四、ConcurrentHashMap 六、ArrayList為什麼是執行緒不安全的,Vector是執行緒安全的?

HashMap執行安全問題

主要集中在put操作和map擴容resize方法上,這2個方法都沒有執行緒同步 1、當多個執行緒向map put時,如果有2個或以上的key(來自不同執行緒)的hash結果一樣(hash碰撞),則只有一個執行緒能put成功,其他執行緒資料丟失 2、map在超過內建的尺寸範圍,則會呼叫resi

併發下HashMap為什麼不是執行安全的?

首先看下HashMap的工作原理,我們回顧一下HashMap的結構: HashMap的結構就是雜湊表,底層是一個數組,這個陣列中儘可能地分散所有的key,通過key的hash值得到陣列下標,然後把entry插到該陣列

2種辦法讓HashMap執行安全

HashMap不是執行緒安全的,往往在寫程式時需要通過一些方法來回避.其實JDK原生的提供了2種方法讓HashMap支援執行緒安全. 方法一:通過Collections.synchronizedMap()返回一個新的Map,這個新的map就是執行緒安全的. 這

Collection、ArrayList、HashMap、HashSet轉為執行安全(集合的安全性問題)

最近在看bugly的是否,發現二維碼掃描程式碼中有一段報錯了,執行緒不安全問題.   裡面有段 new HashSet() 程式碼,這個HashSet,底層是採用HashMap來實現的,執行緒不是安全的, 所以有時候會有執行緒不安全的問題產生. 前言:Collecti

2.hashMap如何保證執行安全

一:hashMap執行緒不安全表現 (1)在兩個執行緒同時嘗試擴容HashMap時,可能將一個連結串列形成環形的連結串列,所有的next都不為空,進入死迴圈; (2)在兩個執行緒同時進行put時可能造成一個執行緒資料的丟失; 二:如何執行緒安全的使用hashMap (

HashMap為什麼不是執行安全的?

HashMap底層是一個Entry陣列,當發生hash衝突的時候,hashmap是採用連結串列的方式來解決的,在對應的陣列位置存放連結串列的頭結點。對連結串列而言,新加入的節點會從頭結點加入。 javadoc中關於hashmap的一段描述如下: 此實現不是同步的。如果多個執行緒同時訪

8. 造成HashMap執行安全的原因

在前面我的一篇總結(6. 執行緒範圍內共享資料)文章中提到,為了資料能線上程範圍內使用,我用了 HashMap 來儲存不同執行緒中的資料,key 為當前執行緒,value 為當前執行緒中的資料。我取的時候根據當前執行緒名從 HashMap 中取即可。 因為當初學習 HashMap 和 HashTable 原

造成HashMap執行安全的原因

我們知道 HashMap 底層是一個 Entry 陣列,當發生 hash 衝突的時候,HashMap 是採用連結串列的方式來解決的,在對應的陣列位置存放連結串列的頭結點。對連結串列而言,新加入的節點會從頭結點加入。javadoc 中有一段關於 HashMap 的描述: 此實現不是同步的。如果多個執

ArrayList和Vector的區別,HashMap和Hashtable的區別以及執行安全的理解

就ArrayList與Vector主要從二方面來說. 一.同步性:Vector是執行緒安全的,也就是說是同步的,而ArrayList是執行緒序不安全的,不是同步的 二.資料增長:當需要增長時,Vector預設增長為原來一培,而ArrayList卻是原來的

一次HashMap執行安全引起的事故

事故分析 最近一次web工程上線,上線大概半個小時,出現了報警,16核的伺服器的cpu使用了1123%,程式出異常了。 Cpu利用率過高一般是因為出現了出現了死迴圈,導致部分執行緒一直執行。佔用cpu時間。使用jstack工具dump出問題的那臺伺服器的棧資訊。死迴圈的話

如何使用執行安全HashMap

HashMap為什麼執行緒不安全 導致HashMap執行緒不安全的原因可能有兩種: 1、當多個執行緒同時使用put方法新增元素的時候,正巧存在兩個put的key發生了碰撞(根據hash值計算的bucket一樣),那麼根據HashMap的儲存原理,這兩個key會新增多陣列的同

ArrayList和Vector的區別,HashMap和Hashtable的區別以及執行安全的理解【轉】

http://www.cnblogs.com/xionglee/articles/1554701.html 就ArrayList與Vector主要從二方面來說. 一.同步性:Vector是執行緒安全的,也就是說是同步的,而ArrayList是執行緒序不安全的,不是同步的 二.資料增長:當需要增長時,Ve

Java集合框架之十三-------------HashMap的擴容與執行安全問題

HashMap擴容中,hash & oldCap的作用,觀察擴容前和擴容後下標的變化 原來的0101和10101在length=16的時候,通過hash&length-1的方法,計算出來都是0101;但是在擴容後即length=32時,hash&

【java併發】造成HashMap執行安全的原因

0. 寫在前面   在前面我的一篇總結執行緒範圍內共享資料文章中提到,為了資料能線上程範圍內使用,我用了HashMap來儲存不同執行緒中的資料,key為當前執行緒,value為當前執行緒中的資料。

執行安全HashMap執行安全的ConcurrentHashMap

在平時開發中,我們經常採用HashMap來作為本地快取的一種實現方式,將一些如系統變數等資料量比較少的引數儲存在HashMap中,並將其作 為單例類的一個屬性。在系統執行中,使用到這些快取資料,都可以直接從該單例中獲取該屬性集合。但是,最近發現,HashMap並不是執行緒

Hashmap執行安全的嗎?為什麼?有哪些執行安全的容器?hashmaphashmap的區別?

Hashmap是執行緒安全的嗎?為什麼?   不是,多執行緒呼叫的情況下擴容會出問題。   有哪些執行

C/C++程式設計教訓----函式內靜態類物件初始化非執行安全(C++11之前)

不少程式設計師在編寫程式的時候,會使用函式內靜態(static)變數,既能滿足函式內這個變數可以持久的記錄某些資訊,又使其訪問範圍的控制侷限於函式內。但函式內靜態類物件初始化是非執行緒安全的。 問題背景 在我們產品中對log4cxx做了一些簡單的封裝 (採用VS2005編譯),其中會