首页 > java > faq > 1.HashMap与ConcurrentHashMap的区别与应用场景

1.HashMap与ConcurrentHashMap的区别与应用场景

从JDK1.5开始加入了ConcurrentHashMap,在面试的过程中我问过很多程序员,HashpMap与ConcurrentHashMap的区别是什么呢。我得到的回答一般都是HashpMap不是线程安全的,ConcurrentHashMap是线程安全的。这个结论很笼统。下面我来详细的举例说明他们之间的区别。

1 HashMap与ConcurrentHashMap多线程同步的误区

使用了ConcurrentHashMap就意味着多线程环境中的数据是线程同步的吗?

答案是不一定。用以下代码来作为示例来说明.

1.1 编码思路

  • 分别创建一个全局HashMap和一个全局ConcurrentHashMap
  • 分别赋予初始值。
  • 多线程修改这两个集合
  • 然后输出值,比较最终结果。

1.2 需要验证的结论

  • HashMap在多线程情况下操作不能保证数据同步。
  • ConcurrentHashMap多线程操作同样不能保证数据同步。

1.3 验证代码

  • Demo1类,验证HashMap多线程操作数据不同步.
package com.dashidan.faq;

import java.util.HashMap;
import java.util.Map;

/**
 *
 * 大屎蛋教程网-dashidan.com
 * HashMap与ConcurrentHashMap的区别于应用场景
 * Created by 大屎蛋 on 2018/5/18.
 */
public class Demo1 {

    public static void main(String[] args) {
        /** 全局HashMap*/
        HashMap<Integer, Integer> hashMap = new HashMap();
        hashMap.put(0, 0);

        /** 多线程编辑100次*/
        for (int i = 0; i < 100; i++) {
            new Thread(new EditThread(hashMap)).start();
        }

        /** 读取线程*/
        new Thread(new ReadThread(hashMap)).start();
        /** 输出最终结果*/
        System.out.println("Demo1 main value " + hashMap.get(0));
    }
}

class EditThread implements Runnable {

    Map<Integer, Integer> hashMap;

    public EditThread(Map<Integer, Integer> hashMap) {
        this.hashMap = hashMap;
    }

    @Override
    public void run() {
        hashMap.put(0, hashMap.get(0) + 1);
    }
}

class ReadThread implements Runnable {

    Map<Integer, Integer> hashMap;

    public ReadThread(Map<Integer, Integer> hashMap) {
        this.hashMap = hashMap;
    }

    @Override
    public void run() {
        System.out.println("value " + hashMap.get(0));
    }
}

输出结果

Demo1 main value 76
value 94

这里每次运行的结果可能会不一样。

  • Demo2类,验证ConcurrentHashMap多线程操作数据不同步.
package com.dashidan.faq;

import java.util.concurrent.ConcurrentHashMap;

/**
 * Created by bj on 2018/5/18.
 */
public class Demo2 {

    public static void main(String[] args) {
        /** 全局ConcurrentHashMap*/
        ConcurrentHashMap<Integer, Integer> hashMap = new ConcurrentHashMap();
        hashMap.put(0, 0);

        /** 多线程编辑100次*/
        for (int i = 0; i < 100; i++) {
            new Thread(new EditThread(hashMap)).start();
        }

        /** 读取线程*/
        new Thread(new ReadThread(hashMap)).start();
        /** 输出最终结果*/
        System.out.println("Demo2 main value " + hashMap.get(0));
    }
}

输出结果:

Demo2 main value 81
value 84

多次运行,输出的结果可能不一致。这样说明多线程修改ConcurrentHashMap中的数据,不能保证多线程同步。需要进行加锁或者其他能达到线程同步的方式配合使用。

2 HashMap应用场景

2.1 HashpMap多线程情况下的ConcurrentModificationException

当方法检测到对象的并发修改(单线程情况也可能),但不允许这种修改时,抛出ConcurrentModificationException异常。

package com.dashidan.faq;

import java.util.HashMap;
import java.util.Map;
import java.util.Random;

/**
 * 大屎蛋教程网-dashidan.com
 * HashMap与ConcurrentHashMap的区别于应用场景
 * Created by 大屎蛋 on 2018/5/18.
 */
public class Demo3 {

