概念
CopyOnWrite
是什么呢,從字面上看,就是在寫入時(shí)復(fù)制??雌饋砻菜坪芎唵?,那么寫入時(shí)復(fù)制,具體是怎么實(shí)現(xiàn)的呢?
先來說說思想,具體怎么實(shí)現(xiàn)等下分析
CopyOnWrite
的思想就是:當(dāng)向一個(gè)容器中添加元素的時(shí)候,不是直接在當(dāng)前這個(gè)容器里面添加的,而是復(fù)制出來一個(gè)新的容器,在新的容器里面添加元素,添加完畢之后再將原容器的引用指向新的容器,這樣就實(shí)現(xiàn)了寫入時(shí)復(fù)制
你還記得在提到數(shù)據(jù)庫的時(shí)候,一般都會(huì)說主從復(fù)制,讀寫分離嗎?CopyOnWrite
的設(shè)計(jì)思想是不是和經(jīng)常說的主從復(fù)制,讀寫分離如出一撤?
(推薦教程:Java教程)
優(yōu)缺點(diǎn)
了解概念之后,對(duì)它的優(yōu)缺點(diǎn)應(yīng)該就比較好理解了
優(yōu)點(diǎn)就是,讀和寫可以并行執(zhí)行,因?yàn)樽x的是原來的容器,寫的是新的容器,它們之間互不影響,所以讀和寫是可以并行執(zhí)行的,在某些高并發(fā)場景下,可以提高程序的響應(yīng)時(shí)間
但是呢,你也看到了, CopyOnWrite
是在寫入的時(shí)候,復(fù)制了一個(gè)新的容器出來,所以要考慮它的內(nèi)存開銷問題,又回到了在學(xué)算法時(shí)一直強(qiáng)調(diào)的一個(gè)思想:拿空間換時(shí)間
需要注意一下,它只保證數(shù)據(jù)的最終一致性。因?yàn)樵谧x的時(shí)候,讀取的內(nèi)容是原容器里面的內(nèi)容,新添加的內(nèi)容是讀取不到的
基于它的優(yōu)缺點(diǎn)應(yīng)該就可以得出一個(gè)結(jié)論:CopyOnWrite
適用于寫操作非常少的場景,而且還能夠容忍讀寫的暫時(shí)不一致 如果你的應(yīng)用場景不適合,那還是考慮使用別的方法來實(shí)現(xiàn)吧
還有一點(diǎn)需要注意的是:在寫入時(shí),它會(huì)復(fù)制一個(gè)新的容器,所以如果有寫入需求的話,最好可以批量寫入,因?yàn)槊看螌懭氲臅r(shí)候,容器都會(huì)進(jìn)行復(fù)制,如果能夠減少寫入的次數(shù),就可以減少容器的復(fù)制次數(shù)
在 JUC
包下,實(shí)現(xiàn) CopyOnWrite
思想的就是 CopyOnWriteArrayList
& CopyOnWriteArraySet
這兩個(gè)方法,本篇文章側(cè)重于講清楚 CopyOnWriteArrayList
CopyOnWriteArrayList
在CopyOnWriteArrayList
中,需要注意的是 add
方法:
public boolean add(E e) {
final ReentrantLock lock = this.lock;
// 在寫入的時(shí)候,需要加鎖,如果不加鎖的話,在多線程場景下可能會(huì)被 copy 出 n 個(gè)副本出來
// 加鎖之后,就能保證在進(jìn)行寫時(shí),只有一個(gè)線程在操作
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
// 復(fù)制原來的數(shù)組
Object[] newElements = Arrays.copyOf(elements, len + 1);
// 將要添加的元素添加到新數(shù)組中
newElements[len] = e;
// 將對(duì)原數(shù)組的引用指向新的數(shù)組
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
在寫的時(shí)候需要加鎖,但是在讀取的時(shí)候不需要添加
因?yàn)樽x取的是原數(shù)組的元素,對(duì)新數(shù)組沒有什么影響,加了鎖反而會(huì)增加性能開銷
public E get(int index) {
return get(getArray(), index);
}
舉個(gè)例子:
CopyOnWrite
在 JUC
包下,那么它就保證了線程安全
咱們來做個(gè)小 demo
驗(yàn)證一下:
@Slf4j
public class ArrayListExample {
// 請(qǐng)求總數(shù)
public static int clientTotal = 5000;
// 同時(shí)并發(fā)執(zhí)行的線程數(shù)
public static int threadTotal = 200;
private static List<Integer> list = new ArrayList<>();
public static void main(String[] args) throws Exception{
ExecutorService executorService = Executors.newCachedThreadPool();
final Semaphore semaphore = new Semaphore(threadTotal);
final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
for (int i = 0; i < clientTotal; i++) {
final int count = i;
executorService.execute(()->{
try {
semaphore.acquire();
update(count);
semaphore.release();
} catch (Exception e) {
log.error("exception",e);
}
countDownLatch.countDown();
});
}
countDownLatch.await();
executorService.shutdown();
log.info("size:{}",list.size());
}
private static void update(int i){
list.add(i);
}
}
上面是客戶端請(qǐng)求 5000 次,有 200 個(gè)線程在同時(shí)請(qǐng)求,我使用的是 ArrayList
實(shí)現(xiàn),咱們看下打印結(jié)果:
如果是線程安全的話,那么最后的結(jié)果應(yīng)該是 5000 才對(duì),多運(yùn)行幾次你會(huì)發(fā)現(xiàn),每次程序的執(zhí)行結(jié)果都是不一樣的
如果是 CopyOnWriteArrayList
呢?
@Slf4j
public class CopyOnWriteArrayListExample {
// 請(qǐng)求總數(shù)
public static int clientTotal = 5000;
// 同時(shí)并發(fā)執(zhí)行的線程數(shù)
public static int threadTotal = 200;
private static List<Integer> list = new CopyOnWriteArrayList<>();
public static void main(String[] args) throws Exception{
ExecutorService executorService = Executors.newCachedThreadPool();
final Semaphore semaphore = new Semaphore(threadTotal);
final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
for (int i = 0; i < clientTotal; i++) {
final int count = i;
executorService.execute(()->{
try {
semaphore.acquire();
update(count);
semaphore.release();
} catch (Exception e) {
log.error("excepiton",e);
}
countDownLatch.countDown();
});
}
countDownLatch.await();
executorService.shutdown();
log.info("size:{}",list.size());
}
private static void update(int i){
list.add(i);
}
}
多運(yùn)行幾次,結(jié)果都是一樣的:
由此可見,CopyOnWriteArrayList
是線程安全的。
(推薦微課:Java微課)
文章來源 公眾號(hào):Java極客技術(shù)
作者:鴨血粉絲
以上就是關(guān)于 Java 并發(fā) CopyOnWrite 的相關(guān)介紹了,希望對(duì)大家有所幫助。