简介
任何一个java的容器都可以使用Collections类提供的synchronizedXXX()函数来把对应的容器转换为线程安全的容器,但是这种加锁是简单粗暴的,性能比较低下。
而JUC(java util concurrent)提供的一些并非容器,虽然不能和java的容器一一对应,但是由于使用了分段锁,所以可以高效的执行代码。
写时复制
当多线程写一个容器的时候(多个增,删,改操作一起的时候),就会造成线程安全问题。于是可以把原始的数据给复制一份,然后老的数据用来进行查看,新的一份数据供增,删,改,经过写操作以后,然后再把新容器代替老容器。也就是相当于把读的操作和写的操作分开来避免线程安全问题,因为读操作的时候不会出现访问冲突等问题。
CopyOnWriteArrayList
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class CopyOnWriteArrayList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
private static final long serialVersionUID = 8673264195747942595L;
/**
* The lock protecting all mutators. (We have a mild preference
* for builtin monitors over ReentrantLock when either will do.)
*/
final transient Object lock = new Object();
/** The array, accessed only via getArray/setArray. */
private transient volatile Object[] array;
/**
* Creates an empty list.
*/
public CopyOnWriteArrayList() {
setArray(new Object[0]);
}
// ...
}
下面分别从增删改查来分析
1
2
3
4
5
6
7
8
9
10
11
// 添加
public boolean add(E e) {
synchronized (lock) {
Object[] es = getArray();
int len = es.length;
es = Arrays.copyOf(es, len + 1);
es[len] = e;
setArray(es);
return true;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 删除
public E remove(int index) {
synchronized (lock) {
Object[] es = getArray();
int len = es.length;
E oldValue = elementAt(es, index);
int numMoved = len - index - 1;
Object[] newElements;
if (numMoved == 0)// 是否是删除的最后一个
newElements = Arrays.copyOf(es, len - 1);
else {// 删除的非最后一个
newElements = new Object[len - 1];
System.arraycopy(es, 0, newElements, 0, index);
System.arraycopy(es, index + 1, newElements, index,
numMoved);
}
setArray(newElements);
return oldValue;
}
}
1
2
3
4
// 查看
public E get(int index) {
return elementAt(getArray(), index);
}
可以根据上面的源码得到结论:
- 查看的时候和写的时候并没有什么关系,是完全独立开来的。
- 数据是弱一直性的;
- 适用的场景是
读多写少
的情况,因为每次写都会整个复制数组。
CopyOnWriteArraySet
和CopyOnWriteArrayList的实现基本一致,底层是利用CopyOnWriteArrayList来实现的。其中contains()函数基于数组来实现,需要遍历查询,执行效率比较低