HashTable源码解析、Collections.synchronizedMap解析

本文详细对比了HashTable与HashMap的区别,包括线程安全、插入null的规则、容量调整、Hash映射算法和扩容机制。HashTable虽然线程安全但效率低,已被废弃,推荐使用ConcurrentHashMap。此外,还介绍了Collections.synchronizedMap的实现原理,它通过同步锁保证线程安全,但效率与HashTable相近,同样不建议在高并发场景下使用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、类继承关系图

二、HashTable介绍

HashTable的操作几乎和HashMap一致,主要的区别在于HashTable为了实现多线程安全,在几乎所有的方法上都加上了synchronized锁,而加锁的结果就是HashTable操作的效率十分低下。

不建议使用HashTable,Oracle官方也将其废弃,建议在多线程环境下使用ConcurrentHashMap类。

三、HashTable和HashMap的对比

1.线程安全

HashMap是线程不安全的类,多线程下会造成并发冲突,但单线程下运行效率较高;HashTable是线程安全的类,很多方法都是用synchronized修饰,但同时因为加锁导致并发效率低下,单线程环境效率也十分低;

2.插入null

HashMap允许有键为null,值为null;但HashTable不允许键或值为null;

3.容量

HashMap底层数组长度必须为2的幂,这样做是为了hash准备,默认为16;而HashTable底层数组长度可以为任意值,这就造成了hash算法散射不均匀,容易造成hash冲突,默认为11;

   public Hashtable(int initialCapacity, float loadFactor) {
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal Load: "+loadFactor);

        if (initialCapacity==0)
            initialCapacity = 1;
        this.loadFactor = loadFactor;
        table = new Entry<?,?>[initialCapacity];
        threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
    }

    /**
     * Constructs a new, empty hashtable with the specified initial capacity
     * and default load factor (0.75).
     *
     * @param     initialCapacity   the initial capacity of the hashtable.
     * @exception IllegalArgumentException if the initial capacity is less
     *              than zero.
     */
    public Hashtable(int initialCapacity) {
        this(initialCapacity, 0.75f);
    }

    /**
     * Constructs a new, empty hashtable with a default initial capacity (11)
     * and load factor (0.75).
     */
    public Hashtable() {
        this(11, 0.75f);
    }

    /**
     * Constructs a new hashtable with the same mappings as the given
     * Map.  The hashtable is created with an initial capacity sufficient to
     * hold the mappings in the given Map and a default load factor (0.75).
     *
     * @param t the map whose mappings are to be placed in this map.
     * @throws NullPointerException if the specified map is null.
     * @since   1.2
     */
    public Hashtable(Map<? extends K, ? extends V> t) {
        this(Math.max(2*t.size(), 11), 0.75f);
        putAll(t);
    }

4.Hash映射

HashMap的hash算法通过非常规设计,将底层table长度设计为2的幂,使用位与运算代替取模运算,减少运算消耗;而HashTable的hash算法首先使得hash值小于整型数最大值,再通过取模进行散射运算;

int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;

5.扩容机制

HashMap创建一个为原先2倍的数组,然后对原数组进行遍历以及然后重新通过位运算计算位置,不管是红黑树还是链表,都先采取尾插法分成两条链,然后再通过链的数量决定是树化还是转链表(其实就是把TreeNode变成Node,因为红黑树分成两条链后其实就是TreeNode组成的链表);HashTable扩容将创建一个原长度2倍的数组 + 1,然后对原数组进行遍历以及rehash,头插法;

hashTable的扩容:

int newCapacity = (oldCapacity << 1) + 1;

hashTable的头插法:

        for (int i = oldCapacity ; i-- > 0 ;) {
            for (Entry<K,V> old = (Entry<K,V>)oldMap[i] ; old != null ; ) {
                Entry<K,V> e = old;
                old = old.next;

                int index = (e.hash & 0x7FFFFFFF) % newCapacity;
                e.next = (Entry<K,V>)newMap[index];
                newMap[index] = e;
            }
        }

6.结构区别

HashMap是由数组+链表形成,在JDK1.8之后链表长度大于8时转化为红黑树;而HashTable一直都是数组+链表;

四、Collections.synchronizedMap解析

1.Collections.synchronizedMap是怎么实现线程安全的

