Java容器线程安全源码探究

我们经常听说这个容器是线程安全的,那个是不安全的,那到底为什么一些是安全的一些是不安全的呢,我们将从源码来查看。

线程安全

线程安全是多线程编程时的计算机程序代码中的一个概念。在拥有共享数据的多条线程并行执行的程序中,线程安全的代码会通过同步机制保证各个线程都可以正常且正确的执行,不会出现数据污染等意外情况。—百度百科

Java容器依赖图(基础)

出自于https://zhuanlan.zhihu.com/p/29421226
图片出自:https://zhuanlan.zhihu.com/p/29421226

List

Vector 同步

/**
* Vector类实现了一个动态数组。和ArrayList和相似,但是两者是不同的:
* Vector是同步访问的。
* Vector包含了许多传统的方法,这些方法不属于集合框架。
* Vector主要用在事先不知道数组的大小,或者只是需要一个可以改变大小的数组的情况。
*/
List<Integer> vector=new Vector<>();

为什么Vector是同步访问的呢?看它的源码
1. 数据结构:数组

protected Object[] elementData;

2. 方法:由synchronized修饰来保证安全

public synchronized void setElementAt(E obj, int index) {
        if (index >= elementCount) {
            throw new ArrayIndexOutOfBoundsException(index + " >= " +
                                                     elementCount);
        }
        elementData[index] = obj;
    }

    public synchronized void removeElementAt(int index) {
        modCount++;
        if (index >= elementCount) {
            throw new ArrayIndexOutOfBoundsException(index + " >= " +
                                                     elementCount);
        }
        else if (index < 0) {
            throw new ArrayIndexOutOfBoundsException(index);
        }
        int j = elementCount - index - 1;
        if (j > 0) {
            System.arraycopy(elementData, index + 1, elementData, index, j);
        }
        elementCount--;
        elementData[elementCount] = null; /* to let gc do its work */
    }

   
    public synchronized void insertElementAt(E obj, int index) {
        modCount++;
        if (index > elementCount) {
            throw new ArrayIndexOutOfBoundsException(index
                                                     + " > " + elementCount);
        }
        ensureCapacityHelper(elementCount + 1);
        System.arraycopy(elementData, index, elementData, index + 1, elementCount - index);
        elementData[index] = obj;
        elementCount++;
    }


    public synchronized void addElement(E obj) {
        modCount++;
        ensureCapacityHelper(elementCount + 1);
        elementData[elementCount++] = obj;
    }


    public synchronized boolean removeElement(Object obj) {
        modCount++;
        int i = indexOf(obj);
        if (i >= 0) {
            removeElementAt(i);
            return true;
        }
        return false;
    }


    public synchronized void removeAllElements() {
        modCount++;
        // Let gc do its work
        for (int i = 0; i < elementCount; i++)
            elementData[i] = null;

        elementCount = 0;
    }

    public synchronized Object clone() {
        try {
            @SuppressWarnings("unchecked")
                Vector<E> v = (Vector<E>) super.clone();
            v.elementData = Arrays.copyOf(elementData, elementCount);
            v.modCount = 0;
            return v;
        } catch (CloneNotSupportedException e) {
            // this shouldn't happen, since we are Cloneable
            throw new InternalError(e);
        }
    }

可见Vector用synchronized关键字来保证数据的一致性,但synchronized是重量级锁,导致性能一般。

ArrayList 不同步

1. 数据结构:数组

transient Object[] elementData;

2. 方法:无任何保护措施,不安全

    public E get(int index) {
        rangeCheck(index);

        return elementData(index);
    }

    public E set(int index, E element) {
        rangeCheck(index);

        E oldValue = elementData(index);
        elementData[index] = element;
        return oldValue;
    }

    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

    public void add(int index, E element) {
        rangeCheckForAdd(index);

        ensureCapacityInternal(size + 1);  // Increments modCount!!
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        elementData[index] = element;
        size++;
    }

ArrayList是我们平时练习中常用的类,但是它很不安全,小心使用。

LinkedList 不同步

1. 数据结构:内部类实现链表

// 使用引用来实现C中指针的概念
private static class Node<E> {
     E item;
     Node<E> next;
     Node<E> prev;

     Node(Node<E> prev, E element, Node<E> next) {
         this.item = element;
         this.next = next;
         this.prev = prev;
     }
 }

2.方法:无安全保障

public boolean add(E e)
public boolean remove(Object o)
public E set(int index, E element)
public void add(int index, E element)

数组方便查找并且结构简单
链表适合增删修改并且容易扩容

Map

HashMap 不同步

