现在项目面临的并发场景越来越多。而像vector,hashTable这一类集合虽然保证的线程的安全行。但是无法兼顾性能。所以出现了如ConcurrentHashMap这一类兼顾性能与线程安全的集合。而对于List和Set同样也有新的线程安全集合——CopyOnWriteArrayList。
Copy-On-Write简称COW,是一种用于程序设计中的优化策略。其基本思路是,从一开始大家都在共享同一个内容,当某个人想要修改这个内容的时候,才会真正把内容Copy出去形成一个新的内容然后再改,这是一种延时懒惰策略。
CopyOnWriteArrayList的构造方法和ArrayList是基本相同的,主要看其添加,删除方法来研究其如何保证性能以及线程安全。
transient final ReentrantLock lock = new ReentrantLock();
//添加方法
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 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];
System.arraycopy(elements, 0, newElements, 0, index);
System.arraycopy(elements, index + 1, newElements, index,
numMoved);
setArray(newElements);
}
return oldValue;
} finally {
lock.unlock();
}
}
//获取方法
private volatile transient Object[] array;
final Object[] getArray() {
return array;
}
public E get(int index) {
return get(getArray(), index);
}
private E get(Object[] a, int index) {
return (E) a[index];
}
可以从代码看出集合类中引入了重入锁ReentrantLock来进行线程安全控制。在添加以及删除操作中。加入同步锁。将原来的数组复制一份出来。在复制的集合中进行修改。而不影响其他线程对原来数组的获取。这样保证了集合的最终一致性。也兼顾了性能。为什么是最终一致性呢。因为他说复制一份数组出来。如果同时有2个线程操作数组。一个在修改一个在获取。那只能获取到修改之前的数组。无法获取到正在修改的数组。所以只能保证最终的一致性,无法保证实时的一致。
适用于用于读多写少的并发场景。比如白名单,黑名单,商品类目的访问和更新场景
同样基于这种读写思想分离来保障线程安全的还有CopyOnWriteArraySet。
CopyOnWriteArraySet是实现了set的集合。我们都知道set的实现类基本都是依靠map来进行存储的。但是CopyOnWriteArraySet是不一样的。他是依靠CopyOnWriteArrayList来进行实现的也就是数组进行存储。
我们看其构造方法
private final CopyOnWriteArrayList<E> al;
public CopyOnWriteArraySet() {
al = new CopyOnWriteArrayList<E>();
}
public CopyOnWriteArraySet(Collection<? extends E> c) {
al = new CopyOnWriteArrayList<E>();
al.addAllAbsent(c);
}
//引用了CopyOnWriteArrayList进行数据存储
再看其添加和移除的方法
public boolean add(E e) {
return al.addIfAbsent(e);
}
public boolean addIfAbsent(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
// Copy while checking if already present.
// This wins in the most common case where it is not present
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = new Object[len + 1];
for (int i = 0; i < len; ++i) {
if (eq(e, elements[i]))
return false; // exit, throwing away copy
else
newElements[i] = elements[i];
}
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
public boolean remove(Object o) {
return al.remove(o);
}
这里的添加方法和CopyOnWriteArrayList不一样。他是新建了一个比原来大的数组。然后循环原数组将元素添加进去。在循环的过程中判断添加的对象是否和原来的对象相同。如果相同则添加失败。由此来保证数组对象无重复。缺点也和CopyOnWriteArrayList一样。无法保证数据的实时一致。只能保证最终一致性。同时如果集合内存过大。也会占用过多的内存空间。导致出发GC影响性能。