(1).调用Collections.synchronizedMap实际是给Map包装成了SynchronizedMap

    public static <K,V> Map<K,V> synchronizedMap(Map<K,V> m) {
        return new SynchronizedMap<>(m);
    }

(2).SynchronizedMap源码

先看属性:

        private final Map<K,V> m;     // Backing Map
        final Object      mutex;        // Object on which to synchronize

再看构造方法:

        SynchronizedMap(Map<K,V> m) {
            this.m = Objects.requireNonNull(m);
            mutex = this;
        }

        SynchronizedMap(Map<K,V> m, Object mutex) {
            this.m = m;
            this.mutex = mutex;
        }

通过构造方法,把map传进来,如果不传Object mutex参数,mutex就是this

再看一下具体是怎么实现线程安全的:

        public int size() {
            synchronized (mutex) {return m.size();}
        }
        public boolean isEmpty() {
            synchronized (mutex) {return m.isEmpty();}
        }
        public boolean containsKey(Object key) {
            synchronized (mutex) {return m.containsKey(key);}
        }
        public boolean containsValue(Object value) {
            synchronized (mutex) {return m.containsValue(value);}
        }
        public V get(Object key) {
            synchronized (mutex) {return m.get(key);}
        }

        public V put(K key, V value) {
            synchronized (mutex) {return m.put(key, value);}
        }
        public V remove(Object key) {
            synchronized (mutex) {return m.remove(key);}
        }
        public void putAll(Map<? extends K, ? extends V> map) {
            synchronized (mutex) {m.putAll(map);}
        }
        public void clear() {
            synchronized (mutex) {m.clear();}
        }

发现几乎所有操作Map的代码,都把mutex作为锁,获取到锁之后去操作Map。

这种和HashTable直接锁整个方法粒度差不多,都不推荐使用,推荐使用ConcurrentHashMap

### Collections.synchronizedMap、ConcurrentHashMap Hashtable 的区别 #### 线程安全性机制 `Collections.synchronizedMap` 方法返回一个同步的 (线程安全的) 映射,该映射的方法被同步锁保护。这意味着每次只有一个线程可以访问映射中的方法调用[^1]。 相比之下,`ConcurrentHashMap` 提供了一种更细粒度的锁定机制,在大多数并发场景下性能更好。它允许多个读取操作并行执行,并发地处理多个桶(buckets),而不仅仅是整个表级别的独占锁。 `Hashtable` 类似于 `synchronizedMap` 返回的对象,所有的主要公共方法都被声明为 `synchronized`,因此也是完全同步化的;但是其内部实现细节与现代集合框架有所不同[^2]。 #### 性能特性 由于采用了分段锁技术,`ConcurrentHashMap` 在高并发环境下通常具有更好的吞吐量。对于只读操作或者少量更新的情况尤其明显,因为这些情况下几乎不需要加锁开销。 另一方面,当使用 `Collections.synchronizedMap` 或者 `Hashtable` 时,即使只是获取键值对这样的简单查询也需要等待其他写入完成才能继续,这可能导致不必要的延迟较低的整体效率。 #### 功能差异 值得注意的是,除了基本的功能外,`ConcurrentHashMap` 还提供了额外的一些实用工具类方法来支持原子性的复合动作,比如计算特定条件下的删除或替换等复杂逻辑而不必担心竞态条件问题。 然而,无论是通过 `Collections.synchronizedMap` 创建的地图还是原生的 `Hashtable` 都缺乏这种高级别的抽象层次的支持。 ```java // 使用 ConcurrentMap 接口提供的 replace() 方法 concurrentHashMap.replace(key, oldValue, newValue); ``` #### 设计哲学的不同 从设计角度来看,`Hashtable` 是较早版本 JDK 中的一部分,后来随着 Java 平台的发展逐渐被推荐使用更加灵活高效的替代品所取代。尽管如此,出于向后兼容的原因仍然保留了下来。 综上所述,如果应用程序确实存在多线程环境下的共享数据结构需求,则应优先考虑采用 `ConcurrentHashMap` 来代替传统的解决方案如 `Hashtable` 或基于 `Collections.synchronizedMap()` 构建的方式,以获得更高的可扩展性响应速度。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值