2015-02-23 48 views
1

我現在在學習java.util.concurrent。我想了解CopyOnWriteArrayList在Java中使用CopyOnWriteArrayList

據我所知,這個類看起來像ArrayList,但線程安全。如果你有很多閱讀和較少的寫作,這個類是非常有用的。

這是我的例子。我如何使用它(僅用於學習目的)?

我可以這樣使用它嗎?

package Concurrency; 

import java.util.concurrent.*; 

class Entry { 
    private static int count; 
    private final int index = count++; 
    public String toString() { 
     return String.format(
        "index:%-3d thread:%-3d", 
        index, 
        Thread.currentThread().getId()); 
    } 

} 

class Reader implements Runnable { 
    private CopyOnWriteArrayList<Entry> list; 
    Reader(CopyOnWriteArrayList<Entry> list) { this.list = list; } 
    public void run() { 
      try { 
       while(true) { 
        if(!list.isEmpty()) 
         System.out.println("-out " + list.remove(0)); 
        TimeUnit.MILLISECONDS.sleep(100); 
       } 
      } catch (InterruptedException e) { 
       return; 
      } 
    } 
} 


class Writer implements Runnable { 
    private CopyOnWriteArrayList<Entry> list; 
    Writer(CopyOnWriteArrayList<Entry> list) { this.list = list; } 
    public void run() { 
     try { 
      while(true) { 
       Entry tmp = new Entry(); 
       System.out.println("+in " + tmp); 
       list.add(tmp); 
       TimeUnit.MILLISECONDS.sleep(10); 
      } 
     } catch (InterruptedException e) { 
      return; 
     } 
    } 
} 

public class FourtyOne { 
    static final int nThreads = 7; 
    public static void main(String[] args) throws InterruptedException { 
     CopyOnWriteArrayList<Entry> list = new CopyOnWriteArrayList<>(); 
     ExecutorService exec = Executors.newFixedThreadPool(nThreads); 
     exec.submit(new Writer(list)); 
     for(int i = 0; i < nThreads; i++) 
      exec.submit(new Reader(list)); 
     TimeUnit.SECONDS.sleep(1); 
     exec.shutdownNow(); 
    } 

} 
+0

很多閱讀和_almost不write_。這很簡單,真的。無論何時你的代碼調用一個修改列表的方法,該方法會創建一個數組的副本,它會修改其副本,然後使用單個原子操作交換舊副本。如果任何其他線程試圖同時訪問列表,另一個線程將訪問舊版本或新版本,但保證不會訪問處於無效狀態的版本。 – 2015-02-23 19:51:23

+0

您正在使用ExecutorService(即線程池)執行永不結束的任務('while(true)...')。不要這樣做。我並不是說它不起作用,但這不是線程池的用途。我會使用'new Thread(...)'或'threadFactory.newThread(...)'來創建一個永不結束的線程。 – 2015-02-23 19:56:22

+0

你的消費者(讀者)一味地相信,如果list.isEmpty()返回true,那麼這個列表就會有一些東西在裏面。這是一個天真的假設。當只有一個消費者時,這是事實,但在許多系統中,消費者不止一個。您應該養成編寫消費者代碼的習慣,如果醒來的事件已經被其他消費者線程服務過,則不會拋出NullPointerException異常。 – 2015-02-23 20:00:41

回答

1

請注意在你的例子中,你的一個作者是以給定閱讀器的速度的10倍寫作,導致大量的複製。另請注意,您的閱讀器也在列表中執行寫操作(remove())。

在這種情況下,您以驚人的速度寫入列表,導致嚴重的性能問題,因爲每次更新此列表時都會使用大量內存。

CopyOnWriteArrayList僅用於當同步開銷是問題並且讀取與結構修改的比率很高時。總陣列副本的成本按一個或多個讀者同時訪問列表時看到的性能收益攤銷。這與傳統的同步列表形成了鮮明對比,其中每個訪問(讀或寫)都受某個互斥體控制,因此只有一個線程可以同時對該列表執行一些操作。

如果需要一個簡單的線程安全列表,請考慮由Collections.synchronizedList()提供的同步列表。

另外請注意:

if(!list.isEmpty()){ 
    System.out.println("-out " + list.remove(0)); 
} 

是不是有效的方案,因爲沒有保證該列表將不會 if語句評估後是空的。爲了保證一致的效果,您需要直接檢查返回值list.remove()或將整個段封裝在​​塊中(這違背了使用線程安全結構的目的)。

remove()呼叫,作爲一個結構修改呼叫也應更換等get()一個方法,以確保正在做沒有結構上的修改,而數據正被讀出。

總之,我相信CopyOnWriteArrayList只需要以非常特定的方式使用,並且只有當傳統的同步變得無法接受時纔會變得緩慢。雖然你的例子可以在你自己的計算機上正常工作,但擴展訪問的規模會更大,你會導致gc做太多的工作來維持堆空間。

+0

謝謝你的答案。我明白 - 我應該使用'synchronized(list){...}'或'if(list.remove(0)!= null){...}'。 – Alex 2015-02-24 06:01:17

+0

@Alex請注意,如果您使用'synchronized(list){...}',則不再需要線程安全結構。只要您對每次訪問(讀取**和**寫入)持續使用'synchronized(list){...}',就可以直接使用更快的「非線程安全」對象,如ArrayList。 – initramfs 2015-02-24 09:51:38

+0

好的。你認爲它會更快,而不是使用'CopyOnWriteArrayList'? – Alex 2015-02-24 18:33:39