文章目录
JUC笔记
6、集合类不安全问题
6.1 List不安全
多线程操作一个list,往里面add值
public class ListDemo {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
for (int i = 0; i < 20; i++) {
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(list);
},String.valueOf(i)).start();
}
}
}
可能会出现如下错误:ConcurrentModificationException 并发修改异常
在并发下list是不安全的!!
如下展示三种解决方案
1、Vector
public class ListDemo {
public static void main(String[] args) {
Vector<String> vector = new Vector<>();
for (int i = 0; i < 20; i++) {
new Thread(()->{
vector.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(vector);
},String.valueOf(i)).start();
}
}
}
-
Vector源码中操作都是加了synchronized的,是线程安全的
-
而ArrayList,LinkedList没有,是线程不安全的
缺点:
我们都知道Vector是jdk1.0的时候出现的,ArrayList是jdk1.2的时候出现的
-
Vector添加元素,随机查找都是同步方法,需要加锁,增加了开销。扩容机制是直接翻一倍
-
ArrayList添加和查找是非同步方法。扩容机制是在原来基础上增加50%
-
ArrayList是为了在单线程下加快效率出现的
综上,使用Vector不是最优解
2、synchronizedList转换
可以用Collections.synchronizedList将List转换为安全的List
public class ListDemo {
public static void main(String[] args) {
List<String> list = Collections.synchronizedList(new ArrayList<>());
for (int i = 0; i < 20; i++) {
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(list);
},String.valueOf(i)).start();
}
}
}
源码探究:
Collections包下的该方法
SynchronizedList里用synchronized对mutex加锁了
3、CopyOnWriteArrayList
CopyOnWriteArrayList,COW,写时复制
public class ListDemo {
public static void main(String[] args) {
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
for (int i = 0; i < 20; i++) {
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(list);
},String.valueOf(i)).start();
}
}
}
源码探究
写时复制技术:
看看add和remove,写的时候加了锁
读的时候没有加锁
缺点:
- 读旧数据(数据一致性问题): 读的时候不需要加锁,如果读的时候有多个线程正在向CopyOnWriteArrayList添加数据,读还是会读到旧的数据,因为开始读的那一刻已经确定了读的对象是旧对象。CopyOnWrite容器只能保证数据的最终一致性,不能保证数据的实时一致性。
- 内存占用问题: 写时复制技术在写操作时,内存会有两个对象,旧的和新的,所以会有内存占用问题
总结:
CopyOnWrite并发容器适合
- 用于读多写少的并发场景比如:白名单,黑名单等
- 对数据一致性要求不高可以考虑使用COW容器
6.2 Set不安全
回顾一下HashSet:
HashSet就是一个Hashmap,但是存进去的值放在hashmap的键上,值是一个空对象
public class SetDemo {
public static void main(String[] args) {
Set<String> set = new HashSet<>();
for (int i = 1; i <=30 ; i++) {
new Thread(()->{
set.add(UUID.randomUUID().toString().substring(0,5));
System.out.println