JUC--CopyOnWriteArrayList源码分析(基于JDK1.8)

1 概述

我们来设想一个场景,在并发的情况下,我们想要使用一个List来保存对象,这个时候为了让List是线程安全的,我们能怎么做呢?首先我们能够想到的应该是使用synchronized对List对象加锁,或者在使用List对象的地方使用ReentrantLock来保证线程同步,但是这样就有一个问题,我们知道对List中的对象进行读操作的时候并不会存在数据一致性的问题,只有在写的时候才有数据一致性的问题,所以这个时候就需要我们的CopyOnWriteArrayList上场了。

CopyOnWriteArrayList使用了读写锁分离的思想,针对写操作重写复制一份数据,将新数据添加到复制的数据当中,然后将对象的引用指向新的集合。而在读取操作的时候,直接读取集合本身的数据,而不需要复制操作,下面我们来看一个CopyOnWriteArrayList使用的具体实例。

2 使用实例

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

/**
 * @author: LIUTAO
 * @Date: Created in 2019/2/22  14:02
 * @Modified By:
 */
public class CopyOnWriteArrayListDemo {
    public static void main(String[] args) {
        List<String> a = new ArrayList<>();
        a.add("a");
        a.add("b");
        a.add("c");

        CopyOnWriteArrayList<String> b = new CopyOnWriteArrayList<>(a);
        Thread thread = new Thread(() ->{
           int count = 1;
            while (count < 5){
                b.add(String.valueOf(count++));
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        thread.setDaemon(true);
        thread.start();
        b.stream().forEach( str -> {
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(str);
            System.out.println(b.hashCode());
        });
    }
}

程序输出如下:

a
121226914
b
534948995
c
534948995

从上面的输出我们可以看出b的引用地址在随着向里面写入数据而改变。所以这也验证了CoypOnWriteArrayList针对写如操作是将数据进行复制后写入,然后将集合地址重新指向新的地址。

在这里我们顺便说一下CoypOnWriteArrayList的使用场景为读多写少的场景,比如黑名单、白名单和导航菜单。

3 源码分析

针对CoypOnWriteArrayList的源码,我们仅仅分析下add和get函数就行,其余的函数大家可以自行查看源码,也不是很难。

(1)add

前文我们提到了CoypOnWriteArrayList的写入操作是进行了线程同步操作的,并且将原数据复制一份,然后在副本中进行数据操作后,将集合的指针指向副本。下面我们来看看具体的逻辑。

public boolean add(E e) {

        //这里使用了ReentrantLock来保证线程安全
        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();
        }
    }

可以看出,这里其实就是简单的复制、添加、赋值操作。使用了ReentrantLock来保证线程安全。

(2)get

通过对ArrayList源码的分析,我们纸袋ArrayList底层是使用数组来保存数据的。所以从CoypOnWriteArrayList的名字我们可以猜想它也是使用数组来保存数据的。

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];
    }

上面就是对源码的分析,欢迎大家交流和指正。谢谢!

 

 

 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值