1.引言
在多線程的環境中,如果想要使用容器類,就需要注意所使用的容器類是否是線程安全的。在最早開始,人們一般都在使用同步容器(Vector,HashTable),其基本的原理,就是針對容器的每一個操作,都添加synchronized來進行同步,此種方式盡管簡單,但是其性能是非常地下的,所以現在已經不怎么使用了。人們普遍會使用并發的容器,在JDK1.5之后,針對基于散列的Map,提供了新的ConcurrentHashMap,針對迭代需求的list,提供了CopyOnWriteList.
2.ConcurrentHashMap
ConcurrentHashMap使用了一種分段鎖的策略,使得map可以被多個讀寫線程并行的訪問。基本可以認為是將map的key值范圍分為多個段,這樣多個線程訪問的時候,他們需要訪問的key值在不同的段,所以可以互相不干擾,
使用不同的鎖對象來進行并發操作。
ConcurrentHashMap在使用迭代器遍歷的時候,不會報ConcurrentModificationException,提供“弱一致性”。在遍歷迭代的時候,也會反應出在迭代器創建之后的數據修改。
應用場景
針對一般的有并發需求的map,都應該使用ConcurrentHashMap. 它的性能優于Hashtable和synchronizedMap。
缺點
不是強一致性
由于是采用的分段鎖策略,所以一些數據不能保證強一致性。比如針對容器的size方法,由于線程A只是獲得了自己的分段鎖,它不能保證其他線程對容器的修改,所以此時線程A可能使用size,會得到不穩定數據。這種情況下,是對同步性能的一些折衷。如果業務需求必須滿足強一致性,才會需要對整個Map進行鎖操作。并發容器的弱一致性的概念背景,是在高并發情況下,容器的size和isEmpty之類的方法,用處不大,所以可以忍受數據不一致性。
3.CopyOnWrite容器
在JDK1.5之后,java.util.concurrent引入了兩個CopyOnWrite容器,分別是CopyOnWriteArrayList, CopyOnWriteArraySet.
顧名思義,CopyOnWrite就是在write操作之前,對集合進行Copy,針對容器的任意改操作(add,set,remove之類),都是在容器的副本上進行的。并且在修改完之后,將原容器的引用指向修改后的副本。
如果線程A得到容器list1的iterator之后,線程B對容器list1加入了新的元素,由于線程A獲得list1的iterator時候在線程B對list1進行修改前,所以線程A是看不到線程B對list1進行的任何修改。
具體到源碼,看一下add操作
/**
* Appends the specified element to the end of this list.
*
* @param e element to be appended to this list
* @return {@code true} (as specified by {@link Collection#add})
*/
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();
}
}
可以發現,寫操作是會有個鎖lock.lock(),這保證了多線程寫操作之間的同步。之后使用Arrays.copyOf來進行數組拷貝,在修改完成后,setArray(newElements)將原來的數組引用指向新的數組。
應用場景
經常用在讀多寫少的場景,比如EventListener的添加,網站的category列表等偶爾修改,但是需要大量讀取的情景。
缺點
1.數據一致性的問題。
因為讀操作沒有用到并發控制,所以可能某個線程讀到的數據不是實時數據。
2.內存占用問題。
因為寫操作會進行數據拷貝,并且舊有的數據引用也可能被其他線程占有一段時間,這樣針對數據比較大的情況,可能會占用相當大的內存。并且由于每次寫操作都會占用額外的內存,最后進行的GC時間也可能相應的增加。
4.HashSet
HashSet內部是用的HashMap,只用了HashMap的key。
同步集合
傳統集合類在并發訪問時的問題說明:死鎖死循環
傳統方式下用Collections工具類提供的synchronizedCollection方法來獲得同步集合,分析該方法的實現源碼
Java5中提供了如下一些同步集合類:
通過看java.util.concurrent包下的介紹可以知道有哪些并發集合
ConcurrentHashMap
CopyOnWriteArrayList
CopyOnWriteArraySet
傳統方式下的Conllection在迭代結合時,不允許對集合進行修改。
根據AbstractList的checkForComodification方法的源碼,分析產生ConcurrentModificationException異常的原因。
public class User implements Cloneable{
private String name;
private int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
public boolean equals(Object obj) {
if(this == obj) {
return true;
}
if(!(obj instanceof User)) {
return false;
}
User user = (User)obj;
//if(this.name==user.name && this.age==user.age)
if(this.name.equals(user.name)
&& this.age==user.age) {
return true;
}
else {
return false;
}
}
public int hashCode() {
return name.hashCode() + age;
}
public String toString() {
return "{name:'" + name + "',age:" + age + "}";
}
public Object clone() {
Object object = null;
try {
object = super.clone();
} catch (CloneNotSupportedException e) {}
return object;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
}