1. 程式人生 > >多執行緒(十): 併發中集合ConcurrentHashMap

多執行緒(十): 併發中集合ConcurrentHashMap

public class HashTest {
    static Map<String, String> map = new HashMap<>();

    public static void main(String[] args) {
        for (int i = 0; i < 2; i++) {
            new Thread(() -> {
                String key = Thread.currentThread().getName();
                String value
= String.valueOf(new Random().nextInt()); map.put(key, value); System.out.println(key + "\t" + map.get(key)); }).start(); } } }

這裡寫圖片描述

HashMap普通的遍歷方式在遍歷時是不允許刪除的

public static void main(String[] args) {
    Map<Integer, String> hashMap = new HashMap<Integer, String>() {
        {
            put(0
, "星期一"); put(1, "星期二"); put(2, "星期三"); put(3, "星期四"); put(4, "星期五"); put(5, "星期六"); put(6, "星期日"); } }; hashMap.forEach((key, value) -> { if (key.intValue() <=4) hashMap.remove(key); }); } Exception in
thread "main" java.util.ConcurrentModificationException at java.util.HashMap.forEach(HashMap.java:1291) at com.company.example.hash.HashMapTest.main(HashMapTest.java:30)

使用迭代器方式在遍歷的時候可以刪除

public static void main(String[] args) {
    Map<Integer, String> hashMap = new HashMap<Integer, String>() {
        {
            put(0, "星期一");
            put(1, "星期二");
            put(2, "星期三");
            put(3, "星期四");
            put(4, "星期五");
            put(5, "星期六");
            put(6, "星期日");
        }
    };

    Iterator<Map.Entry<Integer, String>> iterator = hashMap.entrySet().iterator();
    while (iterator.hasNext()) {
        Map.Entry<Integer, String> entry = iterator.next();
        Integer key = entry.getKey();
        if (key.intValue() <=4) iterator.remove();
    }

    // {5=星期六, 6=星期日}
    System.out.println(hashMap);
}

ConcurrentHashMap: 使用普通的遍歷方式可以在遍歷的時候刪除操作

public static void main(String[] args) {
    Map<Integer, String> hashMap = new ConcurrentHashMap<Integer, String>() {
        {
            put(0, "星期一");
            put(1, "星期二");
            put(2, "星期三");
            put(3, "星期四");
            put(4, "星期五");
            put(5, "星期六");
            put(6, "星期日");
        }
    };

    hashMap.forEach((key, value) -> {
        if (key.intValue() <=4) hashMap.remove(key);
    });

    System.out.println(hashMap);
}

ConcurrentHashMap是併發效率更高的Map,用來替換其他執行緒安全的Map容器,比如Hashtable和Collections.synchronizedMap。

public class ConcurrentHashMap<K,V> extends AbstractMap<K,V>
    implements ConcurrentMap<K,V>, Serializable {

    public ConcurrentHashMap();
    public ConcurrentHashMap(int initialCapacity);
    public ConcurrentHashMap(Map<? extends K, ? extends V> m);

    public V get(Object key);

    public V put(K key, V value);
    // 如果key對應的value不存在,則put進去,返回null。否則不put,返回已存在的value
    public V putIfAbsent(K key, V value)

    // 如果key對應的當前值是oldValue,則替換為newValue,返回true。否則不替換,返回false
    public boolean replace(K key, V oldValue, V newValue);

    // 如果key對應的值是value,則移除K-V,返回true。否則不移除,返回false
    public V remove(Object key);
}    

ConcurrentHashMap的簡要總結:

  • public V get(Object key)不涉及到鎖,也就是說獲得物件時沒有使用鎖;

  • put、remove方法要使用鎖,但並不一定有鎖爭用,原因在於ConcurrentHashMap將快取的變數分到多個Segment,每個Segment上有一個鎖,只要多個執行緒訪問的不是一個Segment就沒有鎖爭用,就沒有堵塞,各執行緒用各自的鎖,ConcurrentHashMap預設情況下生成16個Segment,也就是允許16個執行緒併發的更新而儘量沒有鎖爭用;

  • Iterator物件的使用,不一定是和其它更新執行緒同步,獲得的物件可能是更新前的物件,ConcurrentHashMap允許一邊更新、一邊遍歷,也就是說在Iterator物件遍歷的時候,ConcurrentHashMap也可以進行remove,put操作,且遍歷的資料會隨著remove,put操作產出變化,所以希望遍歷到當前全部資料的話,要麼以ConcurrentHashMap變數為鎖進行同步(synchronized該變數),要麼使用CopiedIterator包裝iterator,使其拷貝當前集合的全部資料,但是這樣生成的iterator不可以進行remove操作。

