Java多线程之并发容器:CopyOnWrite到底干啥用的

CopyOnWrite从字面上理解就是写入的时候做复制操作。而CopyOnWrite是一个Java5之后出现的并发容器,目的是为了提高并发的存取效率。对比CopyOnWrite、ArrayList和Vector源码,可以发现Vector是get和set方法都使用了synchronized关键字做了同步,ArrayList都没有用该关键字,很明显线程不安全;查看CopyOnWrite源码,get方法没有同步,add方法做了同步,也就是说CopyOnWrite的写入操作完全不影响get操作(一种读写分离的思想)。 
起初我也很不明白该容器是有这个好处,但在什么场景下使用呢? 

思考一个场景,一个比较稳定的白名单或者黑名单list,每晚或每周会更新一次,其他时间均被大量线程在读取,这个时候我们用什么合适?并发场景ArrayList首先排除,Vector性能存在问题,官方也不推荐用了,也排查;Collections.synchronizedList呢,该并发容器做了改进,get和add方法均为同步方法,如果平时的读取操作也要走同步那就很不合适了,影响性能,那貌似值剩下CopyOnWrite了。对于这种读多写少的情况,用CopyOnWrite再合适不过了。但是,是否有人会问为什么CopyOnWrite的add操作是直接复制一份,再做写操作,为什么不直接add呢?这是为了在写入时完全不影响读取操作,在所有的数据都写入完毕后读操作才能读取到最新的数据。 


jdk已提供CopyOnWriteArrayList 和 CopyOnWriteSet方法

下面是一个CopyOnWrite的使用例子:

package copyonwrite;

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

/**
 * Created by dingxiangyong on 2016/3/26.
 */
public class Test {
    /**
     * 结束标识
     */
    static volatile boolean stopFlag = false;

    public static void main(String[] args) {

        CopyOnWriteArrayList<Integer> list = new CopyOnWriteArrayList<Integer>();
        //初始化集合
        for (int i = 0; i < 100000; i++) {
            list.add(i);
        }

        ReadTask readTask1 = new ReadTask(list);
        ReadTask readTask2 = new ReadTask(list);
        ReadTask readTask3 = new ReadTask(list);
        ReadTask readTask4 = new ReadTask(list);

        WriteTask writeTask = new WriteTask(list);

        ExecutorService service = Executors.newFixedThreadPool(5);
        service.execute(readTask1);
        service.execute(readTask2);
        service.execute(readTask3);
        service.execute(readTask4);
        service.execute(writeTask);

        service.shutdown();
    }
}

/**
 * 读线程
 */
class ReadTask implements Runnable {

    private List<Integer> list;

    public ReadTask(List<Integer> list) {
        this.list = list;
    }


    @Override
    public void run() {
        while (!Test.stopFlag) {
            try {
                int index = (int) (Math.random() * list.size());
                Integer value = list.get(index);
                System.out.println("正在读取值:" + value);
                Thread.sleep(10); //模拟耗时操作
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

/**
 * 写线程
 */
class WriteTask implements Runnable {

    private List<Integer> list;

    public WriteTask(List<Integer> list) {
        this.list = list;
    }


    @Override
    public void run() {
        List<Integer> newList = new ArrayList<Integer>();

        for (int i = 100000; i < 2000000; i++) {
            newList.add(i);
        }

        try {
            Thread.sleep(100); //模拟耗时操作,让读线程可以读到
            System.out.println("准备写入copyonwritelist");
            list.addAll(newList);
            System.out.println("写入copyonwritelist完毕");
            Thread.sleep(2000); //模拟耗时操作,让读线程可以读到
            Test.stopFlag = true;
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

运行结果:

....
正在读取值:79029
正在读取值:29270
正在读取值:61694
正在读取值:3373
正在读取值:33155
正在读取值:78532
正在读取值:12583
正在读取值:2778
正在读取值:32738
正在读取值:82363
准备写入copyonwritelist
正在读取值:61661
正在读取值:5817
正在读取值:84120
正在读取值:43250
写入copyonwritelist完毕
正在读取值:1744710
正在读取值:422552
正在读取值:1038943
正在读取值:1229489
正在读取值:200887
正在读取值:1547701
正在读取值:1841575
正在读取值:1785725
...

从如上结果可以看出,在写入数据的时候,读取操作并没有被终止,写入完毕后读取操作可以读取到最新的数据。


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值