JAVA8 之 CopyOnWriteArrayList
类继承层次
底层数据结构
同ArrayList
默认初始容量
0
最大容量
等于java数组最大长度
扩容算法
每次新建一个原数组长度加1的数组然后将原数组拷贝到新数组,最后把新增元素加进新数组
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
setArray(newElements);
主要操作时间复杂度
同ArrayList
安全性
线程安全
线程安全实现原理
每个CopyOnWriteArrayList对象持有一个ReentrantLock,在每个写操作上加锁,以add方法为例:
/**
* Appends the specified element to the end of this list.
*
* @param e element to be appended to this list
* @return {@code true} (as specified by {@link Collection#add})
*/
public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
问题
CopyOnWriteArrayList的弱一致性
写操作加锁隔离,但是读操作使用快照读,无法保证按时序的一致性。这也是iterator接口为什么屏蔽了写操作的原因:
/**
* Not supported. Always throws UnsupportedOperationException.
* @throws UnsupportedOperationException always; {@code remove}
* is not supported by this iterator.
*/
public void remove() {
throw new UnsupportedOperationException();
}
/**
* Not supported. Always throws UnsupportedOperationException.
* @throws UnsupportedOperationException always; {@code set}
* is not supported by this iterator.
*/
public void set(E e) {
throw new UnsupportedOperationException();
}
/**
* Not supported. Always throws UnsupportedOperationException.
* @throws UnsupportedOperationException always; {@code add}
* is not supported by this iterator.
*/
public void add(E e) {
throw new UnsupportedOperationException();
}
可疑的静态代码块
CopyOnWriteArrayList中有这么一段静态代码块:
static {
try {
UNSAFE = sun.misc.Unsafe.getUnsafe();
Class<?> k = CopyOnWriteArrayList.class;
lockOffset = UNSAFE.objectFieldOffset
(k.getDeclaredField("lock"));
} catch (Exception e) {
throw new Error(e);
}
}
在看下lock和lockOffset属性的声明以及resetLock方法
final transient ReentrantLock lock = new ReentrantLock();
private static final long lockOffset;
// Support for resetting lock while deserializing
private void resetLock() {
UNSAFE.putObjectVolatile(this, lockOffset, new ReentrantLock());
}
在来看下一段反序列化代码
/**
* Reconstitutes this list from a stream (that is, deserializes it).
* @param s the stream
* @throws ClassNotFoundException if the class of a serialized object
* could not be found
* @throws java.io.IOException if an I/O error occurs
*/
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
s.defaultReadObject();
// bind to new lock
resetLock();
// Read in array length and allocate array
int len = s.readInt();
SharedSecrets.getJavaOISAccess().checkArray(s, Object[].class, len);
Object[] elements = new Object[len];
// Read in all elements in the proper order.
for (int i = 0; i < len; i++)
elements[i] = s.readObject();
setArray(elements);
}
到这里你大概就能猜到了。因为lock对象是一个transient修饰到对象,不会被序列化,所以反序列化的时候需要重新创建这个对象且放到指定到位置上。那可不可以把lock对象前面的transient去掉,这样不就可以了序列化以及反序列化整个CopyOnWriteArrayList对象且不需要这段可疑的static代码了吗?