从CopyOnWriteArrayList谈等效不可变对象在源码中的应用
1 从CopyOnWriteArrayList谈等效不可变对象在源码中的应用
CopyOnWriteArrayList的源码中应用了等效不可变对象。使得集合在遍历操作的时候,不用加锁也能保证线程安全。
1.1 CopyOnWriteArrayList Source Code
public class CopyOnWriteArrayList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable{
private static final long serialVersionUID = ... ;
final transient ReentrantLock lock = new ReentrantLock();
private transient volatile Object[] array;
final Object[] getArray() {return array;}
final void setArray(Object[] a) {array = a;}
}
可以看到CopyOnWriteArrayList源码中,维护了一个array对象数组,用于储存集合中每一个元素,并且这个array数组,只能够通过get和set方法来访问。
// 遍历CopyOnWriteArrayList方法
public Iterator<E> iterator(){
return new COWIterator<E>(getArray(), 0);
}
// 新增一个元素的方法
public boolean add(E e){
final ReentrantLock lock = this.lock;
lock.lock();
try{
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elementes, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
增加元素的步骤:
- elements = 当前数组
- len = 当前数组长度
- 新建一个 newElements,把elements复制进去,并且长度增加1
- 把增加的元素放在newElements最后
- 调取setArray方法,将newElements数组更新旧的数组。
- 返回true,解锁。
1.2 分析关键点
- array被创建后,不会被修改。包括长度和内容。
- 增删时,先整体复制array到newElements中,在新的数组中做好操作,直接整体替换旧数组。
- 在操作新数组时,旧数组内的数值至始至终不改变。
实例Array本质上就是一个数组,数组内的元素是对象,每个对象的内部状态可以发生替换,因此这并非是严格的不可变对象,所以称之为等效不可变对象
1.3 继续说明
下面演示上述所谓的非严格的不可变对象的情况
public static void main(String[] args){
List<Message> list = new CopyOnWriteArrayList<>();
Message message = new Message();
message.setMsg("aaa");
list.add(message);
message.setMsg("bbb");
}
照理来说,放在CopeOnWriteArrayList中的东西应该不能动,除非整个替换。但是我们可以通过调用存在数组中的对象的set方法,修改了对象数值。
这就是不严格的不可变对象。 称之为:等效不可变对象
1.4 精髓
这个数组是写时复制
,也就是当要更新数组内容时,先复制一个副本,在副本上做修改,然后用副本替换原本。
写时复制的意义在于,当读多写少的场景时,通过这个机制,大量的读请求在无需加锁牺牲性能的情况下,保证多线程的并发读写安全。
这个数组的弱点在于:弱一致性
因为任何线程从原理上,只不过是获得了一个数组备份的副本而已,当原本数组发生改变时,这种改变,并不会体现在副本上。
1.5 实际应用 in MySQL Driver
在mysql driver中,有一个registerDriver方法,该方法用来保存不同的数据库驱动的。
源码省略。
无论有多少个数据库模型,数据库的驱动程序一般都是在程序启动的时候加载的。也就是说,registerDriver方法,一般来说都是在程序启动的时候进行调用的,在程序运行的过程中,一般不会调用这个方法,所以非常适合这个List