CopyOnWriteArrayList
要点
1.实现了List接口
2.内部持有一个ReentrantLock lock = new ReentrantLock();
3.底层是用volatile transient声明的数组 array
4.读写分离,写时复制出一个新的数组,完成插入、修改或者移除操作后将新数组赋值给array
增删改查
1)增
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();
}
}
public void add(int index, E element) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
if (index > len || index < 0)
throw new IndexOutOfBoundsException("Index: "+index+
", Size: "+len);
Object[] newElements;
int numMoved = len - index;
if (numMoved == 0)
newElements = Arrays.copyOf(elements, len + 1);
else {
newElements = new Object[len + 1];
System.arraycopy(elements, 0, newElements, 0, index);
System.arraycopy(elements, index, newElements, index + 1,
numMoved);
}
newElements[index] = element;
setArray(newElements);
} finally {
lock.unlock();
}
}
2)删
public E remove(int index) {
final ReentrantLock lock = this.lock;
//获得锁
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
E oldValue = get(elements, index);
int numMoved = len - index - 1;
if (numMoved == 0)
//如果删除的元素是最后一个,直接复制该元素前的所有元素到新的数组
setArray(Arrays.copyOf(elements, len - 1));
else {
//创建新的数组
Object[] newElements = new Object[len - 1];
//将index+1至最后一个元素向前移动一格
System.arraycopy(elements, 0, newElements, 0, index);
System.arraycopy(elements, index + 1, newElements, index,
numMoved);
setArray(newElements);
}
return oldValue;
} finally {
lock.unlock();
}
}
3)改
public E set(int index, E element) {
final ReentrantLock lock = this.lock;
//获得锁
lock.lock();
try {
Object[] elements = getArray();
E oldValue = get(elements, index);
if (oldValue != element) {
int len = elements.length;
//创建新数组
Object[] newElements = Arrays.copyOf(elements, len);
//替换元素
newElements[index] = element;
//将新数组指向原来的引用
setArray(newElements);
} else {
// Not quite a no-op; ensures volatile write semantics
setArray(elements);
}
return oldValue;
} finally {
//释放锁
lock.unlock();
}
}
4)查
//直接获取index对应的元素
public E get(int index) {return get(getArray(), index);}
private E get(Object[] a, int index) {return (E) a[index];}
总结
从以上的增删改查中我们可以发现,增删改都需要获得锁,并且锁只有一把,而读操作不需要获得锁,支持并发。为什么增删改中都需要创建一个新的数组,操作完成之后再赋给原来的引用?这是为了保证get的时候都能获取到元素,如果在增删改过程直接修改原来的数组,可能会造成执行读操作获取不到数据。
CopyOnWriteArrayList为什么并发安全且性能比Vector好
我知道Vector是增删改查方法都加了synchronized,保证同步,但是每个方法执行的时候都要去获得锁,性能就会大大下降,而CopyOnWriteArrayList 只是在增删改上加锁,但是读不加锁,在读方面的性能就好于Vector,CopyOnWriteArrayList支持读多写少的并发情况。
CopyOnWriteArraySet
CopyOnWriteArraySet的部分源码:
public class CopyOnWriteArraySet<E> extends AbstractSet<E>
implements java.io.Serializable {
private static final long serialVersionUID = 5457747651344034263L;
private final CopyOnWriteArrayList<E> al;
/**
* Creates an empty set.
*/
public CopyOnWriteArraySet() {
al = new CopyOnWriteArrayList<E>();
}
...
}
从源码中神奇地发现CopyOnWriteArraySet的底层存储结构竟然是CopyOnWriteArrayList,那么我们就可以知道它的名字的由来了,并且知道它支持并发的原理跟CopyOnWriteArrayList是一样的。
增删改查
1)增
public boolean add(E e) {
return al.addIfAbsent(e);
}
看方法名我们就是如果CopyOnWriteArrayList中不存在某元素才会添加成功
2)删
public boolean remove(Object o) {
return al.remove(o);
}
直接从CopyOnWriteArrayList中移除
总结:
1.CopyOnWriteArraySet支持并发的原理:CopyOnWriteArraySet之所以叫CopyOnWriteArraySet,是因为它的底层存储结构是CopyOnWriteArrayList,同时也就是保证了它的并发安全性。
2.CopyOnWriteArraySet的增删原理:CopyOnWriteArraySet继承了AbstractSet,跟HashSet一样只有增删,没有改查,增删原理也就是调用CopyOnWriteArrayList的增删方法,只不过增的时候需要判断一下List中是否存储该元素。