Java Concurrency代码实例之六-ConcurrentHashMap

本文的读者应该是已经掌握了基本的Java多线程开发技巧,但不熟悉Java Concurrency包的程序员。本文是本系列的第六篇文章,前五篇文章请看这里:
Java Concurrency代码实例之一执行者与线程池
Java Concurrency代码实例之二并发队列
Java Concurrency代码实例之三原子变量
Java Concurrency代码实例之四-锁
Java Concurrency代码实例之五-同步工具

1. 前言

按照用途与特性,Concurrency包中包含的工具被分为六类(外加一个工具类TimeUnit),即:
1. 执行者与线程池
2. 并发队列
3. 同步工具
4. 并发集合
5. 锁
6. 原子变量
由于前五篇文章一次介绍Concurrency包中的一整类特性,导致文章冗长,重点不突出,效果反而不好。因而自此以后,一篇仅介绍一个具体类。本文介绍的是并发集合中最常用的一个类ConcurrentHashMap。

2. 三大Map的区别

谈到ConcurrentHashMap,就不能不提它的前辈HashMap和Hashtable。
HashMap是Java中最常用的一个Map类了,它性能好、速度快,但不能保证线程安全,它可以使用null作为key或者value。
Hashtable是Java中最老的Map类,自JDK1.0版本就存在了,它是一个线程安全的Map类,其公有方法均使用synchronize关键字修饰,这表示在多线程操作时,每个线程在操作之前都会锁住整个map,待操作完成后才释放,这必然导致多线程时性能不佳。另外,Hashtable不能使用null作为key或者value。
ConcurrentHashMap是HashMap的并发类,它是线程安全的。与Hashtable相比,由于它大量使用了自旋等待、多segment等并发技术,使得并发操作时往往不需要锁住整个map,因此其多线程性能远超Hashtable。它也不能使用null作为key或者value。

3. HashMap原理概述

3.1 Hash函数与结构

HashMap是一个存储键值对

3.2 公有方法

HashMap中的公有方法是很少的,主要包括:
插入:put(K key, V value)和putAll(Map<? extends K, ? extends V> m),分别用来插入一个键值对和一组键值对;
查询:containsKey(Object key)用来查询Map中是否包含key,containsValue(Object value)用来查询Map中是否包含value;
获取:get(Object key),用来根据key获取value;
删除:remove(Object key)和clear(),分别用来删除一个键值对和清空Map;
长度:size()和isEmpty()。

3.3 三大集合与迭代子

除了以上方法以外,HashMap使用三大集合和三种迭代子来轮询其Key、Value和Entry对象,其使用方法如下所示:

public class HashMapExam {
    public static void main(String[] args) {
        Map<Integer, String> map = new HashMap<>(16);
        for (int i = 0; i < 15; i++) {
            map.put(i, new String(new char[]{(char) ('A'+ i)}));
        }

        System.out.println("======keySet=======");
        Set<Integer> set = map.keySet();
        Iterator<Integer> iterator = set.iterator();
        while (iterator.hasNext()) {
            System.out.println(iterator.next());
        }

        System.out.println("======values=======");
        Collection<String> values = map.values();
        Iterator<String> stringIterator=values.iterator();
        while (stringIterator.hasNext()) {
            System.out.println(stringIterator.next());
        }

        System.out.println("======entrySet=======");
        for (Map.Entry<Integer, String> entry : map.entrySet()) {
            System.out.println(entry);
        }
    }
}

3.4 自动扩容

HashMap的hash表是一个原生数组Entry<K,V>[] table,这个原生数组的长度在HashMap创建时就被确定。程序员可以在构造函数中使用initialCapacity(初始容量,默认为16)和loadFactor(负载因子,默认为0.75)来指定HashMap的容量以及自动扩容的时机。
initialCapacity用来指定初始容量,HashMap的容量都是2的幂,若initialCapacity不是正好等于2的幂,则被向上取值为最近的一个2的幂值;loadFactor负载因子用来决定扩容的门槛值threshold,这个threshold就是容量与负载因子的乘积,当Map的size值大于threshold时,就会在put时检测是否进行扩容。当满足size大于threshold,且当前插入的Key在hash表中的相应位置已经有值(即该键值对要加入一个单向链表)时,Map会自动扩容为原来的2倍。
下面的例子展示了HashMap扩容的特性:

public class HashMapExam2 {
    public static void main(String[] args) {
        Map<Integer, String> map = new HashMap<>(10);
        System.out.println("map initialize");
        showMap(map);
        for (int i = 0; i < 20; i++) {
            map.put(i, new String(new char[]{(char) ('A'+ i)}));
            System.out.println("put "+i);
            showMap(map);
        }
    }

    private static void showMap(Map<Integer, String> map) {
        try {
            Field f = HashMap.class.getDeclaredField("table");
            f.setAccessible(true);
            Map.Entry<Integer,String>[] table= (Map.Entry<Integer, String>[]) f.get(map);
            f = HashMap.class.getDeclaredField("threshold");
            f.setAccessible(true);
            int threshold = (int) f.get(map);
            System.out.println("map size = "+map.size()+",threshold = "+threshold+",length="+table.length);
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }

}

例子中使用反射获取了HashMap的Map.Entry<Integer,String>[] table和threshold变量。从运行结果可以看出,当Map为空时,table的长度为0;当put了一个元素时,table长度被初始化为16,而threshold为12;当put了17个元素时,table扩容为32,而threshold为24。

4. ConcurrentHashMap原理概述

此文章仅描述JDK1.7版本中的ConcurrentHashMap。与HashMap相比,虽然ConcurrentHashMap仅仅只增加了并发特性,但是其复杂度却极大的上升了。因为考虑到并发性能,它没有像Hashtable一样简单的给每个公有方法加上synchronize,而是利用了JUC包中提供的多种并发特性,在尽量保持性能的前提下实现了多线程安全。顺便说一句,《Java编程思想》的作者曾经提到,Hashtable类已经基本上可以废弃了,因为它能做的ConcurrentHashMap都能做,且性能更好。Hashtable类存在的意义可能就是用来面试吧。

4.1 ConcurrentHashMap与Hashtable之性能比较

为了验证以上的说法,写了一段代码来测试ConcurrentHashMap与Hashtable之性能比较:

public class ConcurrentHashMapVsHashtable {
   
    private static int INPUT_NUMBER = 100000;

    public static void main(String[] args) throws InterruptedException {
//        Map<Integer, String> map = new Hashtable<>(12 * INPUT_NUMBER);
        Map<Integer, String> map = new ConcurrentHashMap<>(12 * INPUT_NUMBER);
        long begin = System.currentTimeMillis();
        ExecutorService service = Executors.newCachedThreadPool();
        for (int i = 0; i < 10; i++) {
            service.execute(
  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值