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呢?這是爲了在寫入時完全不影響讀取操作,在所有的數據都寫入完畢後讀操作才能讀取到最新的數據。
下面是一個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
...
從如上結果可以看出,在寫入數據的時候,讀取操作並沒有被終止,寫入完畢後讀取操作可以讀取到最新的數據。