本篇文章不会讲的太深入,原因有2,其一怕别人看不懂,其二,我也讲不出来;
单线程下List集合类是完全ok的,但在多线程下可能就不太ok了;
public class ListTest {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
for (int i = 0; i < 30; i++) {
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(list);
},String.valueOf(i)).start();
}
}
}
运行上面代码发现会报 java.util.ConcurrentModificationException
错,并发修改异常,会报错才是正常的,原因是因为当一条线程对 list 修改时,另一条线程进来了,先将调用list.add()方法,把modConut 版本号修改了,上一条线程比对版本号时不相等,所以快速失败了;
简单理解就是修改前后被别人插队了,版本号没对应上;
解决方案有3
-
第一种:使用
List<String> list = new Vector<>();
Vector(jdk1.0)出来的版本要比List(jdk1.2)早,Vector在所有方法都很粗暴的加了synchronize来保证线程安全;
看一个add的方法:
public synchronized boolean add(E e) { modCount++; ensureCapacityHelper(elementCount + 1); elementData[elementCount++] = e; return true; }
-
第二种:使用
List<String> list = Collections.synchronizedList(new ArrayList<>());
这个方法是集合工具类Collections提供的,其实它只是在集合类上简单封装了一下,加了synchronized代码块,看一个List add的方法;
public void add(int index, E element) { //mutex 表示当前 List对象 synchronized (mutex) { //调用List add方法 list.add(index, element); } }
-
第三种:使用
List<String> list1 = new CopyOnWriteArrayList<>();
这个是并发包下提供的,是个高级货,按照字面上翻译就是写的时候复制,用了锁+数组拷贝+volatile实现的,意思是读的时候随便读,写的时候先加锁,然后复制一份出来写,写完再将复制出来的一份替换掉原来的那一份,这样就不会影响到原来的list,是不是有点高级;
volatile还要说一下,它可以保证可见性,替换回去之后其他线程会重新读最新的;如果只是在原来的数组上修改几个元素是无法触发可见性的,必须通过修改数组的内存地址才行,也就说要对数组进行重新赋值才行。
看一个add的方法;
// 添加元素到数组尾部 public boolean add(E e) { final ReentrantLock lock = this.lock; // 加锁 lock.lock(); try { // 得到所有的原数组 Object[] elements = getArray(); int len = elements.length; // 拷贝到新数组里面,新数组的长度是 + 1 的,因为新增会多一个元素 Object[] newElements = Arrays.copyOf(elements, len + 1); // 在新数组中进行赋值,新元素直接放在数组的尾部 newElements[len] = e; // 替换掉原来的数组 setArray(newElements); return true; // finally 里面释放锁,保证即使 try 发生了异常,仍然能够释放锁 } finally { lock.unlock(); } }
还有Map和Set的集合类,Collections集合工具类和并发包下也提供的对应线程同步的方法,原理和List是一模一样!
总结
List 集合类不安全的3种解决方法:
//所有方法都加了synchronize
List<String> list1 = new Vector<>();
//封装了一层,加了同步代码块
List<String> list2 = Collections.synchronizedList(new ArrayList<>());
//加锁 + 数组拷贝 + volatile
List<String> list3 = new CopyOnWriteArrayList<>();