• HashMap与线程安全


    HashMap与线程安全

    一、HashMap 为何是线程不安全的

        HashMap是通过散列表来实现存储结构的,具体内容请看我的另一篇博客《HashMap深度解析》,那么HashMap为什么线程不安全呢,主要有两个原因。

    首先肯定是多个线程同时去往集合里添加数据,第一个原因:两个线程同时添加相同的key值数据,当两个线程同时遍历完桶内的链表时,发现,没有该key值的数据,这是他们同时创建了一个Entry结点,都添加到了桶内的链表上,这样在该HashMap集合中就出现了两个Key相同的数据。第二个原因:当两个线程同时检测到size/capacity>负载因子时,在扩容的时候可能会在链表上产生死循环(为什么会产生死循环,可以看一些HashMap的死循环相关的博客),也可能会产生存储异常。

    二、如何线程安全的使用HashMap

    方法一:Hashtable

        Hashtable是Java低版本中提出来的,由于其内部的加锁机制,是的其性能较低,目前已经不常用了。所以当一个线程访问Hashtable的同步方法时,其他线程如果也要访问同步方法,会被阻塞住。举个例子,当一个线程使用put方法时,另一个线程不但不可以使用put方法,连get方法都不可以,效率很低。

        HashTable源码中是使用synchronized来保证线程安全的,比如下面的get方法和put方法:
    public synchronized V get(Object key) {}

    public synchronized V put(K key, V value) {}

    方法二:SynchronizedMap

        调用synchronizedMap()方法后会返回一个SynchronizedMap类的对象,而在SynchronizedMap类中使用了synchronized同步关键字来保证对Map的操作是线程安全的。

    源码如下

    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);}
            }
        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();}
        }
        private transient Set<K> keySet = null;
        private transient Set<Map.Entry<K,V>> entrySet = null;
        private transient Collection<V> values = null;
        public Set<K> keySet() {
                synchronized(mutex) {
                    if (keySet==null)
                        keySet = new                                     SynchronizedSet<K>(m.keySet(),mutex); 
                    return keySet;
                }
        }
        public Set<Map.Entry<K,V>> entrySet() {
                synchronized(mutex) {
                    if (entrySet==null)
                        entrySet = new SynchronizedSet<Map.Entry<K,V>>(m.entrySet(), mutex);
                    return entrySet;
                }
        }
        public Collection<V> values() {
                synchronized(mutex) {
                    if (values==null)
                        values = new SynchronizedCollection<V>(m.values(), mutex);
                    return values;
                }
            }
        public boolean equals(Object o) {
                if (this == o)
                    return true;
                synchronized(mutex) {return m.equals(o);}
            }
        public int hashCode() {
                synchronized(mutex) {return m.hashCode();}
            }
        public String toString() {
            synchronized(mutex) {return m.toString();}
            }
            private void writeObject(ObjectOutputStream s) throws IOException {
            synchronized(mutex) {s.defaultWriteObject();}
            }
    }

    方法三:ConcurrentHashMap

        ConcurrentHashMap是java.util.concurrent包中的一个类,

    首先,我们先来了解一下这个集合的原理。hashtable是做了同步的,但是性能降低了,因为 hashtable每次同步执行的时候都要锁住整个结构。于是ConcurrentHashMap 修改了其锁住整个结构的格式,改为了只锁住HashMap的一个桶,锁的粒度大大减小,如下图:

     

    而且ConcurrentHashMap的读取操作几乎是完全的并发操作。所以ConcurrentHashMap 读操作的加锁加锁粒度变小,个体操作几乎没有锁,所以比起之前的Hashtable大大变快了(这一点在桶更多时表现得更明显些)。只有在求size等操作时才需要锁定整个表。我认为ConcurrentHashMap是线程安全的集合中最高效的。

        而在迭代时,ConcurrentHashMap使用了不同于传统集合的快速失败迭代器的另一种迭代方式,我们称为弱一致迭代器。在这种迭代方式中,当iterator被创建后集合再发生改变就不再是抛出 ConcurrentModificationException,取而代之的是在改变时new新的数据从而不影响原有的数据,iterator完成后再将头指针替换为新的数据,这样iterator线程可以使用原来老的数据,而写线程也可以并发的完成改变,更重要的,这保证了多个线程并发执行的连续性和扩展性,是性能提升的关键。

    在Java 7中使用的是对Segment(Hash表的一个桶)加锁的方式

    ConcurrentHashMap中主要实体类就是三个:ConcurrentHashMap(整个Hash表),Segment(桶),HashEntry(节点)。

    Segment的源码

    static final class Segment<K,V> extends ReentrantLock implements Serializable {

               private static final long serialVersionUID = 2249069246763182397L;

               static final int MAX_SCAN_RETRIES =Runtime.getRuntime().

                    availableProcessors() > 1 ? 64 : 1;

               transient volatile HashEntry<K,V>[] table;

               transient int count;

               transient int modCount;

               transient int threshold;

               final float loadFactor;

          }

    HashEntry的源码

    static final class HashEntry<K,V> { 

        final K key; 

        final int hash; 

        volatile V value; 

        final HashEntry<K,V> next; 

    在Java 8中摒弃了Segment的概念,利用CAS算法做了新方式

    CAS算法采用“数组+链表+红黑树”的方式实现

                                          ————亓慧杰

  • 相关阅读:
    jquery实现瀑布文字
    文本域、bootstrap-table显示以及MySQL三者间的换行符问题
    Mybatis框架的搭建和基本使用方法
    解决ajax多次绑定问题
    浅析JSONP与CROS技术解决跨域问题
    使用Ajax+nodejs实现页面头像上传功能
    自写滚屏组件
    express框架的ejs模板引擎渲染html页面
    自写轮播组件
    mousewheel事件细节
  • 原文地址:https://www.cnblogs.com/JCxiangbei/p/6544778.html
Copyright © 2020-2023  润新知