如何验证HashMap是非线程安全的

418 阅读1分钟

我们知道HashMap是非线程安全的类,那线程安全和非线程安全使用时实际效果到底有什么区别呢?我们可以通过一个代码片段来实际测试验证一下。测试方法:使用四个线程并发地分别向map中放入1000个数据项,然后检查最终的size是否为4000来验证线程安全与否。

map size最终结果会是多少呢?

import java.util.*;
import java.util.concurrent.CountDownLatch;
public class MyMapTest {
    public static void main(String[] args) {
        Map<Integer, Integer> map = new HashMap<>();
        CountDownLatch countDownLatch = new CountDownLatch(4);
        new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                map.put(i, i);
            }
            countDownLatch.countDown();
        }).start();

        new Thread(() -> {
            for (int i = 1000; i < 2000; i++) {
                map.put(i, i);
            }
            countDownLatch.countDown();
        }).start();

        new Thread(() -> {
            for (int i = 2000; i < 3000; i++) {
                map.put(i, i);
            }
            countDownLatch.countDown();
        }).start();

        new Thread(() -> {
            for (int i = 3000; i < 4000; i++) {
                map.put(i, i);
            }
            countDownLatch.countDown();
        }).start();

        try {
            countDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //map size将小于4000
        System.out.println("map size:" + map.size());
    }
}

上面代码实际运行起来后会发现size小于4000,而且小很多,可能只有2000多。

如何做到map size 一定为4000呢?

  1. 使用 Collections.synchronizedMap(map),包装成同步Map,原理就是在HashMap的所有方法上synchronized
Map<Integer, Integer> map = Collections.synchronizedMap(new HashMap<>());
  1. 将HashMap换成线程安全的类如:ConcurrentHashMap
Map<Integer, Integer> map = new ConcurrentHashMap<>();
  1. map.put() 时做同步操作。
import java.util.*;
import java.util.concurrent.CountDownLatch;

public class MyMapTest {
    public static void main(String[] args) {
        Map<Integer, Integer> map = new HashMap<>();
        CountDownLatch countDownLatch = new CountDownLatch(4);
        //对象锁
        final Object object = new Object();
        new Thread(() -> {
            synchronized (object) {
                for (int i = 0; i < 1000; i++) {
                    map.put(i, i);
                }
            }
            countDownLatch.countDown();
        }).start();

        new Thread(() -> {
            synchronized (object) {
                for (int i = 1000; i < 2000; i++) {
                    map.put(i, i);
                }
            }
            countDownLatch.countDown();
        }).start();

        new Thread(() -> {
            synchronized (object) {
                for (int i = 2000; i < 3000; i++) {
                    map.put(i, i);
                }
            }
            countDownLatch.countDown();
        }).start();

        new Thread(() -> {
            synchronized (object) {
                for (int i = 3000; i < 4000; i++) {
                    map.put(i, i);
                }
            }
            countDownLatch.countDown();
        }).start();

        try {
            countDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //这里map size将一定等于4000
        System.out.println("map size:" + map.size());
    }
}

HashMap一定是非线程安全的吗?

其实,如果一开始只有一个线程往HashMap中存放数据,后续有多个读线程从HashMap中取数据,而不会再写入新的数据,是不会有线程安全的问题的。读操作只是读取数据,不会对内部共享变量做更改,相当于这个map是不可变的,所以不会有线程安全问题。