您好,登錄后才能下訂單哦!
這篇文章主要介紹了Java的CopyOnWrite怎么實現的相關知識,內容詳細易懂,操作簡單快捷,具有一定借鑒價值,相信大家閱讀完這篇Java的CopyOnWrite怎么實現文章都會有所收獲,下面我們一起來看看吧。
CopyOnWrite
是什么呢,從字面上看,就是在寫入時復制。看起來貌似很簡單,那么寫入時復制,具體是怎么實現的呢?
先來說說思想,具體怎么實現等下分析
CopyOnWrite
的思想就是:當向一個容器中添加元素的時候,不是直接在當前這個容器里面添加的,而是復制出來一個新的容器,在新的容器里面添加元素,添加完畢之后再將原容器的引用指向新的容器,這樣就實現了寫入時復制
你還記得在提到數據庫的時候,一般都會說主從復制,讀寫分離嗎?CopyOnWrite
的設計思想是不是和經常說的主從復制,讀寫分離如出一撤?
了解概念之后,對它的優缺點應該就比較好理解了
優點就是,讀和寫可以并行執行,因為讀的是原來的容器,寫的是新的容器,它們之間互不影響,所以讀和寫是可以并行執行的,在某些高并發場景下,可以提高程序的響應時間
但是呢,你也看到了, CopyOnWrite
是在寫入的時候,復制了一個新的容器出來,所以要考慮它的內存開銷問題,又回到了在學算法時一直強調的一個思想:拿空間換時間
需要注意一下,它只保證數據的最終一致性。因為在讀的時候,讀取的內容是原容器里面的內容,新添加的內容是讀取不到的
基于它的優缺點應該就可以得出一個結論:CopyOnWrite
適用于寫操作非常少的場景,而且還能夠容忍讀寫的暫時不一致 如果你的應用場景不適合,那還是考慮使用別的方法來實現吧
還有一點需要注意的是:在寫入時,它會復制一個新的容器,所以如果有寫入需求的話,最好可以批量寫入,因為每次寫入的時候,容器都會進行復制,如果能夠減少寫入的次數,就可以減少容器的復制次數
在 JUC
包下,實現 CopyOnWrite
思想的就是 CopyOnWriteArrayList
& CopyOnWriteArraySet
這兩個方法,本篇文章側重于講清楚 CopyOnWriteArrayList
在CopyOnWriteArrayList
中,需要注意的是 add
方法:
public boolean add(E e) { final ReentrantLock lock = this.lock; // 在寫入的時候,需要加鎖,如果不加鎖的話,在多線程場景下可能會被 copy 出 n 個副本出來 // 加鎖之后,就能保證在進行寫時,只有一個線程在操作 lock.lock(); try { Object[] elements = getArray(); int len = elements.length; // 復制原來的數組 Object[] newElements = Arrays.copyOf(elements, len + 1); // 將要添加的元素添加到新數組中 newElements[len] = e; // 將對原數組的引用指向新的數組 setArray(newElements); return true; } finally { lock.unlock(); } }
在寫的時候需要加鎖,但是在讀取的時候不需要添加
因為讀取的是原數組的元素,對新數組沒有什么影響,加了鎖反而會增加性能開銷
public E get(int index) { return get(getArray(), index); }
CopyOnWrite
在 JUC
包下,那么它就保證了線程安全
咱們來做個小 demo
驗證一下:
@Slf4j public class ArrayListExample { // 請求總數 public static int clientTotal = 5000; // 同時并發執行的線程數 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); } }
上面是客戶端請求 5000 次,有 200 個線程在同時請求,我使用的是 ArrayList
實現,咱們看下打印結果:
如果是線程安全的話,那么最后的結果應該是 5000 才對,多運行幾次你會發現,每次程序的執行結果都是不一樣的
如果是 CopyOnWriteArrayList
呢?
@Slf4j public class CopyOnWriteArrayListExample { // 請求總數 public static int clientTotal = 5000; // 同時并發執行的線程數 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); } }
關于“Java的CopyOnWrite怎么實現”這篇文章的內容就介紹到這里,感謝各位的閱讀!相信大家對“Java的CopyOnWrite怎么實現”知識都有一定的了解,大家如果還想學習更多知識,歡迎關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。