>  기사  >  Java  >  Java 동시 프로그래밍: 동시 컨테이너 CopyOnWriteArrayList의 구현 원리

Java 동시 프로그래밍: 동시 컨테이너 CopyOnWriteArrayList의 구현 원리

php是最好的语言
php是最好的语言원래의
2018-07-30 11:41:051648검색

 COW라고도 하는 Copy-On-Write는 프로그래밍에 사용되는 최적화 전략입니다. 기본 아이디어는 모든 사람이 처음부터 동일한 콘텐츠를 공유한다는 것입니다. 누군가가 콘텐츠를 수정하려고 하면 실제로 콘텐츠를 복사하여 새로운 콘텐츠를 만든 다음 이를 수정합니다. 이것은 일종의 지연된 게으름 전략입니다. JDK1.5부터 Java 동시성 패키지는 CopyOnWrite 메커니즘을 사용하여 구현된 두 개의 동시 컨테이너(CopyOnWriteArrayList 및 CopyOnWriteArraySet)를 제공합니다. CopyOnWrite 컨테이너는 매우 유용하며 여러 동시 시나리오에서 사용할 수 있습니다.

CopyOnWrite 컨테이너란 무엇인가요?

CopyOnWrite 컨테이너는 쓰기 시 복사되는 컨테이너입니다. 컨테이너에 요소를 추가할 때 현재 컨테이너에 직접 추가하는 것이 아니라 먼저 현재 컨테이너를 복사하여 새 컨테이너를 만든 다음 요소를 추가한 후 새 컨테이너에 요소를 추가한다는 것이 널리 이해되고 있습니다. 그런 다음 원래 컨테이너의 참조가 새 컨테이너를 가리키도록 합니다. 이것의 장점은 현재 컨테이너가 어떤 요소도 추가하지 않기 때문에 잠금 없이 CopyOnWrite 컨테이너에서 동시 읽기를 수행할 수 있다는 것입니다. 따라서 CopyOnWrite 컨테이너도 읽기와 쓰기를 분리한 아이디어이고, 읽기와 쓰기는 서로 다른 컨테이너입니다.

CopyOnWriteArrayList의 구현 원리

CopyOnWriteArrayList를 사용하기 전에 소스 코드를 읽고 어떻게 구현되는지 살펴보겠습니다. 다음 코드는 CopyOnWriteArrayList의 add 메소드 구현입니다(CopyOnWriteArrayList에 요소 추가). 추가할 때 잠궈야 한다는 것을 알 수 있습니다. 그렇지 않으면 여러 스레드로 쓸 때 N 개의 복사본이 복사됩니다.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

