package main.test;
import java.util.ArrayList;
import java.util.List;
/**
* @Auther: wdq
* @Date: 2020/4/20 08:57
* @Description:
*/
public class ListnoSafe {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
}
}
/*
結果爲:
a
b
c
*/
上面這個demo是我們平時用的最多的,每次運行都沒有出過錯,也太可能會出錯。
所謂的線程不安全,就是加上線程之後,list會不安全,即程序會給你搗亂。
看下面這個例子:
package main.test;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
/**
* @Auther: wdq
* @Date: 2020/4/20 08:57
* @Description:
*/
public class ListnoSafe {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
for (int i = 0; i < 3; i++) {
new Thread(() -> {
list.add(UUID.randomUUID().toString().substring(0,8));
System.out.println(list);
}, String.valueOf(i)).start();
}
}
}
/*
分別執行了三次:
第一次:
[null, null, 7339634e]
[null, null, 7339634e]
[null, null, 7339634e]
第二次:
[c4b59b80, 2fd150dd, 7a38b4ae]
[c4b59b80, 2fd150dd, 7a38b4ae]
[c4b59b80, 2fd150dd, 7a38b4ae]
第三次:
[c9e6745c, cd0769c7]
[c9e6745c, cd0769c7]
[c9e6745c, cd0769c7]
按正常的思維來講,應該有三個元素,但是每次執行的結果都不一樣
原因是cpu太快了,讀寫的順序亂了。
*/
如果把循環3次改成30次,那麼有很大機率會報錯 java.util.ConcurrentModificationException;
這就是list線程不安全。
解決方法:
1、把ArrayList<>();改成Vector<>();
因爲vector是線程安全的,它的add方法的源碼:
public synchronized boolean add(E e) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = e;
return true;
}
因爲有synchronized,所以是線程安全的,能保證數據一致性但是性能卻慢了。
2、把new ArrayList<>();改成Collections.synchronizedList(new ArrayList<>());
線程安全而且性能不慢。
3、把new ArrayList();改成new CopyOnWriteArrayList<>();
JUC的方法。
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();
}
}
CopyOnWriteArrayList容器即寫時複製。往一個容器添加元素的時候,不直接往當前容器object[]添加,而是先將當前容器object[]進行copy,複製出一個新的容器object[] newElements,然後新的容器object[] newElements裏添加元素,之後再將原容器的引用指向新的容器setArray(newElments);。這樣做的好處是可以對copyonwrite容器進行併發的讀而不需要加鎖,因爲當前容器不會添加任何元素。所以copyonwrite容器也是一種讀寫分離的思想,讀和寫不同的容器。