线程安全的列表有
Vector
,CopyOnWriteArrayList
两种,区别则主要在实现方式上,对锁的优化上; 后者主要采用的是copy-on-write
思路,修改时,拷贝一份出来,修改完成以后替换html
Vector
实现vector 保证线程安全的原理比较简单粗暴,直接在方法上加锁java
public synchronized E get(int index) { if (index >= elementCount) throw new ArrayIndexOutOfBoundsException(index); return elementData(index); }
public synchronized E set(int index, E element) { if (index >= elementCount) throw new ArrayIndexOutOfBoundsException(index); E oldValue = elementData(index); elementData[index] = element; return oldValue; }
public synchronized boolean add(E e) { modCount++; ensureCapacityHelper(elementCount + 1); elementData[elementCount++] = e; return true; }
public synchronized int size() { return elementCount; }
从上面几个最最多见的几个方法,就能够看出,这个实现很是的简单粗暴,所有上锁,确定是线程安全的问题了;相应的问题也很明显,效率妥妥的够了,即使全是读操做,都会有阻塞竞争,基本上彻底是无法忍的数组
CopyOnWriteArrayList
实现使用了
copyOnWrite
机制,一句话,读时直接读,在修改时,先拷贝一份出来,在拷贝上进行修改,完成以后替换掉以前的引用安全
下面主要看一下几个最多见的方法,是如何实现的,以此来研究下这套机制的玩法多线程
public int size() { return getArray().length; } /** The array, accessed only via getArray/setArray. */ private transient volatile Object[] array; /** * Gets the array. Non-private so as to also be accessible * from CopyOnWriteArraySet class. */ final Object[] getArray() { return array; }
对比一下 ArrayList 的获取size方法,有一个size属性记录的是链表的长度,直接返回的这个值;而CopyOnWriteArrayList 则是每次都去实时查数组的长度并发
/** * The size of the ArrayList (the number of elements it contains). * * @serial */ private int size; public int size() { return size; }
为何这么作 ?高并发
/** * {@inheritDoc} * * @throws IndexOutOfBoundsException {@inheritDoc} */ public E get(int index) { return get(getArray(), index); } private E get(Object[] a, int index) { return (E) a[index]; }
// ArrayList public E get(int index) { rangeCheck(index); return elementData(index); } E elementData(int index) { return (E) elementData[index]; }
和上面相同,一样是先调用 getArray() 方法,而后在进行相应的操做,若是不这么作,直接如 ArrayList 同样的调用方式时(以下)性能
若是是上面的执行逻辑,则不会如此,由于操做的依然是老的那个数组对应的引用;当发生修改时,是在新的数组上进行的优化
接下来则看一下具体的修改方法,是否是确实在新的数组上进行的操做,源码以下:网站
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(); } }
关注几点:
修改内容的值时,一样是先加锁,再修改,确保每次修改都是串行进行的;须要注意的一点是
public E set(int index, E element) { final ReentrantLock lock = this.lock; lock.lock(); try { Object[] elements = getArray(); E oldValue = get(elements, index); if (oldValue != element) { int len = elements.length; Object[] newElements = Arrays.copyOf(elements, len); newElements[index] = element; setArray(newElements); } else { // Not quite a no-op; ensures volatile write semantics setArray(elements); } return oldValue; } finally { lock.unlock(); } }
Vector
: 不管读写,所有加上了同步锁,致使多线程访问or修改时,锁的竞争,效率较低
CopyOnWriteArrayList
: 读不加锁,在修改时,加锁保证每次只有一个线程在修改列表;且修改的逻辑都是先拷贝一个副本出来,而后在副本上进行修改,最后在替换列表中数组的引用
CopyOnWriteArraySet
: 内部数组其实就是一个 CopyOnWriteArrayList
, 相关方法也是直接来自 CopyOnWriteArrayList
线程安全的Map则主要是
HashTable
ConcurrentHashMap
, 后者采用了分段锁机制来提升并发访问的性能
在便利时,操做Map的几种场景分析
在遍历时,修改Map的引用(即用一个新的map替换这个值)
在遍历时,修改Map中的元素值
在遍历时,新增or删除元素
同 Vector 同样,经过对全部的方法添加 synchronized
关键字来确保的线程安全;缺点也很明显,效率低,具体的几个方法源码以下,很少加说明了
public synchronized int size() { return count; } public synchronized Enumeration<K> keys() { return this.<K>getEnumeration(KEYS); } public synchronized Enumeration<V> elements() { return this.<V>getEnumeration(VALUES); } public synchronized V get(Object key) { Entry<?,?> tab[] = table; int hash = key.hashCode(); int index = (hash & 0x7FFFFFFF) % tab.length; for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) { if ((e.hash == hash) && e.key.equals(key)) { return (V)e.value; } } return null; } public synchronized V put(K key, V value) { // Make sure the value is not null if (value == null) { throw new NullPointerException(); } // Makes sure the key is not already in the hashtable. Entry<?,?> tab[] = table; int hash = key.hashCode(); int index = (hash & 0x7FFFFFFF) % tab.length; @SuppressWarnings("unchecked") Entry<K,V> entry = (Entry<K,V>)tab[index]; for(; entry != null ; entry = entry.next) { if ((entry.hash == hash) && entry.key.equals(key)) { V old = entry.value; entry.value = value; return old; } } addEntry(hash, key, value, index); return null; }
一个ConcurrentHashMap由多个segment组成,每个segment都包含了一个HashEntry数组的hashtable, 每个segment包含了对本身的hashtable的操做,好比get,put,replace等操做,这些操做发生的时候,对本身的hashtable进行锁定。因为每个segment写操做只锁定本身的hashtable,因此可能存在多个线程同时写的状况,性能无疑好于只有一个hashtable锁定的状况
简单来说,就是每一个 segment 的操做都是加锁的;而多个 segment 的操做能够是并发的
详解能够参考: Java集合---ConcurrentHashMap原理分析
更多能够参考我的网站: 一灰的我的博客网站之Java之线程安全的容器