/**/**

     * Appends the specified element to the end of this list.

     *

     * @param e element to be appended to this list

     * @return <tt>true</tt> (as specified by {@link Collection#add})

     */

    public boolean add(E e) {

    final ReentrantLock lock = this.lock;

    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();

    }

    }

   读的时候不需要加锁,如果读的时候有多个线程正在向CopyOnWriteArrayList添加数据,读还是会读到旧的数据,因为写的时候不会锁住旧的CopyOnWriteArrayList。

     *

1

2

3

public E get(int index) {

    return get(getArray(), index);

}

     * 지정된 요소를 이 목록의 끝에 추가합니다.
     * @param 이 목록에 추가할 요소#🎜🎜# #🎜🎜#     * @return <tt>true</tt> ({@link Collection#add}로 지정됨)#🎜🎜# #🎜🎜#     */#🎜🎜# #🎜🎜# 공개 부울 add(E e) {#🎜🎜# #🎜🎜# final ReentrantLock 잠금 = this.lock;#🎜🎜 # #🎜🎜# lock.lock();#🎜🎜# #🎜🎜# 시도 {#🎜🎜# #🎜🎜# Object[] 요소 = getArray();#🎜🎜# #🎜🎜# int len = elements.length;#🎜🎜# #🎜🎜# Object[] newElements = Arrays.copyOf(elements, len + 1);#🎜 🎜# #🎜🎜# newElements[len] = e;#🎜🎜# #🎜🎜# setArray(newElements);#🎜🎜# #🎜🎜# return true;#🎜🎜# #🎜🎜# } 드디어 {#🎜🎜# #🎜🎜# lock.unlock();#🎜🎜# #🎜🎜# }#🎜🎜# #🎜🎜# }#🎜🎜# #🎜🎜# #🎜🎜##🎜🎜##🎜🎜##🎜🎜# 읽을 때 여러 스레드가 CopyOnWriteArrayList에 데이터를 추가하는 경우 읽기에서는 여전히 이전 데이터를 읽습니다. 잠기지 않습니다. #🎜🎜##🎜🎜##🎜🎜##🎜🎜##🎜🎜# #🎜🎜#1#🎜🎜# #🎜🎜#2#🎜🎜# #🎜🎜#3#🎜🎜# #🎜🎜# #🎜🎜# #🎜🎜#공개 E get(int index) {#🎜🎜# #🎜🎜# return get(getArray(), index);#🎜🎜# #🎜🎜#}#🎜🎜# #🎜🎜# #🎜🎜##🎜🎜##🎜🎜#

 CopyOnWriteMap은 JDK에서 제공되지 않습니다. CopyOnWriteArrayList를 참조하여 구현할 수 있습니다.

가져오기java.util.Set;

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

가져오기 java.util.Collection;import java.util.Collection;

import java.util.Map;

import java.util.Set;

 

public class CopyOnWriteMap<K, V> implements Map<K, V>, Cloneable {

    private volatile Map<K, V> internalMap;

 

    public CopyOnWriteMap() {

        internalMap = new HashMap<K, V>();

    }

 

    public V put(K key, V value) {

 

        synchronized (this) {

            Map<K, V> newMap = new HashMap<K, V>(internalMap);

            V val = newMap.put(key, value);

            internalMap = newMap;

            return val;

        }

    }

 

    public V get(Object key) {

        return internalMap.get(key);

    }

 

    public void putAll(Map<? extends K, ? extends V> newData) {

        synchronized (this) {

            Map<K, V> newMap = new HashMap<K, V>(internalMap);

            newMap.putAll(newData);

            internalMap = newMap;

        }

    }

}

가져오기 java.util.Map;
#🎜🎜# #🎜🎜#공개 클래스 CopyOnWriteMap<k v> </k>구현 Map<k v> , 복제 가능 {</k>#🎜🎜# #🎜🎜# 비공개 휘발성 Map<K, V>#🎜🎜# #🎜🎜# #🎜🎜# #🎜🎜# 공개 CopyOnWriteMap() {#🎜🎜# #🎜🎜# internalMap = new HashMap<K, V>();#🎜🎜# #🎜🎜# }#🎜🎜# #🎜🎜# #🎜🎜# #🎜🎜# public V put(K 키, V 값) {#🎜🎜# #🎜🎜# #🎜🎜# #🎜🎜# 동기화 (이것) {#🎜🎜# #🎜🎜# Map<k v> newMap = </k>new HashMap<k v>(internalMap);</k>#🎜🎜# #🎜🎜# V val = newMap.put(키, 값);#🎜🎜# #🎜🎜# internalMap = newMap;#🎜🎜# #🎜🎜# 반환 값;#🎜🎜# #🎜🎜# }#🎜🎜# #🎜🎜# }#🎜🎜# #🎜🎜# #🎜🎜# #🎜🎜# 공개 V get(객체 키) {#🎜🎜# #🎜🎜# return internalMap.get(key);#🎜🎜# #🎜🎜# }#🎜🎜# #🎜🎜# #🎜🎜# #🎜🎜# public void putAll(Map<? extends K, ? 확장 V> newData) {#🎜🎜# #🎜🎜# 동기화 (이것) {#🎜🎜# #🎜🎜# Map<K, V> newMap = new HashMap<K, V>(internalMap);#🎜🎜# #🎜🎜# newMap.putAll(newData);#🎜🎜# #🎜🎜# internalMap = newMap;#🎜🎜# #🎜🎜#<코드> <코드>}#🎜🎜# #🎜🎜#<코드> <코드>}#🎜🎜# #🎜🎜#<코드>}#🎜🎜# #🎜🎜# #🎜🎜##🎜🎜##🎜🎜#

구현은 매우 간단합니다. CopyOnWrite 메커니즘을 이해하면 다양한 CopyOnWrite 컨테이너를 구현하고 다양한 애플리케이션 시나리오에서 사용할 수 있습니다.

CopyOnWrite의 응용 시나리오

 CopyOnWrite 동시 컨테이너는 더 많이 읽고 더 적게 쓰는 동시 시나리오에서 사용됩니다. 예를 들어, 화이트리스트, 블랙리스트, 제품 카테고리 액세스 및 업데이트 시나리오입니다. 검색 웹사이트가 있는 경우 사용자는 이 웹사이트의 검색창에 키워드를 입력하여 콘텐츠를 검색하지만 일부 키워드는 검색이 허용되지 않습니다. 검색할 수 없는 키워드는 블랙리스트에 등록되어 매일 밤 업데이트됩니다. 사용자가 검색할 때 현재 키워드가 블랙리스트에 있는지 확인합니다. 그렇다면 검색을 수행할 수 없다는 메시지가 표시됩니다. 구현 코드는 다음과 같습니다.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

패키지 com.ifeve.book;package com.ifeve.book;

import java.util.Map;

import com.ifeve.book.forkjoin.CopyOnWriteMap;

/**

* 黑名单服务

*

* @author fangtengfei

*

*/

public class BlackListServiceImpl {

private static CopyOnWriteMap<String, Boolean> blackListMap = new CopyOnWriteMap<String, Boolean>(

            1000);

 

    public static boolean isBlackList(String id) {

        return blackListMap.get(id) == null false true;

    }

 

    public static void addBlackList(String id) {

        blackListMap.put(id, Boolean.TRUE);

    }

 

    /**

     * 批量添加黑名单

     *

     * @param ids

     */

    public static void addBlackList(Map<String,Boolean> ids) {

        blackListMap.putAll(ids);

    }

 

}

가져오기 java.util.Map; 🎜 🎜가져오기 com.ifeve.book.forkjoin.CopyOnWriteMap;🎜 🎜 🎜 🎜/**🎜 🎜 * 黑name单服务🎜 🎜 *🎜 🎜 * @author fangtengfei🎜 🎜 *🎜 🎜 */🎜 🎜공개 클래스 BlackListServiceImpl {🎜 🎜 🎜 🎜 private static CopyOnWriteMap<String, Boolean> blackListMap = new CopyOnWriteMap<String, Boolean>(🎜 🎜 1000);🎜 🎜 🎜 🎜 공개 정적 부울 isBlackList(문자열 ID) {🎜 🎜 return blackListMap.get(id) == null 거짓 : ;🎜 🎜 }🎜 🎜 🎜 🎜 public static void addBlackList(String id) {🎜 🎜 blackListMap.put(id, Boolean.TRUE);🎜 🎜 }🎜 🎜 🎜 🎜 /**🎜 🎜 * 일괄적으로 블랙리스트 추가🎜 🎜 *🎜 🎜 * @param ID🎜 🎜 */🎜 🎜 public static void addBlackList(Map<string> ids) {</string> 🎜 🎜 blackListMap.putAll(ids);🎜 🎜 }🎜 🎜 🎜 🎜}🎜 🎜 🎜🎜🎜

코드는 매우 간단하지만 CopyOnWriteMap을 사용할 때 주의해야 할 두 가지 사항이 있습니다.

1. 확장 오버헤드를 줄입니다. 쓰기 중 CopyOnWriteMap 확장 오버헤드를 방지하려면 실제 필요에 따라 CopyOnWriteMap의 크기를 초기화하세요.

 2. 일괄 추가를 사용하세요. 추가할 때마다 컨테이너가 매번 복사되기 때문에 추가 횟수를 줄이면 컨테이너가 복사되는 횟수를 줄일 수 있습니다. 예를 들어 위 코드에서 addBlackList 메서드를 사용합니다.

CopyOnWrite의 단점

CopyOnWrite 컨테이너에는 많은 장점이 있지만 메모리 사용량과 데이터 일관성이라는 두 가지 문제도 있습니다. 그래서 개발할 때 주의를 기울여야 합니다.

메모리 사용량 문제. CopyOnWrite의 쓰기 시 복사 메커니즘으로 인해 쓰기 작업이 수행되면 두 개체, 즉 이전 개체와 새로 작성된 개체가 동시에 메모리에 상주하게 됩니다(참고: 복사 중에는 쓰기 시에만 새 개체가 생성되어 새 컨테이너에 추가되지만 이전 컨테이너의 개체는 계속 사용되므로 개체 메모리의 복사본이 두 개가 됩니다. 이러한 객체가 차지하는 메모리가 비교적 큰 경우(예: 200M 정도) 여기에 100M의 데이터를 쓰면 300M의 메모리를 차지하므로 이때 Yong GC 및 Full GC가 자주 발생할 수 있습니다. 이전에는 CopyOnWrite 메커니즘을 사용하여 매일 밤 큰 개체를 업데이트하는 시스템 서비스를 사용하여 매일 밤 15초의 Full GC가 발생했으며 애플리케이션 응답 시간도 길어졌습니다.

메모리 사용량 문제를 고려하여 컨테이너의 요소를 압축하면 큰 개체의 메모리 소비를 줄일 수 있습니다. 예를 들어 요소가 모두 십진수인 경우 다음으로 압축하는 것을 고려할 수 있습니다. 16진수 시스템 또는 기본 64. 또는 CopyOnWrite 컨테이너를 사용하지 말고 ConcurrentHashMap과 같은 다른 동시 컨테이너를 사용하세요.

 데이터 일관성 문제. CopyOnWrite 컨테이너는 데이터의 최종 일관성만 보장할 수 있지만 데이터의 실시간 일관성은 보장할 수 없습니다. 따라서 작성된 데이터를 즉시 읽으려면 CopyOnWrite 컨테이너를 사용하지 마십시오.

관련 기사:

Java의 동시 프로그래밍: CountDownLatch, CyclicBarrier 및 Semaphore

# 🎜 🎜#[JAVA 동시 프로그래밍 실습] 순차 교착 상태 잠금

관련 동영상:

Java 멀티스레딩 및 동시성 라이브러리 고급 응용 프로그램 동영상 튜토리얼# 🎜 🎜#

위 내용은 Java 동시 프로그래밍: 동시 컨테이너 CopyOnWriteArrayList의 구현 원리의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.