Collection 应该是大家在编程中使用的最多的,在单线程编程中一般不会出现问题,但是到了多线程的时代,可就不一定了。
List 示例代码
/**
* 集合类不安全的问题
* ArrayList
*/
public class ContainerNotSafeDemo {
public static void main(String[] args) {
List list = new ArrayList();
//多线程同时访问
for (int i = 0; i {
list.add(UUID.randomUUID().toString().substring(0, 8));
System.out.println(list);
}).start();
}
}
}
运行结果:并发修改异常
导致原因
并发争抢修改导致,多线程访问 List 导致数据冲突
解决方案
方案 1
不使用 ArrayList,换成 Vector
public class ContainerNotSafeDemo {
public static void main(String[] args) {
// 更换为 Vector
List list = new Vector();
//多线程同时访问
for (int i = 0; i {
list.add(UUID.randomUUID().toString().substring(0, 8));
System.out.println(list);
}).start();
}
}
}
虽然更换为 Vector 可以解决问题,但是 Vector 的源码是使用 synchronized 关键字解决问题的,所以并发量大大下降,不推荐使用。
方案 2
换成 Collections.synchronizedList(new ArrayList());
public class ContainerNotSafeDemo {
public static void main(String[] args) {
List list = Collections.synchronizedList(new ArrayList());;
//多线程同时访问
for (int i = 0; i {
list.add(UUID.randomUUID().toString().substring(0, 8));
System.out.println(list);
}).start();
}
}
}
Collections 工具类提供接口,可以将不安全的集合转换为安全的,其实用的技术就是 synchronized。
方案 3
使用 JUC 包下的 new CopyOnWriteArrayList();
public class ContainerNotSafeDemo {
public static void main(String[] args) {
List list = new CopyOnWriteArrayList();
//多线程同时访问
for (int i = 0; i {
list.add(UUID.randomUUID().toString().substring(0, 8));
System.out.println(list);
}).start();
}
}
}
写时复制
写时复制 copyOnWrite 容器即写时复制的容器 往容器添加元素的时候,不直接往当前容器object[]添加,而是先将当前容器object[]进行,copy 复制出一个新的 object[] newElements 然后向新容器 object[] newElements 里面添加元素添加元素后,再将原容器的引用指向新的容器 setArray(newElements);
这样的好处是可以对 copyOnWrite 容器进行并发的读,而不需要加锁因为当前容器不会添加任何容器。所以 copyOnwrite 容器也是一种读写分离的思想,读和写不同的容器。
Set、Map
Set 的底层使用的 HashMap,所以将上面 List 的代码部分换成 Set、Map,将会报同样错,可以使用上面的三种方法去解决这个问题,我就不在这里赘述。JUC 包中提供 CopyOnWriteHashSet 和 ConcurrentHashMap,使用方法类似。
作者:楚瑞涛