CopyOnWriteArrayList源码分析

释放双眼,带上耳机,听听看~!

简介

线程安全的List,使用Java锁和数组副本实现并发的控制。字面上意思 写时拷贝:当往集合写数据时拷贝一个新的副本进行写,过后替换原来的数组,这个过程为同步操作。总体就是:同步写,并发读,读写分离。

类图

CopyOnWriteArrayList源码分析
继承体系与ArrayList大致相同

属性


1
2
3
4
5
6
7
8
9
10
11
12
13
14
1    /** 控制并发的锁 */
2    final transient ReentrantLock lock = new ReentrantLock();
3
4    /** 仅通过getArray / setArray访问数组 */
5    private transient volatile Object[] array;
6    
7    final Object[] getArray() {
8        return array;
9    }
10    final void setArray(Object[] a) {
11        array = a;
12    }
13
14

构造方法


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
1// 够着一个空数组
2public CopyOnWriteArrayList() {
3    setArray(new Object[0]);
4}
5// 从一个集合初始化
6public CopyOnWriteArrayList(Collection<? extends E> c) {
7    Object[] elements;
8    if (c.getClass() == CopyOnWriteArrayList.class)
9        elements = ((CopyOnWriteArrayList<?>)c).getArray();
10    else {
11        elements = c.toArray();
12        // c.toArray might (incorrectly) not return Object[] (see 6260652)
13        if (elements.getClass() != Object[].class)
14            elements = Arrays.copyOf(elements, elements.length, Object[].class);
15    }
16    setArray(elements);
17}
18// 从一个数组初始化
19public CopyOnWriteArrayList(E[] toCopyIn) {
20    setArray(Arrays.copyOf(toCopyIn, toCopyIn.length, Object[].class));
21}
22
23

写 add、set


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
1public boolean add(E e) {
2    final ReentrantLock lock = this.lock;
3    lock.lock(); // 获取锁
4    try {
5        Object[] elements = getArray(); // 获取现在的数组
6        int len = elements.length;
7        // 拷贝一个比原来长度+1的副本
8        Object[] newElements = Arrays.copyOf(elements, len + 1);
9        newElements[len] = e; // 新数组最后一位设置为新数据
10        setArray(newElements); // 替换旧数组
11        return true;
12    } finally {
13        lock.unlock();
14    }
15}
16
17public E set(int index, E element) {
18    final ReentrantLock lock = this.lock;
19    lock.lock();
20    try {
21        Object[] elements = getArray();
22        E oldValue = get(elements, index);
23
24      // 判断修改位置数据是否和现在相同
25      // 不相同拷贝副本替换数据
26      // 相同将原来的数组放回去(只是为了保证写的语义)
27        if (oldValue != element) {
28            int len = elements.length;
29            Object[] newElements = Arrays.copyOf(elements, len);
30            newElements[index] = element;
31            setArray(newElements);
32        } else {
33            setArray(elements);
34        }
35        return oldValue;
36    } finally {
37        lock.unlock();
38    }
39}
40
41

读 get


1
2
3
4
5
6
7
8
9
10
1// 不加锁 直接读
2private E get(Object[] a, int index) {
3    return(E) a[index];
4}
5
6public E get(int index) {
7    return get(getArray(), index);
8}
9
10

遍历


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
1static final class COWIterator<E> implements ListIterator<E> {
2    /** 数组快照 */
3    private final Object[] snapshot;
4    /** 下一个遍历的元素索引  */
5    private int cursor;
6
7    private COWIterator(Object[] elements, int initialCursor) {
8        cursor = initialCursor;
9        snapshot = elements;
10    }
11
12    public boolean hasNext() {
13        return cursor < snapshot.length;
14    }
15
16    public boolean hasPrevious() {
17        return cursor > 0;
18    }
19
20    @SuppressWarnings("unchecked")
21    public E next() {
22        if (! hasNext())
23            throw new NoSuchElementException();
24        return (E) snapshot[cursor++];
25    }
26
27    public E previous() {
28        if (! hasPrevious())
29            throw new NoSuchElementException();
30        return (E) snapshot[--cursor];
31    }
32
33    public int nextIndex() {
34        return cursor;
35    }
36
37    public int previousIndex() {
38        return cursor-1;
39    }
40
41    /**
42     * 遍历时只允许读,不支持修改
43     */
44    public void remove() {
45        throw new UnsupportedOperationException();
46    }
47
48    public void set(E e) {
49        throw new UnsupportedOperationException();
50    }
51
52    public void add(E e) {
53        throw new UnsupportedOperationException();
54    }
55
56    public void forEachRemaining(Consumer<? super E> action) {
57        Objects.requireNonNull(action);
58        Object[] elements = snapshot;
59        final int size = elements.length;
60        for (int i = cursor; i < size; i++) {
61            E e = (E) elements[i];
62            action.accept(e);
63        }
64        cursor = size;
65    }
66}
67
68

总结

核心思想,写时复制,读写分离,适合读远多于写的场景
写时复制:写的时候拷贝一个新的副本,性能不高
读写分离:读可以并发读,写时需要同步

给TA打赏
共{{data.count}}人
人已打赏
安全经验

如何避免Adsense违规封号

2021-10-11 16:36:11

安全经验

安全咨询服务

2022-1-12 14:11:49

个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索