자바 동기 용기 와 병발 용기 상세 설명

동기 용기
자바 에서 동기 화 용 기 는 주로 2 가지 유형 을 포함한다.
  • Vector,Stack,HashTableCollections 류 에서 제공 하 는 정적 공장 방법 으로 만 든 클래스(Collections.synchronizedXxxx 등 방법)
  • Collections 클래스 에서 제공 하 는 정적 공장 방법 으로 만 든 클래스
  • Vector 는 List 인 터 페 이 스 를 실현 했다.Vector 는 실제 적 으로 하나의 배열 로 Array List 와 유사 하지만 Vector 의 방법 은 모두 synchronized 방법,즉 동기 화 조 치 를 취 했다.
    Stack 도 동기 용기 입 니 다.동기 화 된 방법 도 synchronized 로 동기 화 되 었 습 니 다.이것 은 실제로 Vector 류 에 계승 되 었 습 니 다.
    HashTable 은 Map 인 터 페 이 스 를 실 현 했 습 니 다.HashMap 과 비슷 하지만 HashTable 은 동기 화 처 리 를 했 고 HashMap 은 없습니다.
    동기 용기 결함
    동기 용기 의 동기 화 원 리 는 방법 적 으로 synchronized 로 수식 하 는 것 이다.그렇다면 이 방법 들 은 매번 하나의 스 레 드 만 호출 할 수 있다.
    성능 문제
    synchronized 에 의 해 수 정 된 방법 으로 매번 하나의 스 레 드 만 실행 할 수 있 기 때문에 이 방법 에 접근 하려 는 다른 스 레 드 는 기다 릴 수 밖 에 없습니다.분명히 이런 방식 은 synchronized 를 사용 하지 않 은 용기 보다 성능 이 떨어진다.
    안전 문제
    동기 화 용 기 는 정말 안전 합 니까?
    꼭 그렇지 는 않다.동기 용기 가 꼭 안전 한 것 은 아니다.복합 작업 을 할 때,여전히 자 물 쇠 를 넣 어 보호해 야 한다.
    흔히 볼 수 있 는 복합 조작 은 다음 과 같다.
  • 교체:전체 요 소 를 옮 겨 다 닐 때 까지 요 소 를 반복 적 으로 방문 합 니 다.
  • 점프:지정 한 순서에 따라 현재 요소 의 다음(다음 n 개)요 소 를 찾 습 니 다.
  • 조건 연산:예 를 들 어 없 으 면 추가 등;
  • 안전 하지 않 은 예시
    
    <pre style="margin: 0px; padding: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: &quot;Courier New&quot; !important; font-size: 12px !important;">public class Test { static Vector<Integer> vector = new Vector<Integer>();
    public static void main(String[] args) throws InterruptedException {
    while(true) {
    for (int i=0;i<10;i++)
    vector.add(i);
    Thread thread1 = new Thread(){
    public void run() {
    for (int i=0;i<vector.size();i++)
    vector.remove(i);
    }
    ;
    }
    ;
    Thread thread2 = new Thread(){
    public void run() {
    for (int i=0;i<vector.size();i++)
    vector.get(i);
    }
    ;
    }
    ;
    thread1.start();
    thread2.start();
    while(Thread.activeCount()>10) {
    }
    }
    }
    }
    </pre>
    실행 중 배열 의 크로스 오 버 가 발생 할 수 있 습 니 다.
    Vector 는 스 레 드 가 안전 한데 왜 이 잘못 을 보고 합 니까?간단 합 니 다.Vector 에 대해 서 는 매 시간 하나의 스 레 드 만 접근 할 수 있 도록 보장 할 수 있 지만 이러한 가능성 을 배제 하지 않 습 니 다.
    어느 스 레 드 가 어느 순간 이 문장 을 실행 할 때:
    
    <pre style="margin: 0px; padding: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: &quot;Courier New&quot; !important; font-size: 12px !important;">for (int i=0;i<vector.size();i++)
    vector.get(i);
    </pre>
    만약 이때 vector 의 size 방법 이 10 을 되 돌려 준다 면 i 의 값 은 9 입 니 다.
    그리고 다른 라인 에서 이 문장 을 실 행 했 습 니 다.
    
    <pre style="margin: 0px; padding: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: &quot;Courier New&quot; !important; font-size: 12px !important;">for (int i=0;i<vector.size();i++)
    vector.remove(i);
    </pre>
    다음 9 로 표 시 된 요 소 를 삭 제 했 습 니 다.
    그러면 get 방법 을 통 해 9 로 표 시 된 요 소 를 방문 하면 문제 가 생 길 것 입 니 다.
    안전 예시
    따라서 스 레 드 안전 을 확보 하기 위해 서 는 방법 호출 단 에서 추가 적 인 동기 화 조 치 를 취해 야 합 니 다.아래 와 같 습 니 다.
    
    public class Test {
    static Vector<Integer> vector = new Vector<Integer>();
    public static void main(String[] args) throws InterruptedException {
    while(true) {
    for (int i=0;i<10;i++)
    vector.add(i);
    Thread thread1 = new Thread(){
    public void run() {
    synchronized (Test.class) {
    //       
    for (int i=0;i<vector.size();i++)
    vector.remove(i);
    }
    }
    ;
    }
    ;
    Thread thread2 = new Thread(){
    public void run() {
    synchronized (Test.class) {
    for (int i=0;i<vector.size();i++)
    vector.get(i);
    }
    }
    ;
    }
    ;
    thread1.start();
    thread2.start();
    while(Thread.activeCount()>10) {
    }
    }
    }
    }
    ConcurrentModificationException 이상
    Vector 등 용 기 를 동시에 교체 수정 할 때 Concurrent ModificationException 이상 을 보고 하고 이 이상 에 대해 서 는 후속 글 에서 설명 할 예정 이다.
    하지만 병발 용기 에 서 는 이 문제 가 발생 하지 않 는 다.
    병발 용기
    JDK 의 java.util.concurrent 패키지(즉 jc)에서 매우 유용 한 병발 용 기 를 제공 합 니 다.
  • CopyOnWriteArray List-스 레 드 가 안전 한 Array List
  • CopyOn Write Array Set-스 레 드 가 안전 한 Set 은 내부 에 CopyOn Write Array List 가 포함 되 어 있 기 때문에 본질 적 으로 CopyOn Write Array List 에 의 해 이 루어 집 니 다.
  • Concurrent SkipListset-스 레 드 안전 에 해당 하 는 TreeSet.그것 은 질서 있 는 Set 이다.그것 은 Concurrent SkipListMap 에서 이 루어 집 니 다.
  • Concurrent HashMap-라인 이 안전 한 HashMap.세그먼트 자 물 쇠 를 사용 하여 효율 적 인 병발 을 실현 하 다.
  • ConcurrentSkipListMap-스 레 드 안전 질서 있 는 지도.점프 시 계 를 사용 하여 효율 적 인 병발 을 실현 하 다.
  • Concurrent LinkedQueue-스 레 드 안전 한 무한 대기 열.1 층 은 싱글 체인 표를 사용한다.FIFO 지원.
  • Concurrent LinkedDeque-스 레 드 가 안전 한 무한 쌍 단 대기 열.바 텀 은 양 방향 링크 를 사용한다.FIFO 와 FILO 를 지원 합 니 다.
  • Array BlockingQueue-배열 이 실현 하 는 차단 대기 열.
  • 링크 드 BlockingQueue-링크 가 실 현 된 차단 대기 열.
  • 링크 드 BlockingDeque-양 방향 링크 가 실 현 된 양 단 차단 대기 열.
  • ConcurrentHashMap
    요점
    역할:Concurrent HashMap 은 라인 이 안전 한 HashMap 입 니 다.
    원리:JDK 6 와 JDK 7 에서 Concurrent HashMap 은 세그먼트 잠 금 체 제 를 사용 했다.JDK 8 에 서 는 잠 금 세그먼트 메커니즘 을 지양 하고 CAS 알고리즘 을 활용 한 것 으로 변경 했다.
    소스 코드
    JDK7
    Concurrent HashMap 류 가 jdk 1.7 에서 디자인 되 었 는데 그 기본 구 조 는 그림 과 같다.

    모든 segment 는 HashEntry[]table 입 니 다.table 의 모든 요 소 는 본질 적 으로 HashEntry 의 단 방향 대기 열 입 니 다.예 를 들 어 table[3]을 첫 번 째 노드 로 하고 table[3]->next 는 노드 1 이 며 그 다음 에 노드 2 로 순서대로 유추 합 니 다.
    
    <pre style="margin: 0px; padding: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: &quot;Courier New&quot; !important; font-size: 12px !important;">public class ConcurrentHashMap<K, V> extends AbstractMap<K, V>
    implements ConcurrentMap<K, V>, Serializable { //    hashmap      map,  segment     ; hashtable  ,          put, remove   ,        ,  //                  ,       
    final Segment<K,V>[] segments;
    //    Segment       hashmap,  table            ,   ReentrantLock,          
    static final class Segment<K,V> extends ReentrantLock implements Serializable {
    transient volatile HashEntry<K,V>[] table;
    transient int count;
    }
    //     ,  Key, Value 
    static final class HashEntry<K,V> {
    final int hash;
    final K key;
    volatile V value;
    volatile HashEntry<K,V> next;
    }
    }
    </pre>
    JDK8
    jdk 8 에서 주로 2 가지 개선 을 했 습 니 다.
  • segments 필드 를 취소 하고 transient volatile HashEntry[]table 로 데 이 터 를 저장 하 며 table 배열 요 소 를 자물쇠 로 하여 각 줄 의 데 이 터 를 잠 그 고 충돌 할 확률 을 더욱 감소 합 니 다.
  • 은 원래 table 배열+단 방향 링크 의 데이터 구 조 를 table 배열+단 방향 링크+빨 간 검 은 나무의 구조 로 변경 합 니 다.
  • hash 표 에 있어 서 가장 핵심 적 인 능력 은 key hash 를 배열 에 고 르 게 분포 하 는 것 이다.hash 이후 에 고 르 게 흩 어 져 있 으 면 table 배열 의 모든 대기 열 길 이 는 0 또는 1 입 니 다.
    그러나 실제 상황 이 항상 이상 적 인 것 은 아 닙 니 다.Concurrent HashMap 류 의 기본 로드 인 자 는 0.75 이지 만 데이터 양 이 너무 많 거나 운 이 좋 지 않 은 상황 에서 일부 대기 행렬 의 길이 가 너무 긴 상황 이 존재 합 니 다.만약 에 단 방향 목록 방식 을 사용한다 면 특정한 노드 의 시간 복잡 도 는 O(n)입 니 다.
    따라서 개수 가 8(기본 값)이 넘 는 목록 에 대해 jdk 1.8 에서 붉 은 검 은 나무의 구 조 를 사용 하면 조회 시간 복잡 도 를 O(logN)로 낮 추어 성능 을 개선 할 수 있다.
    
    <pre style="margin: 0px; padding: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: &quot;Courier New&quot; !important; font-size: 12px !important;">final V putVal(K key, V value, Boolean onlyIfAbsent) {
    if (key == null || value == null) throw new NullPointerException();
    int hash = spread(key.hashCode());
    int binCount = 0;
    for (Node<K,V>[] tab = table;;) {
    Node<K,V> f;
    int n, i, fh;
    //   table  ,   ;  ,  hash         i,  tab[i]  ,      Node  。 :tab[i]              。
    if (tab == null || (n = tab.length) == 0)
    tab = initTable(); else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
    if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value, null))) break;
    // no lock when adding to empty bin
    }
    //   tab[i]     hash  MOVED,         transfer  ,        table。 else if ((fh = f.hash) == MOVED)
    tab = helpTransfer(tab, f); else {
    V oldVal = null;
    //             ,   segment,         
    synchronized (f) {
    if (tabAt(tab, i) == f) {
    if (fh >= 0) {
    binCount = 1;
    for (Node<K,V> e = f;; ++binCount) {
    K ek;
    //           key   e,    e.val = value  。
    if (e.hash == hash && ((ek = e.key) == key || (ek != null && key.equals(ek)))) {
    oldVal = e.val;
    if (!onlyIfAbsent)
    e.val = value;
    break;
    }
    //         key   ,    Node       。
    Node<K,V> pred = e;
    if ((e = e.next) == null) {
    pred.next = new Node<K,V>(hash, key,
    value, null);
    break;
    }
    }
    }
    //       TreeBin  ,        ,  putTreeVal  。 else if (f instanceof TreeBin) {
    Node<K,V> p;
    binCount = 2;
    if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
    value)) != null) {
    oldVal = p.val;
    if (!onlyIfAbsent)
    p.val = value;
    }
    }
    }
    }
    if (binCount != 0) {
    //      >=8,              。
    if (binCount >= TREEIFY_THRESHOLD)
    treeifyBin(tab, i);
    if (oldVal != null) return oldVal;
    break;
    }
    }
    }
    //     1,     transfer  (  )。
    addCount(1L, binCount);
    return null;
    }
    </pre>
    예시
    
    <pre style="margin: 0px; padding: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: &quot;Courier New&quot; !important; font-size: 12px !important;">public class ConcurrentHashMapDemo { public static void main(String[] args) throws InterruptedException { // HashMap             ConcurrentModificationException    // Map<Integer, Character> map = new HashMap<>();
    Map<Integer, Character> map = new ConcurrentHashMap<>();
    Thread wthread = new Thread(() -> {
    System.out.println("         "); for (int i = 0; i < 26; i++) {
    map.put(i, (char) ('a' + i));
    }
    });
    Thread rthread = new Thread(() -> {
    System.out.println("         "); for (Integer key : map.keySet()) {
    System.out.println(key + " - " + map.get(key));
    }
    });
    wthread.start();
    rthread.start();
    Thread.sleep(1000);
    }
    }</pre>
    CopyOnWriteArrayList
    요점
    역할:CopyOnWrite 는 쓰기 시 복사 한 다 는 뜻 입 니 다.CopyonWriteArrayList 는 라인 이 안전 한 ArrayList 입 니 다.
    원리:
  • 은 CopyonWrite Aarray List 에서 읽 기 동작 이 일치 하지 않 습 니 다.내부 배열 의 스냅 샷 에서 작업 하기 때문에 여러 개의 교체 기 는 서로 막 히 지 않 고 동시에 옮 겨 다 닐 수 있 습 니 다(1,2,4).
  • 의 모든 쓰기 동작 은 동기 화 되 었 다.그들 은 백업 배열(3)의 사본 에서 일한다.쓰기 작업 이 완료 되면 예비 배열 은 복 사 된 배열 로 바 뀌 고 잠 금 을 해제 합 니 다.배열 이 쉽게 변 하 는 것 을 지원 하기 때문에 배열 을 바 꾸 는 호출 은 원자(5)입 니 다.
  • 쓰기 작업 후 만 든 교체 기 는 수 정 된 구조(6,7)를 볼 수 있 습 니 다.
  • 쓰기 시 집합 을 복사 하여 되 돌아 오 는 교체 기 는 Concurrent ModificationException 을 던 지지 않 습 니 다.배열 의 스냅 샷 에서 작업 하고 후속 수정(2,4)이 어떻든 교체 기 가 만 들 때 처럼 요 소 를 완전히 되 돌려 주기 때 문 입 니 다.

  • 소스 코드
    중요 속성
  • lock-쓰기 시 복사 작업 을 수행 하려 면 잠 금 추가
  • 을 사용 해 야 합 니 다.
  • array-대상 배열,요소
  • 저장 에 사용
    
    <pre style="margin: 0px; padding: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: &quot;Courier New&quot; !important; font-size: 12px !important;"> 
    /** The lock protecting all mutators */
    final transient ReentrantLock lock = new ReentrantLock();
    /** The array, accessed only via getArray/setArray. */
    private transient volatile Object[] array;
    </pre>
    중요 한 방법
    추가 작업
    추 가 된 논 리 는 간단 합 니 다.먼저 원 용기 copy 1 부 를 한 다음 새 복사 본 에 쓰기 작업 을 한 다음 인용 을 전환 합 니 다.물론 이 과정 은 자 물 쇠 를 채 워 야 한다.
    
    <pre style="margin: 0px; padding: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: &quot;Courier New&quot; !important; font-size: 12px !important;">public Boolean add(E e) {
    //ReentrantLock  ,      
    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();
    }
    }
    </pre>
    삭제 작업
    삭제 작업 은 같은 이치 입 니 다.요 소 를 삭제 할 것 을 제외 한 다른 요 소 를 새 복사 본 에 복사 한 다음 인용 을 전환 하여 원래 용기 의 인용 을 새 복사 본 으로 가 리 킵 니 다.같은 쓰기 동작 이 므 로 자 물 쇠 를 추가 해 야 합 니 다.
    
    <pre style="margin: 0px; padding: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: &quot;Courier New&quot; !important; font-size: 12px !important;">public E remove(int index) {
    //  
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
    Object[] elements = getArray();
    int len = elements.length;
    E oldValue = get(elements, index);
    int numMoved = len - index - 1;
    if (numMoved == 0) //             ,   len-1        ,     
    setArray(Arrays.copyOf(elements, len - 1)); else {
    //  ,                     ,     
    Object[] newElements = new Object[len - 1];
    System.arraycopy(elements, 0, newElements, 0, index);
    System.arraycopy(elements, index + 1, newElements, index,
    numMoved);
    setArray(newElements);
    }
    return oldValue;
    }
    finally {
    //  
    lock.unlock();
    }
    }
    </pre>
    읽 기 동작
    CopyonWriteArray List 의 읽 기 동작 은 잠 금 을 넣 지 않 아 도 되 고 성능 이 매우 높다.
    
    <pre style="margin: 0px; padding: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: &quot;Courier New&quot; !important; font-size: 12px !important;">public E get(int index) {
    return get(getArray(), index);
    }
    private E get(Object[] a, int index) {
    return (E) a[index];
    }
    </pre>
    예시
    
    <pre style="margin: 0px; padding: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: &quot;Courier New&quot; !important; font-size: 12px !important;">public class CopyOnWriteArrayListDemo { static class ReadTask implements Runnable {
    List<String> list;
    ReadTask(List<String> list) {
    this.list = list;
    }
    public void run() {
    for (String str : list) {
    System.out.println(str);
    }
    }
    }
    static class WriteTask implements Runnable {
    List<String> list;
    int index;
    WriteTask(List<String> list, int index) {
    this.list = list;
    this.index = index;
    }
    public void run() {
    list.remove(index);
    list.add(index, "write_" + index);
    }
    }
    public void run() {
    final int NUM = 10;
    // ArrayList             ConcurrentModificationException    // List<String> list = new ArrayList<>();
    CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
    for (int i = 0; i < NUM; i++) {
    list.add("main_" + i);
    }
    ExecutorService executorService = Executors.newFixedThreadPool(NUM);
    for (int i = 0; i < NUM; i++) {
    executorService.execute(new ReadTask(list));
    executorService.execute(new WriteTask(list, i));
    }
    executorService.shutdown();
    }
    public static void main(String[] args) {
    new CopyOnWriteArrayListDemo().run();
    }
    }
    </pre>
    이상 이 바로 본 고의 모든 내용 입 니 다.여러분 의 학습 에 도움 이 되 고 저 희 를 많이 응원 해 주 셨 으 면 좋 겠 습 니 다.

    좋은 웹페이지 즐겨찾기