今天在研究iOS線程鎖的時候,突然想起了Java中存在線程安全和線程非安全的說法 ;我們常見的線程安全和非安全無法就是以下三對
ArrayList (線程非安全) <–> Vector(線程安全)
HashMap (線程非安全) <–>HashTable (線程安全)
StringBuilder (線程非安全) <–>StringBuffer (線程安全)
看了很多文章,最終還是寫代碼解決了疑惑 。。。
首先我們來看一下 ArrayList 和Vector 這兩個集合類,線程安全與線程非安全那麼有個前提就是多線程的情況下,單線程說線程安全或線程非安全那是扯犢子 ,先上代碼
public class ListAVTest {
ArrayList<String> ar = new ArrayList<>();
// Vector<String> ar = new Vector<>();
public static void main(String[] args) {
ListAVTest sBbTest = new ListAVTest();
new Thread() {
public void run() {
sBbTest.test1();
};
}.start();
new Thread() {
public void run() {
sBbTest.test2();
};
}.start();
}
private void test1() {
synchronized (ar) {
for (int i = 0; i < 50; i++) {
ar.add("A");
System.out.println(ar.toString());
}
}
}
private void test2() {
synchronized (ar) {
for (int i = 0; i < 50; i++) {
ar.add("1");
System.out.println(ar.toString());
}
}
}
}
兩個線程同時操作一個Vector ,很順利。。。因爲Vector線程安全,多少個線程同時操作都可以 。。 那麼Vector的debug我就不截圖了 , 我們來看下,把 Vector換成ArrayList,看看debug
public class ConcurrentModificationException
extends
RuntimeException
當方法檢測到對象的併發修改,但不允許這種修改時,拋出此異常。
例如,某個線程在 Collection 上進行迭代時,通常不允許另一個線性修改該 Collection。通常在這些情況下,迭代的結果是不確定的。如果檢測到這種行爲,一些迭代器實現(包括 JRE 提供的所有通用 collection 實現)可能選擇拋出此異常。執行該操作的迭代器稱爲快速失敗 迭代器,因爲迭代器很快就完全失敗,而不會冒着在將來某個時間任意發生不確定行爲的風險。
注意,此異常不會始終指出對象已經由不同 線程併發修改。如果單線程發出違反對象協定的方法調用序列,則該對象可能拋出此異常。例如,如果線程使用快速失敗迭代器在 collection 上迭代時直接修改該 collection,則迭代器將拋出此異常。
注意,迭代器的快速失敗行爲無法得到保證,因爲一般來說,不可能對是否出現不同步併發修改做出任何硬性保證。快速失敗操作會盡最大努力拋出 ConcurrentModificationException。因此,爲提高此類操作的正確性而編寫一個依賴於此異常的程序是錯誤的做法,正確做法是:ConcurrentModificationException 應該僅用於檢測 bug。
看來 ArrayList真的是線程非安全了 ,如何解決了 ?
private void test1() {
synchronized (ar) {
for (int i = 0; i < 50; i++) {
ar.add("A");
System.out.println(ar.toString());
}
}
}
使用這個關鍵字 synchronized 就可以 ,用這個關鍵字修飾被訪問的集合對象
然後我們來看下 HashMap 和 HashTable 這一對 ,同樣代碼附上 。。
public class HashTSTest {
HashMap<String, String> sb = new HashMap();
// Hashtable<String, String> sb = new Hashtable();
public static void main(String[] args) {
HashTSTest sBbTest = new HashTSTest();
new Thread() {
public void run() {
sBbTest.test1();
};
}.start();
new Thread() {
public void run() {
sBbTest.test2();
};
}.start();
}
private void test1() {
for (int i = 0; i < 50; i++) {
sb.put("a"+i, ""+i);
System.out.println(sb.toString());
}
}
private void test2() {
for (int i = 0; i < 50; i++) {
sb.put("b"+i, ""+i);
System.out.println(sb.toString());
}
}
}
結果肯定是HashMap報異常 ,而HashTable 正常執行
**
當然報的異常跟上面的是一樣的 ,因爲他們是一個祖宗 ,打開源碼其實不難發現 HashTable 和Vector 的大部分方法都被synchronized這個關鍵字修飾了,所以 。。。
其實我今天要說的重頭戲是
StringBuffer和 StringBuilder;剛剛我們在上面說過StringBuffer線程安全,也就是StringBuffer的大部分方法被 synchronized 這個關鍵字修飾,是不是
像上面代碼那樣多個線程訪問報異常了 ???? 其實我一開始是兩個線程,後來加到了6個線程
**
public class StringBBTest {
// StringBuffer sb = new StringBuffer();
StringBuilder sb = new StringBuilder();
public static void main(String[] args) {
StringBBTest sBbTest = new StringBBTest();
new Thread() {
public void run() {
sBbTest.test1();
};
}.start();
new Thread() {
public void run() {
sBbTest.test2();
};
}.start();
new Thread() {
public void run() {
sBbTest.test3();
};
}.start();
new Thread() {
public void run() {
sBbTest.test4();
};
}.start();
new Thread() {
public void run() {
sBbTest.test5();
};
}.start();
new Thread() {
public void run() {
sBbTest.test6();
};
}.start();
}
private void test1() {
for (int i = 0; i < 50; i++) {
sb = sb.append("A");
System.out.println(sb.toString());
}
}
private void test2() {
for (int i = 0; i < 50; i++) {
sb = sb.append("1");
System.out.println(sb.toString());
}
}
private void test3() {
for (int i = 0; i < 50; i++) {
sb = sb.append("=");
System.out.println(sb.toString());
}
}
private void test4() {
for (int i = 0; i < 50; i++) {
sb = sb.append("+");
System.out.println(sb.toString());
}
}
private void test5() {
for (int i = 0; i < 50; i++) {
sb = sb.append("}");
System.out.println(sb.toString());
}
}
private void test6() {
for (int i = 0; i < 50; i++) {
sb = sb.append("~");
System.out.println(sb.toString());
}
}
}
不管我使用的是insert方法還是使用append方法 ,都不報異常 。。。。。。 說好了StringBuilder線程不安全 , 爲神馬會出現這種情況了 ? 支持多線程訪問 。。。。。
private void test1() {
for (int i = 0; i < 50; i++) {
sb = sb.append("A");
System.out.println(sb.toString());
}
}
甚至是 這樣用 依然不報錯 。。。。。。 沒有報異常 。。。。
我們卻被灌輸StringBuilder 線程不安全 和StringBuffer線程安全的思想 。。。。。 事實卻不是 。。。。。。。。。。。 。。。。。。。。。 等待解決或明示,到底錯在哪裏了 ?