CopyOnWriteArrayList了解

前言:CopyOnWriteArrayList是ArrayList的线程安全版本,在写入时会copy一份数据,然后写完再设置成新的数据。适用于读多写少的并发场景

COW

CopyOnWrite 简称COW,写入时复制,是计算机程序设计领域的一种优化策略。

核心思想:

多个调用者共同去访问一个资源(指向同一个读指针),如果有人试图修改资源的内容,系统会复制一份专用副本给该调用者,去修改这个副本,而对于其他人来说访问的资源还是原来的,不会发生变化。

原理:

COW的处理过程中需维持一个为读请求使用的“指针”,并在新数据写入完成后,更新这个指针以提升读写并发能力。因此,COW也间接提供了数据更新过程中的原子性。在保证数据的完整性同时还保证了一定的读写效率

案例分析:

多线程环境对list进行写操作,会引发 java.util.ConcurrentModificationException 并发修改异常!

public static void main(String[] args) {
    /**解决方案:
        1.使用Vector,底层add源码增加了锁 public synchronized boolean add(E e){...}
        2.使用Collections.synchronizedList(list) 将其包装成一个线程安全的List
        3.使用CopyOnWriteArrayList,底层使用了可重入锁,保证并发完全 */
    
    //List<String> list = new ArrayList<>(); 报错:并发修改异常
    //List<String> list = new Vector<>();
    //List<String> list = Collections.synchronizedList(new ArrayList<>()); 
    List<String> list = new CopyOnWriteArrayList<>();
    for (int i = 0; i < 10; i++) {
        new Thread(()->{
            list.add(UUID.randomUUID().toString().substring(0,5));
            System.out.println(list);
        },String.valueOf(i)).start();
    }
}

结果分析

ArrayList 之所以存在并发异常,是底层没有做任何锁的操作

 

① Vector 在add操作时,加入了synchronized锁,保证并发安全

 

② Collections.synchronizedList (mutex) 使用的是synchronized代码块对mutex对象加锁,指定锁定的对象

 

③ CopyOnWriteArrayList 加入了可重入锁,保证并发安全

public boolean add(E e) {
    final ReentrantLock lock = this.lock;
    // 上锁,只允许一个线程进入
    lock.lock(); 
    try {
        // 获得当前数组对象
        Object[] elements = getArray(); 
        int len = elements.length;
        // 1. 旧数组元素拷贝到一个新的数组中,新数组的长度是:旧数组长度+1
        Object[] newElements = Arrays.copyOf(elements, len + 1);
        // 2. 追加元素到新数组末尾(下标是从0开始)。上述新数组的长度的最后一个下标=新元素
        newElements[len] = e;
        // 3. 指向新数组
        setArray(newElements);
        return true;
    } finally {
        // 释放锁
        lock.unlock();
    }
}

CopyOnWriteArraySet

CopyOnWriteArrayList 与 CopyOnWriteArraySet 同理,就不多展示了

public static void main(String[] args) {
    //  Set<String> set = new HashSet<>();
    // Set<String> set = Collections.synchronizedSet(new HashSet<>()); 
    Set<String> set = new CopyOnWriteArraySet<>();
    for (int i = 0; i < 10; i++) {
        new Thread(()->{
            set.add(UUID.randomUUID().toString().substring(0,5));
            System.out.println(set);
        },String.valueOf(i)).start();
    }
}

Set 原理

// Set的底层实际上是HashMap
public HashSet() {
    map = new HashMap<>();
}
// 使用 HashMap 的 key 保存 HashSet 中所有元素  
public boolean add(E e) {
        return map.put(e, PRESENT)==null; //PRESENT是固定值,目的是确保key不重复
}

总结

HashSet本质是在内部维护一个HashMap对象,所有数据交给HashMap处理,LinkedHashSet是HashSet的子类,也是一个空壳,构造方法都调用HashSet的一个default构造方法,LinkedHashSet也是对LinkedHashMap包装了一层,TreeSet也是基于TreeMap实现的

HashSet(int initialCapacity, float loadFactor, boolean dummy) {
    map = new LinkedHashMap<>(initialCapacity, loadFactor);
}

常见问题

一、Vector 和 CopyOnWriteArrayList 区别?

  • Vector 每个方法都加了锁,CopyOnWriteArrayList 读操作未加锁,读性能高于Vector
  • Vector 每次扩容的大小都是原数组的2倍,CopyOnWriteArrayList无需扩容,通过cow思想满足容量要求

二、HashSet的特点:

  • 无序的
  • 不允许元素重复
  • 最多只有一个null值
  • 不是同步的,不安全

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值