1. 数据结构:内部类构成的数据+链表
需要注意Jdk 1.8中对HashMap的实现做了优化,当链表中的节点数据超过八个之后,该链表会转为红黑树来提高查询效率,从原来的O(n)到O(logn)

	// HashMap实际上是一个“链表散列”的数据结构,即数组和链表的结合体
    static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;
        final K key;
        V value;
        Node<K,V> next;

        Node(int hash, K key, V value, Node<K,V> next) {
            this.hash = hash;
            this.key = key;
            this.value = value;
            this.next = next;
        }

        public final K getKey()        { return key; }
        public final V getValue()      { return value; }
        public final String toString() { return key + "=" + value; }

        public final int hashCode() {
            return Objects.hashCode(key) ^ Objects.hashCode(value);
        }

        public final V setValue(V newValue) {
            V oldValue = value;
            value = newValue;
            return oldValue;
        }

        public final boolean equals(Object o) {
            if (o == this)
                return true;
            if (o instanceof Map.Entry) {
                Map.Entry<?,?> e = (Map.Entry<?,?>)o;
                if (Objects.equals(key, e.getKey()) &&
                    Objects.equals(value, e.getValue()))
                    return true;
            }
            return false;
        }
    }

2. 方法:未有安全措施

    public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }

所以HashMap也是不安全的,还有HashMap红黑树的转变算法,建议手写,面试可能让你手写呢[手动狗头]

HashTable 同步

1. 数据结构:内部类构成的数据+链表

private static class Entry<K,V> implements Map.Entry<K,V> {
        final int hash;
        final K key;
        V value;
        Entry<K,V> next;

        protected Entry(int hash, K key, V value, Entry<K,V> next) {
            this.hash = hash;
            this.key =  key;
            this.value = value;
            this.next = next;
        }

        @SuppressWarnings("unchecked")
        protected Object clone() {
            return new Entry<>(hash, key, value,
                                  (next==null ? null : (Entry<K,V>) next.clone()));
        }

        // Map.Entry Ops

        public K getKey() {
            return key;
        }

        public V getValue() {
            return value;
        }

        public V setValue(V value) {
            if (value == null)
                throw new NullPointerException();

            V oldValue = this.value;
            this.value = value;
            return oldValue;
        }

        public boolean equals(Object o) {
            if (!(o instanceof Map.Entry))
                return false;
            Map.Entry<?,?> e = (Map.Entry<?,?>)o;

            return (key==null ? e.getKey()==null : key.equals(e.getKey())) &&
               (value==null ? e.getValue()==null : value.equals(e.getValue()));
        }

        public int hashCode() {
            return hash ^ Objects.hashCode(value);
        }

        public String toString() {
            return key.toString()+"="+value.toString();
        }
    }

2. 方法:synchronized修饰保证安全

public synchronized V put(K key, V value)
public synchronized V remove(Object key)

要考虑线程安全的情况下,还是不要使用HashMap,可以考虑HashTable,但是HashTable不允许value为空

    public synchronized V put(K key, V value) {
        // Make sure the value is not null
        if (value == null) {
            throw new NullPointerException();
        }
        ---

TreeMap 不同步

与HashMap相比,TreeMap是一个能比较元素大小的Map集合,会对传入的key进行了大小排序。其中,可以使用元素的自然顺序,也可以使用集合中自定义的比较器来进行排序。
TreeMap 是一个有序的key-value集合,它是通过红黑树实现的。
方法

public V put(K key, V value)
public V remove(Object key)

TreeMap的结构更为复杂,但是功能也更为强大,在希望得到有序的数据集可以考虑使用。

Set

HashSet 不同步

1. 数据结构:HashMap

// 没想到吧,是个HashMap
private transient HashMap<E,Object> map;

2.方法:不安全

public boolean add(E e) {
        return map.put(e, PRESENT)==null;
}
public boolean remove(Object o) {
        return map.remove(o)==PRESENT;
}

TreeSet 不同步

1. 数据结构:NavigableMap

private transient NavigableMap<E,Object> m;

2. 方法:不安全

public boolean add(E e) {
        return m.put(e, PRESENT)==null;
}
public boolean remove(Object o) {
        return m.remove(o)==PRESENT;
}

TreeSet是一个包含有序的且没有重复元素的集合,基于TreeMap实现,增加了元素排序的功能。

总结

Vector 同步
ArrayList 不同步
LinkedList 不同步
HashMap 不同步
HashTable 同步
TreeMap 不同步
HashSet 不同步
TreeSet 不同步
那有这么多不同步的容器,我们选起来多麻烦啊,别担心,在java.util.concurrent包下有专门为线程安全而创建的容器可供使用。
ConcurrentHashMap:并发版HashMap
CopyOnWriteArrayList:并发版ArrayList
CopyOnWriteArraySet:并发Set
ConcurrentLinkedQueue:并发队列(基于链表)
ConcurrentLinkedDeque:并发队列(基于双向链表)
ConcurrentSkipListMap:基于跳表的并发Map
ConcurrentSkipListSet:基于跳表的并发Set
ArrayBlockingQueue:阻塞队列(基于数组)
LinkedBlockingQueue:阻塞队列(基于链表)
LinkedBlockingDeque:阻塞队列(基于双向链表)
PriorityBlockingQueue:线程安全的优先队列
SynchronousQueue:读写成对的队列
LinkedTransferQueue:基于链表的数据交换队列
DelayQueue:延时队列


版权声明:本文为dhdhsff原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。