線程安全的列表有
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之線程安全的容器