CopyOnWriteArrayList 设计思路深入了解

通过学习,我们可以发现,ArrayList 作为共享变量的话,是线程不安全的。
如果要想保住线程安全,可以通过以下两种方式实现:

1、Collections.synchronizedList 方法

2、 CopyOnWriteArrayList 方法

今天记录一下:CopyOnWriteArrayList 的学习



CopyOnWriteArrayList 方法:

  1. 线程安全的,多线程环境下可以直接使用,底层在读时不会加锁,写时会加锁;
  2. 通过锁 + 数组拷贝 + volatile 关键字保证了线程安全;
  3. 写时复制,每次数组操作,都会把数组拷贝一份出来,在新数组上进行操作,操作成功之后再赋值回去。
1、整体架构:

从整体架构上来说,CopyOnWriteArrayList 数据结构和 ArrayList 是一致的,底层是个数组,只不过

CopyOnWriteArrayList 在对数组进行操作的时候,基本会分四步走:

  1. 加锁;
  2. 从原数组中拷贝出新数组;
  3. 在新数组上进行操作,并把新数组赋值给数组容器;
  4. 解锁。

除了加锁之外,CopyOnWriteArrayList 的底层数组还被 volatile 关键字修饰,意思是一旦数组被修
改,其它线程立马能够感知到,代码如下:

private transient volatile Object[] array;


2、CopyOnWriteArrayList 新增元素:

新增也分多组情况:尾部新增、指定位置新增、批量元素新增

下面看下尾部新增源码:

public boolean add(E e) {
	final ReentrantLock lock = this.lock;
	//加锁
	lock.lock();
	try {
		//得到所有的原数组
		Object[] elements = getArray();
		int len = elements.length;
		
		//拷贝到新数组里面,新数组的长度是 + 1  的,因为新增会多一个元素
		Object[] newElements = Arrays.copyOf(elements, len + 1);
		
		//在新数组中进行赋值,新元素直接放在数组的尾部
		newElements[len] = e;
		
		//替换掉原来的数组
		setArray(newElements);
		
		return true;
	} finally {
		// finally  里面释放锁,保证即使 try  发生了异常,仍然能够释放锁
		lock.unlock();
	}
}

上面 add 方法 过程都是在持有锁的状态下进行的,通过加锁,来保证同一时刻只能有一个线程能够对同一个数组进行 add 操作。除了加锁之外,还会从老数组中创建出一个新数组,然后把老数组的值拷贝到新数组上,这时候就有一个问题:都已经加锁了,为什么需要拷贝数组,而不是在原来数组上面进行操作,原因主要是:

volatile 关键字修饰的是数组,如果我们简单的在原来数组上修改其中某几个元素的值,是无法触发可见性的,我们必须通过修改数组的内存地址才行,也就说要对数组进行重新赋值才行。

3、总结:

从 add 系列方法可以看出,CopyOnWriteArrayList 通过加锁 + 数组拷贝+ volatile 来保证了线程安全,每一个要素都有着其独特的含义:

  1. 加锁:保证同一时刻数组只能被一个线程操作;
  2. 数组拷贝:保证数组的内存地址被修改,修改后触发 volatile 的可见性,其它线程可以立马
    知道数组已经被修改;
  3. volatile:值被修改后,其它线程能够立马感知最新值。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值