Hashtable和ConcurrentHashMap的不同點:

  • Hashtable對get,put,remove都使用了同步操作,它的同步級別是正對Hashtable來進行同步的,也就是說如果有執行緒正在遍歷集合,其他的執行緒就暫時不能使用該集合了,這樣無疑就很容易對效能和吞吐量造成影響,從而形成單點。而ConcurrentHashMap則不同,它只對put,remove操作使用了同步操作,get操作並不影響,詳情請看以上第1,2點,當前ConcurrentHashMap這樣的做法對一些執行緒要求很嚴格的程式來說,還是有所欠缺的,對應這樣的程式來說,如果不考慮效能和吞吐量問題的話,個人覺得使用Hashtable還是比較合適的;

  • Hashtable在使用iterator遍歷的時候,如果其他執行緒,包括本執行緒對Hashtable進行了put,remove等更新操作的話,就會丟擲ConcurrentModificationException異常,但如果使用ConcurrentHashMap的話,就不用考慮這方面的問題了,詳情請看以上第3點;


ConcurrentHashMap是執行緒安全的,但是ConcurrentHashMap只能保證自己是安全的,並不能保證其它業務邏輯是執行緒安全的

public static void main(String[] args) throws InterruptedException {
    String key = "key";
    Map<String, Integer> hashMap = new ConcurrentHashMap<>();
    CountDownLatch countDownLatch = new CountDownLatch(2);

    Runnable runnable = () -> {
        for (int i = 0; i < 5; i++) {
            Integer value = hashMap.get(key);
            if (value == null) {
                hashMap.put(key, 1);
            } else {
                hashMap.put(key, value + 1);
            }
        }

        countDownLatch.countDown();
    };

     // 兩個執行緒操作同一個key,可能會出現覆蓋的現象,導致key的值小於10
    new Thread(runnable).start();
    new Thread(runnable).start();

    countDownLatch.await();
    System.out.println(hashMap);
}

這裡寫圖片描述
CAS改進版

public static void main(String[] args) throws InterruptedException {
    String key = "key";
    Map<String, Integer> hashMap = new ConcurrentHashMap<>();
    CountDownLatch countDownLatch = new CountDownLatch(2);

    Runnable runnable = () -> {
        Integer oldValue, newValue;
        for (int i = 0; i < 5; i++) {
            while (true) {
                // CAS機制:如果put失敗,繼續獲取最新的值,然後繼續嘗試新增,直到put成功
                oldValue = hashMap.get(key);
                if (oldValue == null) {
                    newValue = 1;
                    // 如果key != newValue 則 hashMap.put(key, newValue)
                    if (hashMap.putIfAbsent(key, newValue) == null) {
                        break;
                    }
                } else {
                    newValue = oldValue + 1;
                    // 如果key對應的當前值是oldValue,則替換為newValue,返回true。否則不替換,返回false
                    if (hashMap.replace(key, oldValue, newValue)) {
                        break;
                    }
                }
            }
        }

        countDownLatch.countDown();
    };

    new Thread(runnable).start();
    new Thread(runnable).start();

    countDownLatch.await();
    System.out.println(hashMap);
}

public static void main(String[] args) {
String key = “key”;
Map

public static void main(String[] args) throws InterruptedException {
    String key = "key";
    Map<String, AtomicInteger> hashMap = new ConcurrentHashMap<>();
    CountDownLatch countDownLatch = new CountDownLatch(2);

    Runnable runnable = () -> {
        AtomicInteger oldValue = null;
        for (int i = 0; i < 5; i++) {
            oldValue = hashMap.get(key);
            if (oldValue == null) {
                System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t if");
                AtomicInteger initValue = new AtomicInteger(0);
                oldValue = hashMap.putIfAbsent(key, initValue);
                if (oldValue == null) {
                    oldValue = initValue;
                }
            }

            oldValue.incrementAndGet();
        }

        countDownLatch.countDown();
    };

    new Thread(runnable).start();
    new Thread(runnable).start();

    countDownLatch.await();

    System.out.println(hashMap);
}

徹頭徹尾理解 ConcurrentHashMap: https://blog.csdn.net/justloveyou_/article/details/72783008