异常简介
ConcurrentModificationException(并发修改异常)是基于java集合中的 快速失败(fail-fast) 机制产生的,在使用迭代器遍历一个集合对象时,如果遍历过程中对集合对象的内容进行了增删改,就会抛出该异常。
 快速失败机制使得java的集合类不能在多线程下并发修改,也不能在迭代过程中被修改。
异常原因
示例代码
val elements : MutableList<Int> = 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.ConcurrentModificationException
 at java.util.ArrayList$Itr.next(ArrayList.java:860)
异常原因是什么呢?
 modCount:表示list集合结构上被修改的次数
 expectedModCount:表示对ArrayList修改次数的期望值(在开始遍历元素之前记录的)
 list的for循环中是通过Iterator迭代器遍历访问集合内容,在遍历过程中会使用到modCount变量,如果在遍历过程期间集合内容发生变化,则会改变modCount的数值,每当迭代器使用next() 遍历下一个元素之前,都会检测 modCount 变量是否为 expectedModCount 值,相等的话就返回遍历;否则抛出异常(ConcurrentModificationException),终止遍历。
 
而在我们的示例代码中,线程二在调用add方法的时候modCount+1,导致线程一在遍历的时候modCount!=expectedModCount,所以抛出了ConcurrentModificationException
解决方法
那在多线程下,我们需要集合支持并发读写怎么实现呢?
- 使用Collections.synchronizedList给集合加锁
val elements : MutableList<Int> = Collections.synchronizedList(mutableListOf())
...
val thread = Thread {
    //线程一读数据
    synchronized(elements) {
    //使用Iterator遍历时需要手动加锁
        elements.forEach {
            Log.i("testTag", it.toString())
        }
    }
}
...
原理:
 以组合的方式将对 List 的接口方法操作,委托给传入的 list 对象,并且对所有的接口方法对象加锁,得到并发安全性。通过组合的方式对传入的list对象的get,set,add等方法加synchronized同步锁,但是对于需要用到iterator迭代器的时候需要手动加锁
public static <T> List<T> synchronizedList(List<T> list) {
    return (list instanceof RandomAccess ?
            new SynchronizedRandomAccessList<>(list) :
            new SynchronizedList<>(list));
}
static <T> List<T> synchronizedList(List<T> list, Object mutex) {
    return (list instanceof RandomAccess ?
            new SynchronizedRandomAccessList<>(list, mutex) :
            new SynchronizedList<>(list, mutex));
}
SynchronizedCollection(Collection<E> c) {
    this.c = Objects.requireNonNull(c);
    //需要加锁的对象,这里指自己
    mutex = this;
}
static class SynchronizedList<E>
    extends SynchronizedCollection<E>
    implements List<E> {
    private static final long serialVersionUID = -7754090372962971524L;
    final List<E> list;
    SynchronizedList(List<E> list) {
        super(list);
        this.list = list;
    }
    SynchronizedList(List<E> 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 ListIterator<E> listIterator() {
        return list.listIterator(); // Must be manually synched by user
    }
    public ListIterator<E> listIterator(int index) {
        return list.listIterator(index); // Must be manually synched by user
    }
优点:可以使非线程安全的集合如Arraylist封装成线程安全的集合,并且相对CopyOnWriteArrayList写操作性能较好
 缺点:在任何操作之前都需要加同步锁,使用iterator还需要手动加锁才能保证并发读写安全
 2. 使用支持并发读写的CopyOnWriteArrayList
val elements : CopyOnWriteArrayList<Int> = 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方法时,会先对集合对象添加同步锁,然后创建一个len+1的数组,再把旧数组中数据复制添加到新数组中,最后把新数组替换掉老数组
 优点:读操作效率高,无加锁操作
 缺点:写操作每次都需要复制一份新数组,性能较差
拓展:多线程下怎么做好单例的设计
懒汉式单例
在需要的时候再去创建实例。
 锁它!锁它!锁它!
同步锁
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? = null
        @Synchronized
        fun 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:只有枚举能防止反射和反序列化调用


















![【C++入门到精通】C++的IO流(输入输出流) [ C++入门 ]](https://img-blog.csdnimg.cn/direct/686178f7f6ea4d23a2e3953d816d3215.png)
