您的位置:首页 > 编程语言 > Java开发

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

2017-12-25 17:37 435 查看
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
...
从如上结果可以看出,在写入数据的时候,读取操作并没有被终止,写入完毕后读取操作可以读取到最新的数据。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: