java基础—HashMap实现原理,如何保证HashMap的线程安全?

Java HashMap 是非线程安全的。

在多线程条件下,容易导致死循环,具体表现为CPU使用率100%。因此多线程环境下保证 HashMap 的线程安全性,主要有如下几种方法:

 
使用 java.util.Hashtable 类,此类是线程安全的。
使用 java.util.concurrent.ConcurrentHashMap,此类是线程安全的。
使用 java.util.Collections.synchronizedMap() 方法包装 HashMap object,得到线程安全的Map,并在此Map上进行操作。
自己在程序的关键方法或者代码段加锁,保证安全性,当然这是严重的不推荐。
 
这里重点分析下上面列举的几种方法实现并行安全性的原理:
 
(一)java.util.Hashtable类:类的主要数据结构如下:
    
Java代码  收藏代码
    /** 
     * The hash table data. 
     */  
    private transient Entry[] table;  
  
private static class Entry<K,V> implements Map.Entry<K,V> {  
    int hash;  
    K key;  
    V value;  
    Entry<K,V> next;  
     
       可见,Hashtable 的实现是一个数组,每个数组元素是一个LinkList结构,因此类的数据实际上保存在一个散列表中。这个实现和 HashMap 的实现是一致的。数据结构如下:


 
      那么Hashtable如何保证线程安全性的哪?下面是 Hashtable的源码:
 
Java代码  收藏代码
public synchronized V get(Object key) {  
    Entry tab[] = table;  
    …… //此处省略,具体的实现请参考 jdk实现  
    }  
  
public synchronized V put(K key, V value) {  
    …… //具体实现省略,请参考jdk实现  
    }  
  
  
public synchronized V remove(Object key) {  
    …… //具体实现省略,请参考jdk实现  
    }  
  
     
    public synchronized void putAll(Map<? extends K, ? extends V> t) {  
        for (Map.Entry<? extends K, ? extends V> e : t.entrySet())  
            put(e.getKey(), e.getValue());  
    }  
  
  
    public synchronized void clear() {  
    …… //具体实现省略,请参考jdk实现  
    }  
     上面是 Hashtable 提供的几个主要方法,包括 get(), put(), remove() 等。注意到每个方法本身都是 synchronized 的,不会出现两个线程同时对数据进行操作的情况,因此保证了线程安全性,但是也大大的降低了执行效率。因此是不推荐的。
 
 
(二)使用 java.util.Collections.synchronizedMap(Map<K,V>) 方法进行封装。 方法源代码如下:
 
Java代码  收藏代码
public static <K,V> Map<K,V> synchronizedMap(Map<K,V> m) {  
    return new SynchronizedMap<K,V>(m);  
    }  
  
  
private static class SynchronizedMap<K,V>  
    implements Map<K,V>, Serializable {  
    // use serialVersionUID from JDK 1.2.2 for interoperability  
    private static final long serialVersionUID = 1978198479659022715L;  
  
    private final Map<K,V> m;     // Backing Map  
        final Object      mutex;    // Object on which to synchronize  
  
    SynchronizedMap(Map<K,V> m) {  
            if (m==null)  
                throw new NullPointerException();  
            this.m = m;  
            mutex = this;  
        }  
  
    SynchronizedMap(Map<K,V> m, Object mutex) {  
            this.m = m;  
            this.mutex = mutex;  
        }  
  
    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);}  
        }  
       从实现源代码可以发现,其封装的本质和 Hashtable 的实现是完全一致的,即对原Map本身的方法进行加锁,加锁的对象或者为外部指定共享对象mutex,或者为包装后的线程安全的Map本身。Hashtable 可以理解为 SynchronizedMap mutex=null 时候的特殊情况。因此这种同步方式的执行效率也是很低的。
   
        既然已经有了Hashtable, 为什么还需要Collections 提供的这种静态方法包装哪?很简单,这种包装是Java Collection Framework提供的统一接口,除了用于 HashMap 外,还可以用于其他的Map。当然 除了对Map进行封装,Collections工具类还提供了对 Collection(比如Set,List)的线程安全实现封装方法,具体请参考 java.util.Colletions 实现,其原理和 SynchronizedMap 是一致的。
 
