hashmap在多线程put的时候是不安全的。

验证代码

public static void main(String[] args) {
        List<String> dayList = Arrays.asList(
                "monday","tuesday","wednesday","thursday","friday", "saturday", "sunday");
        boolean flag = true;
        int count=0;
        while (flag) {
            Map<String, List<Long>> testHashMap = new HashMap<>();
            List<CompletableFuture<Void>> futures = new ArrayList<>(dayList.size());
            for (String key : dayList) {
                futures.add(getLong().thenAccept(l -> {
                    System.out.println("future put map " + key);
                    testHashMap.put(key, l);
                }));
            }
            CompletableFuture.allOf(futures.toArray(new CompletableFuture[dayList.size()]))
                    .whenComplete((aVoid, throwable) -> {
                        System.out.println("Completed allOf");
                    }).join();
            System.out.println("Joined");
            count++;
            try {
                for (String key : dayList) {
                    List<Long> resultList = new ArrayList<>();
                    List<Long> timeList = testHashMap.get(key);
                    System.out.println("count:"+ count + " "+ key + " : timeList : " + timeList);
                    resultList.addAll(timeList);
                }
                testHashMap.clear();
            } catch (Exception e) {
                e.printStackTrace();
                System.out.println( testHashMap);
                flag = false;
            }
        }
    }
    public static CompletableFuture<List<Long>> getLong() {
        return CompletableFuture.supplyAsync(() -> {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    return Arrays.asList(System.currentTimeMillis());
                }
        );
    }

验证结果

大概循环几十次之后会报空指针异常,出错频率不固定。

future put map wednesday
future put map friday
future put map monday
future put map thursday
future put map tuesday
future put map sunday
future put map saturday
Completed allOf
Joined
count:99 monday : timeList : [1665285907674]
count:99 tuesday : timeList : [1665285907674]
count:99 wednesday : timeList : [1665285907674]
count:99 thursday : timeList : [1665285907674]
count:99 friday : timeList : null
{sunday=[1665285907674], saturday=[1665285907674], tuesday=[1665285907674], wednesday=[1665285907674], thursday=[1665285907674], monday=[1665285907674]}
java.lang.NullPointerException
	at java.util.ArrayList.addAll(ArrayList.java:583)

问题原因

hashmap线程不安全,在多线程put,或者多线程put,get时操作不是原子的。

public V put(K key, V value) {
    if (key == null)
        return putForNullKey(value);

    //Operation 1       
    int hash = hash(key.hashCode());
    int i = indexFor(hash, table.length);
    for (Entry<K,V> e = table[i]; e != null; e = e.next) {
        Object k;
        if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
            V oldValue = e.value;
            e.value = value;
            e.recordAccess(this);
            return oldValue;
        }
    } 

    //Operation 2
    modCount++;

    //Operation 3
    addEntry(hash, key, value, i);
    return null;
}

As you can see put() involves 3 operations which are not synchronized. And compound operations are non thread safe. So theoretically it is proven that HashMap is not thread safe.

解决方案

使用并发包下的java.util.concurrent.ConcurrentHashMap,ConcurrentHashMap实现了更高级的线程安全; 或者使用synchronizedMap() 同步方法包装 HashMap object,得到线程安全的Map,并在此Map上进行操作

其它

一开始以为是CompletableFuture.allof().join()的问题,查了查发现不是。不过看到另一个值得注意的点: https://stackoverflow.com/questions/47418241/completablefuture-allof-not-completing-after-individual-futures

标签:

更新时间:

留下评论