同步容器類存在的問題
同步類容器都是線程安全的,但在某些場景下可能需要加鎖來保護複合操作,在複合操作,如:迭代、跳轉已經條件運算中,這些操作可能會表現出意外的行爲,最經典的便是ConcurrentModificationException,原因是當容器迭代的過程中,被併發的修改了內容,這是由於早起迭代器設計的時候並沒有考慮併發修改的原因。
開門見山,我們直接來看兩個例子:
public class UseVector {
/**
* 遍歷向量
* @return
*/
public void travelVector(Vector<String> list) {
for (String str: list) {
System.out.println(str);
}
}
/**
* 該方法的主要作用是:從數組中移除指定的元素
* @param list
* @param target
* @return 拋出異常java.util.ConcurrentModificationException
*/
public Collection<String> removeOne(Vector<String> list, String target) {
if(target == null || "".equals(target)) {
return list;
}
//拋出java.util.ConcurrentModificationException異常
for(String str : list) {
if(target.equals(str)) {
list.remove(str);
}
}
return list;
}
public static void main(String[] args) {
//定義了一個動態數組,並向動態數組中添加3個元素
Vector<String> vector = new Vector<String>();
vector.add("1");
vector.add("2");
vector.add("3");
UseVector demo = new UseVector();
//遍歷這個數組
demo.travelVector(vector);
//從這個動態數組中移除3這個元素
demo.removeOne(vector, "3");
}
}
該案例創建了一個動態數組Vector並向動態數組中添加3個元素,同時該類有兩個方法,分別是travelVector()遍歷動態數組方法和removeOne從動態數組中移除掉目標元素(target)的方法。運行上訴代碼,程序拋出異常:
java.util.ConcurrentModificationException
at java.util.Vector$Itr.checkForComodification(Vector.java:1210)
at java.util.Vector$Itr.next(Vector.java:1163)
at com.springchang.threadcore.test.UseVector.removeOne(UseVector.java:31)
at com.springchang.threadcore.test.UseVector.main(UseVector.java:51)
果不其然,該代碼拋出了在併發編程中常見的ConcurrentModificationException異常,並追蹤上述代碼得知是removeOne方法中的list.remove(str);這一行拋出的異常。咱暫時先不管爲啥拋出的異常,先想想有沒有其他代替方案解決我們的需求:遍歷我們的數組,當數組元素等於目標值時將其移除。上述循環數組用的是增強for循環的方法,我們不妨換成另一種方式,用Iterator迭代器試一試。
public class UseVector {
/**
* 通過迭起器方法移除一個元素
* @param list
* @param target
* @return 拋出異常java.util.ConcurrentModificationException
*/
public Collection<String> removeOneByIt(Vector<String> list, String target) {
if(target == null || "".equals(target)) {
return list;
}
Iterator<String> it = list.iterator();
while(it.hasNext()) {
String str = it.next();
if(target.equals(str)) {
list.remove(str);
}
}
return list;
}
public static void main(String[] args) {
//定義了一個動態數組,並向動態數組中添加3個元素
Vector<String> vector = new Vector<String>();
vector.add("1");
vector.add("2");
vector.add("3");
UseVector demo = new UseVector();
//從這個動態數組中移除3這個元素
demo.removeOneByIt(vector, "3");
}
}
悲傷的是Iterator迭代器並沒有解決我們的問題,同樣的Iterator也拋出了ConcurrentModificationException異常。在JDK 1.5之前有沒有解決的方案呢?有的,請看以下代碼:
public class UseVector {
/**
* 安全的從數組中移除元素的方法
* @param list
* @param target
* @return 不會拋出拋出異常java.util.ConcurrentModificationException
*/
public Collection<String> removeOneBySafe(Vector<String> list, String target) {
if(target == null || "".equals(target)) {
return list;
}
//該方法是單現成的,所以安全
for(int i = 0; i < list.size(); i++) {
if(target.equals(list.get(i))) {
list.remove(target);
}
}
return list;
}
public static void main(String[] args) {
//定義了一個動態數組,並向動態數組中添加3個元素
Vector<String> vector = new Vector<String>();
vector.add("1");
vector.add("2");
vector.add("3");
UseVector demo = new UseVector();
//從這個動態數組中移除3這個元素
demo.removeOneBySafe(vector, "3");
System.out.println("移除後的數組內容:" + vector);
}
}
如上所述,上述代碼並沒有使用增強的for循環來遍歷數組,也不用Iterator迭代器來遍歷數組。那麼前兩者與最後一個例子的區別在哪裏呢?這是因爲無論是增強的for循環還是迭代器在運行的時候迭代器多了一個線程來控制遊標位置,控制遊標當前的指向位置,而案例3是單線程的,只有一個i下標指向當前元素位置,讀和寫操作都在同一線程中完成的,故而安全。
同步類容器的使用
同步類容器如Vector,Hashtable等這些容器的同步功能都是有JDK的Collections.synchronized***等工廠方法去創建實現的。其底層機制用synchronized關鍵字對每個公用的方法都進行同步,或者使用Object mutex對象鎖的機制使得每次只能有一個線程訪問容器的狀態。使用的代碼如下:
public static void main(String[] args) {
//定義了一個動態數組,並向動態數組中添加3個元素
Vector<String> vector = new Vector<String>();
vector.add("1");
vector.add("2");
vector.add("3");
UseVector demo = new UseVector();
Collection<String> col = Collections.synchronizedCollection(vector); //先將線程不安全的同步類vectory轉爲線程安全的col
System.out.println(col);
}
要想把同步類變成線程安全,先使用Collections.synchronized***方法將vectory轉爲線程安全的Colection接口的子類,然後在來操作該方法返回的引用,其實現的原理是在底層給對象加鎖。