copyOnWrite技术的思路是通过写时复制的方式来来解决并发的问题,采用一种读写分离的思想,这样在读的时候,可以并发读,提高效率。具体的方式,是并发读,然后写入前,先复制一份,然后在复制的那一份数据中,写入需要的数据,然后替换掉原来的引用,那么就写成功了。需要注意的是,copyOnWrite只能保证最终一致性,不能实时更新。
在介绍copyOnwrite技术前,先看下多线程集合的情况:
1.1、ArrayList的测试
public static void main(String[] args) {
List<String> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
new Thread(() -> {
list.add("a");
}).start();
}
list.forEach(x->{
System.out.print(x);
});
}
多运行几次,你可以得到有些数据为null的情况,比如: nullaaaaaaaaa 出现了线程安全问题
1.2、使用Vector类
public static void main(String[] args) {
List<String> list = new Vector<>();
for (int i = 0; i < 10; i++) {
new Thread(() -> {
list.add("a");
}).start();
}
list.forEach(x->{
System.out.print(x);
});
}
vector是一个比较古老的类,底层代码是直接加synchronized来解决并发问题的,性能比较低。源码:
// 底层代码,直接通过关键字synchronized来解决并发问题,但是性能比较低
public synchronized boolean add(E e) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = e;
return true;
}
1.3、使用工具类Collections
使用工具类Collections的静态方法:synchronizedList,跟上述一样,线程安全,但是性能比较低
public static void main(String[] args) {
List<String> list = Collections.synchronizedList(new ArrayList<>());
for (int i = 0; i < 10; i++) {
new Thread(() -> {
list.add("a");
}).start();
}
list.forEach(x->{
System.out.print(x);
});
}
1.4、CopyOnWriteArrayList
适用于读多写比较少的场景
public static void main(String[] args) {
List<String> list = new CopyOnWriteArrayList<>();
for (int i = 0; i < 10; i++) {
new Thread(() -> {
list.add("a");
}).start();
}
list.forEach(x->{
System.out.print(x);
});
}
查看底层源码:
public boolean add(E e) {
final ReentrantLock lock = this.lock;
// 写入的时候加锁
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
// 复制一份副本newElements
Object[] newElements = Arrays.copyOf(elements, len + 1);
// 往副本中写入数据
newElements[len] = e;
// 替换副本和原来数据的引用
// array为旧数据的引用
// final void setArray(Object[] a) {
// array = a;
// }
//
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
copyOnWrite的实现,内存会占用多一倍,因为复制一个副本,同时,这个时候别的线程使用这个list的话,用的还是旧的那个实例,所以这个不是实时的,有一定的时间延迟。