栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Java中的线程七

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Java中的线程七

Java线程之JUC中的常用线程安全集合类
  • 一、ConcurrentHashMap
  • 二、BlockingQueue(暂时忽略)
  • 三、ConcurrentLinkedQueue(暂时忽略)
  • 四、CopyOnWriteArraylist
    • 1. 基本概念
    • 2. 源码分析
      • 2.1 更改(以JDK8新增为例)
      • 2.2 读(JDK8)
    • 3. 弱一致性
      • 3.1 读操作的弱一致性
      • 3.2 迭代器的弱一致性
    • 4. 注意

一、ConcurrentHashMap 二、BlockingQueue(暂时忽略) 三、ConcurrentLinkedQueue(暂时忽略) 四、CopyOnWriteArraylist 1. 基本概念
  • CopyOnWriteArraylist底层实现采用了写入时拷贝的思想,即增删改操作会将底层数组拷贝一份,在新数组上执行这些操作,这时不影响其它线程的并发读(因为读的是旧数组)。它实现了读写分离。比较适合读多写少的场景。
  • CopyOnWriteArraySet是CopyOnWriteArraylist的马甲,它的源码中实际上是定义了一个CopyOnWriteArraylist对象作为其属性,在调用CopyOnWriteArraySet的方法时,底层实际上调用的都是CopyOnWriteArraylist的方法,CopyOnWriteArraySet的存储元素不重复的实现也是通过再添加元素时,调用CopyOnWriteArraylist的addIfAbsent(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 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 list = new CopyOnWriteArrayList<>();
list.add(1);
list.add(2);
list.add(3);
Iterator iter = list.iterator();
new Thread(() -> {
	list.remove(0);
	System.out.println(list);
}).start();
sleep1s();
while (iter.hasNext()) {
	System.out.println(iter.next());
}
4. 注意
  • 并不要觉得弱一致性就不好,它可以做到读写的并发,数据库的 MVCC 都是弱一致性的表现。
  • 并发高和一致性是矛盾的,需要权衡。
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/881575.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号