(三) 使用 java.util.concurrent.ConcurrentHashMap 类。这是 HashMap 的线程安全版,同 Hashtable 相比,ConcurrentHashMap 不仅保证了访问的线程安全性,而且在效率上有较大的提高。
 

ConcurrentHashMap的数据结构如下:




 
可以看出,相对 HashMap 和 Hashtable, ConcurrentHashMap 增加了Segment 层,每个Segment 原理上等同于一个 Hashtable, ConcurrentHashMap 为 Segment 的数组。下面是 ConcurrentHashMap 的 put 和 get 方法:
Java代码  收藏代码
final Segment<K,V> segmentFor(int hash) {  
        return segments[(hash >>> segmentShift) & segmentMask];  
    }  
  
public V put(K key, V value) {  
        if (value == null)  
            throw new NullPointerException();  
        int hash = hash(key.hashCode());  
        return segmentFor(hash).put(key, hash, value, false);  
    }  
  
public V get(Object key) {  
        int hash = hash(key.hashCode());  
        return segmentFor(hash).get(key, hash);  
    }  
向 ConcurrentHashMap 中插入数据或者读取数据,首先都要讲相应的 Key 映射到对应的 Segment,因此不用锁定整个类, 只要对单个的 Segment 操作进行上锁操作就可以了。理论上如果有 n 个 Segment,那么最多可以同时支持 n 个线程的并发访问,从而大大提高了并发访问的效率。另外 rehash() 操作也是对单个的 Segment 进行的,所以由 Map 中的数据量增加导致的 rehash 的成本也是比较低的。
 
单个 Segment 的进行数据操作的源码如下:
 
Java代码  收藏代码
V put(K key, int hash, V value, boolean onlyIfAbsent) {  
            lock();  
            try {  
                int c = count;  
                if (c++ > threshold) // ensure capacity  
                    rehash();  
  
                 …… // 代码省略,具体请查看源码                  
  
            } finally {  
                unlock();  
            }  
        }  
  
V replace(K key, int hash, V newValue) {  
            lock();  
            try {  
                HashEntry<K,V> e = getFirst(hash);  
                
                 …… // 代码省略,具体请查看源码     
                 
            } finally {  
                unlock();  
            }  
        }  
 

   可见对 单个的 Segment 进行的数据更新操作都是 加锁的,从而能够保证线程的安全性。

文章转自:http://liqianglv2005.iteye.com/blog/2025016

HashMap实现原理是基于哈希表(Hash Table)的数据结构。在HashMap中,每个元素被存储为键值对(key-value pair),通过计算键的哈希值来确定其在数组中的存储位置。 具体实现原理如下: 1. 当我们向HashMap中插入一个键值对时,首先会计算键的哈希值。 2. 根据哈希值,HashMap使用哈希函数将其转换为一个数组索引。 3. 如果该索引处没有元素,则直接将键值对插入该位置。 4. 如果该索引处已经存在元素,则发生冲突,HashMap会使用链表或者红黑树的方式来处理冲突(JDK 8之后,如果链表长度超过阈值(默认为8),链表会转换为红黑树)。 5. 当需要查找元素时,HashMap会根据键的哈希值找到对应的数组索引,并在该位置上进行查找。 HashMap如何保证线程安全取决于具体的使用场景和需求。如果在多线程环境下使用HashMap,并且涉及到插入、删除、修改等操作,可以考虑以下几种方式来保证线程安全: 1. 使用ConcurrentHashMapConcurrentHashMapJava提供的线程安全的哈希表实现。它使用了分段锁(Segment)的机制来保证并发访问的安全性,可以支持高并发的读操作和部分并发的写操作。 2. 使用Collections.synchronizedMap()方法:通过该方法可以将HashMap转换为线程安全的Map,底层使用了同步块(synchronized block)来保证线程安全。例如:Map<String, String> synchronizedMap = Collections.synchronizedMap(new HashMap<>()); 3. 使用锁机制:在多线程环境下,可以使用显式的锁机制(如ReentrantLock、synchronized等)来对HashMap进行加锁,保证每次操作的原子性和互斥性。 需要注意的是,即使采取了上述措施来保证HashMap线程安全性,也需要根据具体业务场景考虑并发读写带来的性能影响。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值