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容器也是一种读写分离的思想,读和写不同的容器。