java.util.concurrent包系列文章
JUC—ThreadLocal源码解析(JDK13)
JUC—ThreadPoolExecutor线程池源码解析(JDK13)
JUC—各种锁(JDK13)
JUC—原子类Atomic*.java源码解析(JDK13)
JUC—CAS源码解析(JDK13)
JUC—ConcurrentHashMap源码解析(JDK13)
JUC—CopyOnWriteArrayList源码解析(JDK13)
JUC—并发队列源码解析(JDK13)
JUC—多线程下控制并发流程(JDK13)
JUC—AbstractQueuedSynchronizer解析(JDK13)
一、CopyOnWrite*系列
CopyOnWriteArrayList和CopyOnWriteArraySet也是常用的并发集合类。他们支持并发的读写。线程安全的。不过有它的缺点。
本篇就分析下CopyOnWriteArrayList的原理和源码。CopyOnWriteArraySet也是类似的。
二 、CopyOnWriteArrayList
CopyOnWriteArrayList诞生自JDK1.5,是为了取代Vector和SynchronizedList的,就和ConcurrentHashMap取代SynchronizedMap一样。Vector和SynchronizedList锁的粒度太大,并发效率低。并且迭代时无法编辑。
适用场景:
- 多读少写的情况,比如系统缓存的白名单。偶尔更新一次,读却特别多。
读写规则:
- 读取完全不用加锁,写入也不会阻塞读操作。只有写入与写入之间需要同步等待。
看一份代码:
public static void main(String[] args) {
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
list.add("A");
list.add("B");
list.add("C");
list.add("D");
// CopyOnWriteArrayList生成迭代器。迭代器里的数组就是生成迭代器时list的数组。
// 后续的对list的修改时对这个数组的数据不产生影响。修改是在新的内存中新copy了一个同样的数组。
// 修改完成后再把list数组指向这个新数组。
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
System.out.println("list is " + list);
String next = iterator.next();
System.out.println("next is " + next);
if (next.equals("B")) {
list.remove("D");
}
if (next.equals("C")) {
list.add("E");
}
}
}
运行结果:
list is [A, B, C, D]
next is A
list is [A, B, C, D]
next is B
list is [A, B, C]
next is C
list is [A, B, C, E]
next is D
这就是CopyOnWrite的意思。创建新副本,读写分离。add的时候会在新内存copy一份原数组,add完成之后让CopyOnWriteArrayList的array指向新数组,旧的就会被回收了。实现了读写不需要同步。写与写才需要同步。
上面把CopyOnWriteArrayList换成ArrayList是不行的,ArrayList不支持在迭代时操作数组。
ArrayList报错原因:
看看ArrayList里面迭代器的属性和方法
public E next() {
// 获取下一个元素之前会判断当前数组长度是否变化了
checkForComodification();
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
}
// expectedModCount是生成迭代器时modCount赋给expectedModCount
int expectedModCount = modCount;
final void checkForComodification() {
// modCount每次修改就+1,
// expectedModCount是生成迭代器时modCount赋给expectedModCount
if (modCount != expectedModCount)
// 如果数组长度变化了,说明被修改了,直接报错
throw new ConcurrentModificationException();
}
CopyOnWriteArrayList的add方法
public boolean add(E e) {
// 上锁,写必须同步
synchronized (lock) {
Object[] es = getArray();
int len = es.length;
// 复制了一个新数组,并且长度是原来的+1
es = Arrays.copyOf(es, len + 1);
// 新加的元素添加到数组最后
es[len] = e;
// add完成后,把array指向新数组
setArray(es);
return true;
}
}
final void setArray(Object[] a) {
array = a;
}
CopyOnWriteArrayList的get方法
可以发现get方法没有任何的加锁行为
public E get(int index) {
return elementAt(getArray(), index);
}
static <E> E elementAt(Object[] a, int index) {
return (E) a[index];
}
初始化方法与锁
public class CopyOnWriteArrayList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
private static final long serialVersionUID = 8673264195747942595L;
final transient Object lock = new Object();
private transient volatile Object[] array;
final Object[] getArray() {
return array;
}
final void setArray(Object[] a) {
array = a;
}
public CopyOnWriteArrayList() {
setArray(new Object[0]);
}
}
CopyOnWriteArrayList的缺点:
- 迭代期间的数据可能过时了的
- 内存占用更大,因为是copy一份数组嘛