现在还不知道hashmap不安全怎么解决?

86 阅读1分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第8天,点击查看活动详情

为什么要使用ConcurrentHashMap:

  1. HashMap线程不安全,会导致数据错乱
  2. 使用线程安全的Hashtable效率低下

基于以上两个原因,便有了ConcurrentHashMap的登场机会。

  • HashMap线程不安全演示。

公有、静态的集合:

public class Const {
    public static HashMap<String,String> map = new HashMap<>();
}

线程,向map中写入数据:

public void run() {
        for (int i = 0; i < 500000; i++) {
            Const.map.put(this.getName() + (i + 1), this.getName() + i + 1);
        }
        System.out.println(this.getName() + " 结束!");
    }

测试类:

public class Demo {
    public static void main(String[] args) throws InterruptedException {
        Thread1A a1 = new Thread1A();
        Thread1A a2 = new Thread1A();
        a1.setName("线程1-");
        a2.setName("线程2-");
​
        a1.start();
        a2.start();
        //休息10秒,确保两个线程执行完毕
        Thread.sleep(1000 * 5);
        //打印集合大小
        System.out.println("Map大小:" + Const.map.size());
    }
}

说明:两个线程分别向同一个map中写入50000个键值对,最后map的size应为:100000,但多运行几次会发现有以下几种错误:

  1. 假死:

image.png

  1. 异常:

image.png

  1. 错误结果:

image.png

为了保证线程安全,可以使用Hashtable。注意:线程中加入了计时

公有、静态的集合:

public class Const {
    public static Hashtable<String,String> map = new Hashtable<>();
}

线程,向map中写入数据:

public void run() {
        long start = System.currentTimeMillis();
        for (int i = 0; i < 500000; i++) {
            Const.map.put(this.getName() + (i + 1), this.getName() + i + 1);
        }
        long end = System.currentTimeMillis();
        System.out.println(this.getName() + " 结束!用时:" + (end - start) + " 毫秒");
    }

测试类:

public class Demo {
    public static void main(String[] args) throws InterruptedException {
        Thread1A a1 = new Thread1A();
        Thread1A a2 = new Thread1A();
        a1.setName("线程1-");
        a2.setName("线程2-");
​
        a1.start();
        a2.start();
        //休息10秒,确保两个线程执行完毕
        Thread.sleep(1000 * 5);
        //打印集合大小
        System.out.println("Map大小:" + Const.map.size());
    }
}

执行结果:

image.png

可以看到,Hashtable保证的线程安全,时间是2秒多。

  • 再看ConcurrentHashMap

    公有、静态的集合:

    public class Const {
        public static ConcurrentHashMap<String,String> map = new ConcurrentHashMap<>();
    }
    

    线程,向map中写入数据:

    public void run() {
            long start = System.currentTimeMillis();
            for (int i = 0; i < 500000; i++) {
                Const.map.put(this.getName() + (i + 1), this.getName() + i + 1);
            }
            long end = System.currentTimeMillis();
            System.out.println(this.getName() + " 结束!用时:" + (end - start) + " 毫秒");
        }
    

    测试类:

    public class Demo {
        public static void main(String[] args) throws InterruptedException {
            Thread1A a1 = new Thread1A();
            Thread1A a2 = new Thread1A();
            a1.setName("线程1-");
            a2.setName("线程2-");
    ​
            a1.start();
            a2.start();
            //休息10秒,确保两个线程执行完毕
            Thread.sleep(1000 * 5);
            //打印集合大小
            System.out.println("Map大小:" + Const.map.size());
        }
    }
    

    执行结果:

image.png

  • ConcurrentHashMap仍能保证结果正确,而且提高了效率。

HashTable效率低下原因:

public synchronized V put(K key, V value) 
public synchronized V get(Object key)

HashTable容器使用synchronized来保证线程安全,但在线程竞争激烈的情况下HashTable的效率非常低下。因为当一个线程访问HashTable的同步方法,其他线程也访问HashTable的同步方法时,会进入阻塞状态。如线程1使用put进行元素添加,线程2不但不能使用put方法添加元素,也不能使用get方法来获取元素,所以竞争越激烈效率越低。

image.png ConcurrentHashMap高效的原因:CAS + 局部(synchronized)锁定

image.png