网站用户互动,软件开发工具链,wordpress全屏弹窗插件,青海环保网站建设公司异常简介
ConcurrentModificationException#xff08;并发修改异常#xff09;是基于java集合中的 快速失败#xff08;fail-fast#xff09; 机制产生的#xff0c;在使用迭代器遍历一个集合对象时#xff0c;如果遍历过程中对集合对象的内容进行了增删改#xff0c;…异常简介
ConcurrentModificationException并发修改异常是基于java集合中的 快速失败fail-fast 机制产生的在使用迭代器遍历一个集合对象时如果遍历过程中对集合对象的内容进行了增删改就会抛出该异常。 快速失败机制使得java的集合类不能在多线程下并发修改也不能在迭代过程中被修改。
异常原因
示例代码
val elements : MutableListInt mutableListOf()
for ( i in 0..100) {//添加元素elements.add(i)
}val thread Thread {//线程一读数据elements.forEach {Log.i(testTag, it.toString())}
}val thread2 Thread {//线程二写入数据for (i in 1..100) {elements.add(i)}
}thread.start()
thread2.start()抛出异常java.util.ConcurrentModificationExceptionat java.util.ArrayList$Itr.next(ArrayList.java:860)异常原因是什么呢 modCount表示list集合结构上被修改的次数 expectedModCount表示对ArrayList修改次数的期望值(在开始遍历元素之前记录的) list的for循环中是通过Iterator迭代器遍历访问集合内容在遍历过程中会使用到modCount变量如果在遍历过程期间集合内容发生变化则会改变modCount的数值每当迭代器使用next() 遍历下一个元素之前都会检测 modCount 变量是否为 expectedModCount 值相等的话就返回遍历否则抛出异常(ConcurrentModificationException)终止遍历。
而在我们的示例代码中线程二在调用add方法的时候modCount1导致线程一在遍历的时候modCountexpectedModCount所以抛出了ConcurrentModificationException
解决方法
那在多线程下我们需要集合支持并发读写怎么实现呢
使用Collections.synchronizedList给集合加锁
val elements : MutableListInt Collections.synchronizedList(mutableListOf())
...
val thread Thread {//线程一读数据synchronized(elements) {//使用Iterator遍历时需要手动加锁elements.forEach {Log.i(testTag, it.toString())}}
}
...原理 以组合的方式将对 List 的接口方法操作委托给传入的 list 对象并且对所有的接口方法对象加锁得到并发安全性。通过组合的方式对传入的list对象的getsetadd等方法加synchronized同步锁但是对于需要用到iterator迭代器的时候需要手动加锁
public static T ListT synchronizedList(ListT list) {return (list instanceof RandomAccess ?new SynchronizedRandomAccessList(list) :new SynchronizedList(list));
}static T ListT synchronizedList(ListT list, Object mutex) {return (list instanceof RandomAccess ?new SynchronizedRandomAccessList(list, mutex) :new SynchronizedList(list, mutex));
}SynchronizedCollection(CollectionE c) {this.c Objects.requireNonNull(c);//需要加锁的对象这里指自己mutex this;
}static class SynchronizedListEextends SynchronizedCollectionEimplements ListE {private static final long serialVersionUID -7754090372962971524L;final ListE list;SynchronizedList(ListE list) {super(list);this.list list;}SynchronizedList(ListE list, Object mutex) {super(list, mutex);this.list list;}//在list提供的方法外加了synchronized同步锁public boolean equals(Object o) {if (this o)return true;synchronized (mutex) {return list.equals(o);}}public int hashCode() {synchronized (mutex) {return list.hashCode();}}public E get(int index) {synchronized (mutex) {return list.get(index);}}public E set(int index, E element) {synchronized (mutex) {return list.set(index, element);}}public void add(int index, E element) {synchronized (mutex) {list.add(index, element);}}public E remove(int index) {synchronized (mutex) {return list.remove(index);}}public int indexOf(Object o) {synchronized (mutex) {return list.indexOf(o);}}public int lastIndexOf(Object o) {synchronized (mutex) {return list.lastIndexOf(o);}}public boolean addAll(int index, Collection? extends E c) {synchronized (mutex) {return list.addAll(index, c);}}//使用iterator迭代器的时候需要手动加锁public ListIteratorE listIterator() {return list.listIterator(); // Must be manually synched by user}public ListIteratorE listIterator(int index) {return list.listIterator(index); // Must be manually synched by user}
优点可以使非线程安全的集合如Arraylist封装成线程安全的集合并且相对CopyOnWriteArrayList写操作性能较好 缺点在任何操作之前都需要加同步锁使用iterator还需要手动加锁才能保证并发读写安全 2. 使用支持并发读写的CopyOnWriteArrayList
val elements : CopyOnWriteArrayListInt CopyOnWriteArrayList()原理
public E get(int index) {return get(getArray(), index);
}public boolean add(E e) {synchronized (lock) {Object[] elements getArray();int len elements.length;Object[] newElements Arrays.copyOf(elements, len 1);newElements[len] e;setArray(newElements);return true;}
}
...读操作直接读数组对应位置的数据 写操作以add方法为例在执行add方法时会先对集合对象添加同步锁然后创建一个len1的数组再把旧数组中数据复制添加到新数组中最后把新数组替换掉老数组 优点读操作效率高无加锁操作 缺点写操作每次都需要复制一份新数组性能较差
拓展多线程下怎么做好单例的设计
懒汉式单例
在需要的时候再去创建实例。 锁它锁它锁它
同步锁
Java
public class SingleTon {private static volatile SingleTon instance;private SingleTon() {}public static SingleTon getInstance() {synchronized (SingleTon.class) {if (instance null) {instance new SingleTon();}} return instance;}
}Kotlin
class SingleTon {companion object {private var instance: SingleTon? nullSynchronizedfun getInstance(): SingleTon {if (instance null) {instance SingleTon()}return instance!!}}
}优点线程安全可以延时加载。 缺点调用效率不高有锁且需要先创建对象。
DCL
为提升性能减小同步锁的开销避免每次获取实例都需要经过同步锁可以使用双重检测判断实例是否已经创建。 Java
public class SingleTon {private static volatile SingleTon4 instance;private SingleTon() {}public static SingleTon getInstance() {if (instance null) {synchronized (SingleTon.class) {if (instance null) {instance new SingleTon4、();}}}return instance;}
}Kotlin
class SingleTon4 {companion object {val instance by lazy(mode LazyThreadSafetyMode.SYNCHRONIZED) {SingleTon4()}}
}饿汉式单例
在类被加载的时候就把Singleton实例给创建出来供使用以后不再改变。 Java
public class SingleTon {private static SingleTon singleTon new SingleTon();private SingleTon() {}public static SingleTon getInstance() {return singleTon;}
}Kotlin
object SingleTon1 {}优点实现简单 线程安全调用效率高无锁且对象在类加载时就已创建可直接使用。 缺点可能在还不需要此实例的时候就已经把实例创建出来了不能延时加载在需要的时候才创建对象。
静态内部类
静态内部类只有被主动调用的时候JVM才会去加载这个静态内部类。外部类初次加载会初始化静态变量、静态代码块、静态方法但不会加载内部类和静态内部类。 Java
public class Singleton {private Singleton() {}public static Singleton getInstance() {return SingletonFactory.instance;}private static class SingletonFactory {private static Singleton instance new Singleton();}}Kotlin
class SingleTon5 {companion object {fun getInstance() Holder.instance}private object Holder {val instance SingleTon5()}
}优点线程安全调用效率高可以延时加载。
枚举类
最佳的单例实现模式就是枚举模式。写法简单线程安全调用效率高可以天然的防止反射和反序列化调用不能延时加载。 Java
public enum Singleton {INSTANCE;public void show() {System.out.println(show);}}调用Singleton.INSTANCE.show();Kotlin
enum class Singleton {INSTANCE;fun show() {println(show)}
}写在最后
在线程安全的几种单例中 枚举无锁调用效率高可以防止反射和反序列化调用不能延时加载 静态内部类无锁调用效率高可以延时加载 双重同步锁有锁调用效率高于懒汉式可以延时加载 懒汉式有锁调用效率不高可以延时加载 ≈ 饿汉式无锁调用效率高不能延时加载
ps:只有枚举能防止反射和反序列化调用