如何使用线程安全的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; } publicsynchronizedV 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"));
    }
}











  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
线程安全HashMap通常使用锁或者并发数据结构来实现。以下是两种常见的实现方式: 1. 使用实现线程安全HashMap 在单线程环境下,HashMap是非常高效的数据结构。但是在多线程环境下,由于线程之间的竞争,可能会导致HashMap出现错误或者不一致的状态。为了解决这个问题,可以使用锁来保证线程安全。常见的实现方式包括: - 使用synchronized关键字来保护HashMap的读写操作。这种方式比较简单,但是会导致性能下降,因为每个线程都需要获得锁才能进行读写操作。 - 使用ConcurrentHashMap代替HashMap。ConcurrentHashMap是Java并发包提供的高性能线程安全Map实现。它利用分段锁来实现线程安全,可以同时支持多个读操作和少量写操作,因此在高并发环境下性能比较好。 2. 使用并发数据结构实现线程安全HashMap 除了使用锁,还可以使用Java并发包提供的一些并发数据结构来实现线程安全HashMap。例如: - ConcurrentHashMap。这是Java并发包提供的高性能线程安全Map实现,它利用分段锁来实现线程安全,可以同时支持多个读操作和少量写操作,因此在高并发环境下性能比较好。 - ConcurrentSkipListMap。这是Java并发包提供的高性能线程安全有序Map实现,它使用跳表来实现数据结构,可以支持快速的查找、插入和删除操作,而且可以保证数据的有序性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值