• Java 集合 线程安全


    Java中常用的集合框架中的实现类HashSet、TreeSet、ArrayList、ArrayDeque、LinkedList、HashMap、TreeMap都是线程不安全的,如果多个线程同时访问它们,而且有超过一个的线程试图修改它们,则存在线程安全的问题。

    Hashtable:

      Hashtable是线程安全的,任意时刻只能有一个线程对Hashtable进行操作,并发性不如ConcurrentHashMap,因为后者引入了分段锁

      Hashtable的线程安全使用的是一个单独的全部Map范围的锁,这个锁在所有的插入、删除、查询操作中都会持有,甚至在使用Iterator遍历整个Map时也会持有这个单独的锁。当锁被一个线程持有时,就能够防止其他线程访问该Map,即便其他线程都处于闲置状态。这种单个锁机制极大的限制了并发的性能

      参考博客:http://blog.csdn.net/zcc_0015/article/details/46932667

    Collections.sychroinzedXxx():

      Collections提供了多个synchronizedXxx()方法可以将指定集合包装成线程同步的集合,从而解决多线程并发访问集合时的线程安全问题

            Collections.synchronizedMap(map);
            Collections.synchronizedList(list);
            Collections.synchronizedSet(set);

      以synchronizedMap()为例,它的方法实现如下,接受一个map对象,返回一个SynchronizedMap<>对象

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

       我们再来看SynchronizedMap<>类,它实现了Map<K,V>接口,并且是可序列化的,它有两个构造方法,而且使用了synchronized来保证对Map的操作是线程安全的

     private static class SynchronizedMap<K,V>
            implements Map<K,V>, Serializable {
            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) {
                this.m = Objects.requireNonNull(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();}
            }
      }

       但是我们进一步考虑,这里的get、set、remove等方法是同步的,也就是说我们对map的操作是线程安全的,但是它们之间呢?比如说:用户A对map进行了get操作,准备remove map中的元素;而此时,用户B也对map进行get操作,然后remove了这一项,此时,用户A开始做remove的时候,发现这个元素已经不存在了,这段操作显然不是线程安全的,我们显然需要将整个map锁住,这样的话,效率就会大大降低

    ConcurrentHashMap:

      Java5新增了ConcurrentMap接口和它的一个实现类ConcurrentHashMap,ConcurrentHashMap提供了和HashTable、SynchronizedMap种不同的锁机制。Hashtable中采用的锁机制是一次锁住整个hash表,从而同一时刻只能由一个线程对其进行操作;而ConcurrentHashMap中则是一次锁住一个桶。ConcurrentHashMap默认将hash表分为16个桶,诸如get,put,remove等常用操作只锁当前需要用到的桶。这样,原来只能一个线程进入,现在却能同时有16个写线程执行,并发性能的提升是显而易见的

      ConcurrentHashMap抛弃了HashTable的单锁机制,使用了锁分离技术,使得多个修改操作能够并发进行。ConcurrentHashMap内部使用段(Segment),默认是16个段,来表示这些不同的部分,每个段其实就是一个小的hash table,即通过多个锁来控制对不同段的hash表的修改,每个锁只负责一部分key的hash值范围。只要多个修改操作发生在不同的段上,它们就可以并发进行

    CopyOnWrite:

      同步的实例方法在执行之前都会隐式的需要一个锁。java中 也可以显示的调用锁,CopyOnWrite容器就是用ReentrantLock锁实现的,ReentrantLock是为创建相互排斥的锁的Lock的具体实现

      CopyOnWrite容器即写时复制的容器。通俗的理解是当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行Copy,复制出一个新的容器,然后新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器。这样做的好处是我们可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器

      参考链接:http://blog.csdn.net/mark_wk/article/details/22588263

             https://zhidao.baidu.com/question/1692121547117149828.html

        public boolean add(E e) {
            final ReentrantLock lock = this.lock;
            lock.lock();
            try {
                Object[] elements = getArray();
                int len = elements.length;
                Object[] newElements = Arrays.copyOf(elements, len + 1);
                newElements[len] = e;
                setArray(newElements);
                return true;
            } finally {
                lock.unlock();
            }
        }
  • 相关阅读:
    Delphi String的散漫记录,真是知识无数,陷阱无数(转)
    GB2312 编码
    Windows系统字体与文件对照表
    Windows字符集的统一与转换
    Delphi 的绘图功能[10]
    各种字符集和编码详解
    NTC热敏电阻参数
    Spring Cloud 学习笔记(一)——入门、特征、配置
    上坡路定点停车与坡道起步
    网闸结构和工作原理
  • 原文地址:https://www.cnblogs.com/roxy/p/7341270.html
Copyright © 2020-2023  润新知