Java中的线程进阶:线程安全集合类

在这里插入图片描述

一、ConcurrentHashMap

二、BlockingQueue(暂时忽略)

三、ConcurrentLinkedQueue(暂时忽略)

四、CopyOnWriteArraylist

1. 基本概念

  • CopyOnWriteArraylist底层实现采用了写入时拷贝的思想,即增删改操作会将底层数组拷贝一份,在新数组上执行这些操作,这时不影响其它线程的并发读(因为读的是旧数组)。它实现了读写分离。比较适合读多写少的场景。
  • CopyOnWriteArraySetCopyOnWriteArraylist的马甲,它的源码中实际上是定义了一个CopyOnWriteArraylist对象作为其属性,在调用CopyOnWriteArraySet的方法时,底层实际上调用的都是CopyOnWriteArraylist的方法,CopyOnWriteArraySet的存储元素不重复的实现也是通过再添加元素时,调用CopyOnWriteArraylistaddIfAbsent(E e)方法判断内部是否已经存在当前要加入的元素,如果不存在,才进行添加。

2. 源码分析

2.1 更改(以JDK8新增为例)

加锁是为了保证写写的互斥,不能同时进行写操作。但是读取是不影响的,可以并发执行,只不过读的是旧数组而已。

final transient ReentrantLock lock = new ReentrantLock();
private transient volatile Object[] array;

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

final void setArray(Object[] a) {
	array = a;
}

注意:JDK8和JDK11有所不同,不同的是JDK8中使用的是ReentrantLock而JDK11中使用的是synchronized。

2.2 读(JDK8)

读操作是未加锁的。

private transient volatile Object[] array;

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];
}
public void forEach(Consumer<? super E> action) {
	Objects.requireNonNull(action);
	for (Object x : getArray()) {
		@SuppressWarnings("unchecked") E e = (E) x;
		action.accept(e);
	}
}

3. 弱一致性

3.1 读操作的弱一致性

读操作指的是foreach遍历操作和get操作,下面仅以get操作为例进行说明。
在这里插入图片描述

  1. Thread-0想要读取CopyOnWriteArraylist中下标为0的数据,获得了CopyOnWriteArraylist内部的属性array的引用,此时,仅仅获取了数组的引用,还未来的及读取数据。
  2. Thread-1想要对数组下标为0的元素进行删除,复制了一个新的数组,并删除了下标为0的元素,将新的数组的引用设置为CopyOnWriteArraylist内部的属性array的引用。
  3. 此时尽管下标为0的元素已经删除了,且新的引用已经赋给了CopyOnWriteArraylist内部的属性array,但是Thread-0拿到的还是原来的引用,因此还可以读到原来的下标为0的数据。

3.2 迭代器的弱一致性

再利用迭代器进行遍历的情况下,同样会存在上述的问题。

CopyOnWriteArrayList<Integer> list = new CopyOnWriteArrayList<>();
list.add(1);
list.add(2);
list.add(3);
Iterator<Integer> iter = list.iterator();
new Thread(() -> {
	list.remove(0);
	System.out.println(list);
}).start();
sleep1s();
while (iter.hasNext()) {
	System.out.println(iter.next());
}

4. 注意

  • 并不要觉得弱一致性就不好,它可以做到读写的并发,数据库的 MVCC 都是弱一致性的表现。
  • 并发高和一致性是矛盾的,需要权衡。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值