    public static void main(String[] args) {
        /** 全局HashMap*/
        HashMap<Integer, Integer> hashMap = new HashMap();

        /** 多线程编辑100次*/
        for (int i = 0; i < 1000; i++) {
            hashMap.put(i, i);
        }

        new Thread(new AddThread(hashMap)).start();
        new Thread(new RemoveThread(hashMap)).start();

        /** 读取线程*/
        new Thread(new IteratorThread(hashMap)).start();
        /** 输出最终结果*/
    }
}

class AddThread implements Runnable {
    Map<Integer, Integer> hashMap;

    public AddThread(Map<Integer, Integer> hashMap) {
        this.hashMap = hashMap;
    }

    @Override
    public void run() {
        while (true) {
            int random = new Random().nextInt();
            hashMap.put(random, random);
        }
    }
}

class RemoveThread implements Runnable {
    Map<Integer, Integer> hashMap;

    public RemoveThread(Map<Integer, Integer> hashMap) {
        this.hashMap = hashMap;
    }

    @Override
    public void run() {
        int random = new Random().nextInt(1000);
        while (true) {
            hashMap.remove(random);
        }
    }
}


class IteratorThread implements Runnable {

    Map<Integer, Integer> hashMap;

    public IteratorThread(Map<Integer, Integer> hashMap) {
        this.hashMap = hashMap;
    }

    @Override
    public void run() {
        System.out.println("------------------ " + hashMap.size());
        for (Integer value : hashMap.values()) {
//            System.out.println("value " + value);
        }
        System.out.println("+++++++++++++++++++ " + hashMap.size());
    }
}

输出结果:

------------------ 1259
Exception in thread "Thread-2" java.util.ConcurrentModificationException
    at java.util.HashMap$HashIterator.nextNode(HashMap.java:1429)
    at java.util.HashMap$ValueIterator.next(HashMap.java:1458)
    at com.dashidan.faq.IteratorThread.run(Demo3.java:78)
    at java.lang.Thread.run(Thread.java:745

输出结果可能不一致,多次运行有几率出现ConcurrentModificationException异常.这个异常会导致程序运行停止.

2.3 推荐HashMap应用场景

上文中的输出示例验证了多线程操作下HashMap无法保证数据同步,多线程修改HashMap并且有遍历的操作时,可能会产生ConcurrentModificationException异常。所以,推荐的HashMap应用场景是单线程运行环境,并且不需要遍历操作的场景。

这个推荐场景不是硬性条件。比如多线操作HashMap,我们通过加锁或者加入同步控制依然能正常应用HashMap,只是需要加上同步操作的代价。

3 推荐ConcurrentHashMap应用场景

ConcurrentHashMap所有操作都是线程安全的,但获取操作不必锁定,并且不支持以某种防止所有访问的方式锁定整个表。获取操作(包括 get)通常不会受阻塞,因此,可能与更新操作交迭(包括 put 和 remove)。在创建迭代器/枚举时或自此之后,Iterators 和 Enumerations返回在某一时间点上影响哈希表状态的元素。它们不会 抛出 ConcurrentModificationException。

在上文中的HashMap示例中,我们将HashMap改为ConcurrentHashMap,来看看会发生什么.以下示例代码:

package com.dashidan.faq;

import java.util.concurrent.ConcurrentHashMap;

/**
 * 大屎蛋教程网-dashidan.com
 * HashMap与ConcurrentHashMap的区别于应用场景
 * Created by 大屎蛋 on 2018/5/18.
 */
public class Demo4 {

    public static void main(String[] args) {
        /** 全局ConcurrentHashMap*/
        ConcurrentHashMap<Integer, Integer> hashMap = new ConcurrentHashMap();

        /** 多线程编辑1000次*/
        for (int i = 0; i < 1000; i++) {
            hashMap.put(i, i);
        }

        new Thread(new AddThread(hashMap)).start();
        new Thread(new RemoveThread(hashMap)).start();

        /** 读取线程*/
        new Thread(new IteratorThread(hashMap)).start();
    }
}

输出结果

------------------ 1381
+++++++++++++++++++ 3072

每次运行输出结果可能会不一致。这个是多线程操作下,不能保证插入顺序,所以插入的随机值位置不固定。输出遍历前h后的ConcurrentHashMap长度不一致。

这种情况说明,在遍历ConcurrentHashMap时如果遍历过程中,该集合的机构发生变化,比如put,remove数据。这时不会抛出ConcurrentModificationException,能够正常遍历完成ConcurrentHashMap.

  • ConcurrentHashMap推荐应用场景 多线程对HashMap数据添加删除操作时,可以采用ConcurrentHashMap。
转载请保留原文链接.