您的位置:首页 > 编程语言 > Java开发

《Java源码分析》:CopyOnWriteArrayList/ CopyOnWriteArraySet

2017-09-12 10:27 671 查看


《Java源码分析》:CopyOnWriteArrayList/CopyOnWriteArraySet

CopyOnWriteArrayList/CopyOnWriteArraySet的基本思想是一旦对容器有修改,那么就“复制”一份新的集合,在新的集合上修改,然后将新集合复制给旧的引用。当然了这部分少不了要加锁。显然对于CopyOnWriteArrayList/CopyOnWriteArraySet来说最大的好处就是“读”操作不需要锁了。

CopyOnWriteArrayList是ArrayList 的一个线程安全的变体,其中所有可变操作(add、set 等等)都是通过对底层数组进行一次新的复制来实现的。 这一般需要很大的开销,但是当遍历操作的数量大大超过可变操作的数量时,这种方法可能比其他替代方法更 有效。在不能或不想进行同步遍历,但又需要从并发线程中排除冲突时,它也很有用。“快照”风格的迭代器方法在创建迭代器时使用了对数组状态的引用。此数组在迭代器的生存期内不会更改,因此不可能发生冲突,并且迭代器保证不会抛出
ConcurrentModificationException。创建迭代器以后,迭代器就不会反映列表的添加、移除或者更改。在迭代器上进行的元素更改操作(remove、set 和 add)不受支持。这些方法将抛出 UnsupportedOperationException。

这个类确实比较简单,在一些可变操作下通过加锁并对底层数组进行一次复制来实现。下面我们就简单的看下源码。


CopyOnWriteArrayList构造函数

CopyOnWriteArrayList的底层还是基于数组来实现的,只是此时的数组采用volatile来修饰,来保证内存的一致性,即一个线程对array的修改对另一个线程可见,但不是立即可见。
private transient volatile Object[] array;
1

构造函数如下:
public CopyOnWriteArrayList() {
setArray(new Object[0]);
}
/**
* Sets the array.
*/
final void setArray(Object[] a) {
array = a;
}
1
2
3
4
5
6
7
8
9

即初始对象构造了一个长度为零的List。


get方法

public E get(int index) {
return get(getArray(), index);
}
final Object[] getArray() {
return array;
}

private E get(Object[] a, int index) {
return (E) a[index];
}
1
2
3
4
5
6
7
8
9
10
11

利用的数组的不可变性,get方法的操作数组都是某一时刻array的镜像。这样在高并发的情况下get方法和其它线程对该List的访问(无论是读操作还是写操作)都不会产生冲突。


另一些不加锁的方法contains/indexOf

public int indexOf(Object o) {
Object[] elements = getArray();//得到此时数组的一份镜像
return indexOf(o, elements, 0, elements.length);
}
private static int indexOf(Object o, Object[] elements,
int index, int fence) {
if (o == null) {
for (int i = index; i < fence; i++)
if (elements[i] == null)
return i;
} else {
for (int i = index; i < fence; i++)
if (o.equals(elements[i]))
return i;
}
return -1;
}
//同indexOf一直
public boolean contains(Object o) {
Object[] elements = getArray();
return indexOf(o, elements, 0, elements.length) >= 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22


可变操作(add/set/remove)

下面为CopyOnWriteArrayList的一些可变操作的内部实现,可变操作都是采用的如下的思想:

1、加锁

2、将原来的数组copy一份到新数组中,然后修改

3、将旧的引用array指向新数组。

4、释放锁

由于源码都比较简单好懂,这里就不解释了。
/**
* 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();
}
}

/**
* Inserts the specified element at the specified position in this
* list. Shifts the element currently at that position (if any) and
* any subsequent elements to the right (adds one to their indices).
*
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
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();
}
}

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();
}
}

/**
* Replaces the element at the specified position in this list with the
* specified element.
*
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
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 确保语义
/*
为了保持“volatile”的语义,任何一个读操作都应该是一个写操作的结果,
也就是读操作看到的数据一定是某个写操作的结果(尽管写操作没有改变数据本身)。
所以这里即使不设置也没有问题,仅仅是为了一个语义上的补充(就如源码中的注释所言)。
*/
setArray(elements);//写回,什么都没有改变,为什么还要写回了?这是因为
}
return oldValue;
} finally {
lock.unlock();
}
}
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
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111


CopyOnWriteArraySet类分析

上面介绍了CopyOnWriteArrayList类的常见方法,比较简单。而CopyOnWriteArraySet就更简单了,因为,CopyOnWriteArraySet里面有一个CopyOnWriteArrayList的引用,即CopyOnWriteArraySet类里面的内部实现全部是委托给CopyOnWriteArrayList来实现的,只是额外的封装了下。

看如下的代码你就明白了。
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>();
}
1
2
3
4
5
6
7
8
9
10
11
12
13


小结

关于CopyOnWriteArrayList、CopyOnWriteArraySet,我们需要记住以下几点就好了

1、CopyOnWriteArrayList是ArrayList的线程安全的实现。

2、如何来实现线程安全的呢?底层的数组采用Volatile来声明的,对于可变操作采用加锁并对底层的数组进行拷贝一份,在新数组上进行修改,最后将旧引用指向这个新数组即可,对于不可变的操作,利用的数组的不可变性来完成的。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