Synchronized Collection vs Concurrent Collection

정미·2022년 7월 27일
0

Computer Science

목록 보기
44/81

공통점

  • 스레드 안정성(Thread-safety) 보장

차이점

  1. 성능(performance)
  2. 확장성(scalability)
  3. 스레드 안정성을 확보하는 방법(how they achieve thread-safety)

Synchronized Collection

synchronize

한 번에 하나의 스레드만 객체에 접근하도록 허용한다.

synchronized object는 여러 스레드에 의해 동시에 조작될 수 없다.

대표 컬렉션

  • Vector
  • Hashtable
  • synchronized HashMap, HashSet, ArrayList, ..

특징

1. 성능

  • Concurrent Collection보다 성능이 낮다.
  • Collection 전체 객체(Map이나 List)에 락을 건다.

2. 스레드 안정성 확보 방법

  • public 메서드에 synchronized 키워드를 사용하여 내부의 값을 한 스레드만 사용할 수 있도록 제어한다.
  • HashTable
    • 해시테이블의 사이즈와 상관없이 iteration이나 쓰기 연산 때 전체 map에 락을 건다.

3. 확장성

  • 컬렉션 전반에 거는 하나의 락은 확장성에 장애물이 된다.
  • ConcurrentModificationException을 피하기 위한 스레드 대기 시간이 길어진다.

종류

Vector

package java.util;

public class Vector<E> extends AbstractList<E>
    implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
    ...

    public synchronized boolean add(E e) {
        modCount++;
        add(e, elementData, elementCount);
        return true;
    }

		public synchronized int size() {
        return elementCount;
    }

    ...
}

Hashtable

package java.util;

public class Hashtable<K,V>
    extends Dictionary<K,V>
    implements Map<K,V>, Cloneable, java.io.Serializable {
		...

		@SuppressWarnings("unchecked")
    public synchronized V get(Object key) {
        Entry<?,?> tab[] = table;
        int hash = key.hashCode();
        int index = (hash & 0x7FFFFFFF) % tab.length;
        for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) {
            if ((e.hash == hash) && e.key.equals(key)) {
                return (V)e.value;
            }
        }
        return null;
    }

		public synchronized int size() {
        return count;
    }

		...
}

Collections.synchronizedXXX()

package java.util;

public class Collections {
	...

	public static <T> List<T> synchronizedList(List<T> list) {
      return (list instanceof RandomAccess ?
              new SynchronizedRandomAccessList<>(list) :
              new SynchronizedList<>(list));
  }

	public static <K,V> Map<K,V> synchronizedMap(Map<K,V> m) {
      return new SynchronizedMap<>(m);
  }

	public static <T> Set<T> synchronizedSet(Set<T> s) {
      return new SynchronizedSet<>(s);
  }

	static class SynchronizedList<E>
      extends SynchronizedCollection<E>
      implements List<E> {

      @SuppressWarnings("serial") // Conditionally serializable
      final List<E> list;

      public E get(int index) {
          synchronized (mutex) {return list.get(index);}
      }

      public void add(int index, E element) {
          synchronized (mutex) {list.add(index, element);}
      }

      public E remove(int index) {
          synchronized (mutex) {return list.remove(index);}
      }

      ...
  }

	...
}
List<String> list = Collections.synchronizedList(new ArrayList<String>());
  • SynchronizedList 내부 클래스를 보면 모든 메서드의 각 synchronized 블록에서 mutex 객체를 공유한다. 한 스레드가 synchronized 블록에 들어가는 순간 다른 메서드의 syncrhonized 블록이 lock에 걸리기 때문에 다른 스레드들은 모두 동기화 메서드를 이용하지 못하고 blocking 상태가 된다.
  • 읽기와 쓰기 동작 시 모두 인스턴스 자체에 잠금이 걸린다.

Concurrent Collection

concurrent

  • 여러 스레드가 한 객체에 동시에 접근할 수 있다.

대표 컬렉션

  • ConcurrentHashMap, CopyOnWriteArrayList, CopyOnWriteHashSet, ..

특징

1. 성능

  • Synchronized Collection보다 높은 성능
  • 절대 전체 Map이나 List에 lock을 걸지 않는다.
  • 여러 스레드가 한 번에 접근 가능하기 때문에 스레드 대기 시간을 줄여준다.
  • 하나 이상의 스레드가 concurrent하게 read, write 연산을 할 수 있다.

2. 스레드 안정성 확보 방법

  • Lock Stripping 같은 정교한 기법
  • ConcurrentHashMap
    1. 전체 Map을 여러 조각으로 나눈다.
    2. 관련된 부분들에만 락을 건다.
    3. 해당 컬렉션의 락이 걸리지 않은 다른 조각들에 여러 스레드들이 접근할 수 있다.
  • CopyOnWriteArrayList
    1. read: synchronization 없이 여러 읽기 스레드들이 읽을 수 있다.
    2. write: 전체 ArrayList를 복사하고 더 최신 컬렉션과 바꾼다.

3. 확장성

  • 읽기 연산이 쓰기 연산보다 많이 일어나는 등의 더 필요한 상황에 맞게 concurrent collection 클래스를 골라 사용할 수 있다.
  • 더 높은 동시성(concurrency)과 확장성

종류

CopyOnWriteArrayList

  • 모든 쓰기 동작 시 원본 배열의 요소를 복사하여 새로운 임시 배열을 만들고, 임시 배열에 쓰기 동작을 수행한 후 원본 배열을 갱신한다.
    • 명시적 락 사용
  • 읽기 동작은 lock이 걸리지 않는다.
  • 비용이 높은 배열 복사 작업이 있기 때문에 쓰기 작업이 많은 경우 성능 이슈가 발생할 수 있다.
package java.util.concurrent;

public class CopyOnWriteArrayList<E>
    implements List<E>, RandomAccess, Cloneable, java.io.Serializable {

    final transient Object lock = new Object();

    private transient volatile Object[] array;

    public boolean add(E e) {
        synchronized (lock) {
            Object[] es = getArray();
            int len = es.length;
            es = Arrays.copyOf(es, len + 1);
            es[len] = e;
            setArray(es);
            return true;
        }
    }

    public E get(int index) {
        return elementAt(getArray(), index);
    }
	
    ...
}

ConcurrentHashMap

  1. 자바 8 전
    • ReentrantLock을 상속받는 Segment를 이용하여 영역을 구분하고 영역별로 잠금
  2. 자바 8 이후
    • 각 테이블 버킷을 독립적으로 잠금
    • 빈 버킷에 노드를 삽입할 경우 lock 대신 CAS 알고리즘
    • 그 외 변경은 각 버킷의 첫번째 노드를 기준으로 부분적인 잠금(synchronized block)을 획득
      • 스레드 경합 최소화

출처

0개의 댓글