동기화된 컬렉션
자바의 기본적인 컬렉션 클래스들은(ArrayList, HashSet, HashMap 등) 싱글 스레드 환경에서 사용할 수 있도록 설계되었습니다. 때문에 이런 컬렉션 클래스들은 멀티 스레드 환경에서 안전하지 않기 때문에, 여러 스레드가 동시에 접근할 때 데이터 불일치나 비정상적인 동작이 발생할 수 있습니다. 이를 방지하기 위해 동기화된 컬렉션을 사용합니다.
Java에서는 Collections 유틸리티 클래스에서 동기화된 컬렉션을 쉽게 생성할 수 있도록 여러 메서드를 제공합니다.
- Collections.synchronizedList()
- Collections.synchronizedSet()
- Collections.synchronizedMap()
// 동기화된 List List<String> syncList = Collections.synchronizedSet(new ArrayList<String>()); // 동기화된 Set Set<String> syncSet = Collections.synchronizedSet(new HashSet<String>()); // 동기화된 Map Map<String, Integer> syncMap = Collections.synchronizedMap(new HashMap<String, Integer>());
동기화된 컬렉션 사용
1. synchronizedList()
// 기본 ArrayList 생성 List<String> list = new ArrayList<>(); // 동기화된 리스트 생성 List<String> syncList = Collections.synchronizedList(list); // 동기화된 리스트에 요소 추가 syncList.add("Apple"); syncList.add("Banana"); syncList.add("Cherry"); // 동기화된 리스트에서 요소를 안전하게 출력 synchronized (syncList) { for (String fruit : syncList) { System.out.println(fruit); } }
2. synchronizedSet
// 기본 HashSet 생성 Set<String> set = new HashSet<>(); // 동기화된 Set 생성 Set<String> syncSet = Collections.synchronizedSet(set); // 동기화된 Set에 요소 추가 syncSet.add("Apple"); syncSet.add("Banana"); syncSet.add("Cherry"); // 동기화된 Set에서 요소 출력 synchronized (syncSet) { for (String fruit : syncSet) { System.out.println(fruit); } }
3. synchronizedMap
// 기본 HashMap 생성 Map<String, Integer> map = new HashMap<>(); // 동기화된 Map 생성 Map<String, Integer> syncMap = Collections.synchronizedMap(map); // 동기화된 Map에 요소 추가 syncMap.put("Apple", 1); syncMap.put("Banana", 2); syncMap.put("Cherry", 3); // 동기화된 Map에서 요소 출력 synchronized (syncMap) { for (Map.Entry<String, Integer> entry : syncMap.entrySet()) { System.out.println("Key: " + entry.getKey() + ", Value: " + entry.getValue()); } }
💁 synchronized 키워드를 이용한 동기화된 컬렉션
동기화된 컬렉션은 synchronized 키워드를 이용해 컬렉션에 대한 접근을 동기화 처리합니다. synchronized 키워드를 메소드나 블록에 적용하면, 해당 코드 영역은 한 번에 하나의 스레드만 실행할 수 있게 됩니다. 이를 통해 모든 읽기 및 쓰기 작업이 동기화된 블록에서 이루어지므로 스레드 충돌을 방지할 수 있습니다.
하지만 synchronized의 사용은 성능 비용이 따릅니다. 여러 스레드가 동시에 접근 할 때 동기화된 컬렉션은 동기화된 블록 안에서만 작업을 수행할 수 있으므로, 성능이 떨어질 수 있습니다.
Concurrent 컬렉션 (java.util.concurrent)⭐
자바 5 이후 도입된 java.util.concurrent 패키지는 동기화된 컬렉션의 성능 문제를 해결하기 위해 설계되었습니다. 이 컬렉션들은 여러 스레드가 동시에 읽기 및 쓰기 작업을 수행할 수 있도록 최적화되어 있으며, 세분화된 락 또는 비동기적 알고리즘(락 없는 알고리즘)을 사용합니다.
Concurrent 컬렉션
멀티 스레드 환경에서 데이터 무결성과 성능을 동시에 보장해야 할 때, Concurrent 컬렉션을 사용합니다. 특히 ConcurrentHashMap은 많은 스레드가 동시에 데이터를 읽고 수정해야 하는 환경에서 성능이 뛰어납니다. 반면 읽기 작업이 주로 이루어지는 경우 CopyOnWriteArrayList와 같은 구조가 더 적합합니다.
- ConcurrentHashMap: 읽기와 쓰기가 빈번하게 일어나는 경우
- CopyOnWriteArrayList: 읽기 작업은 자주 발생하지만 쓰기 작업이 적은 경우
- ConcurrentLinkedQueue: 큐를 사용해 비동기 방식으로 데이터를 주고받아야 할 때
ConcurrentHashMap
ConcurrentHashMap은 해시 테이블 기반의 동시성 맵입니다. 일반적인 동기화 컬렉션과 달리, 부분적 락을 사용해 버킷(bucket) 단위로 락을 걸어, 여러 스레드가 동시에 다른 버킷에 접근할 수 있습니다. 즉, 여러 스레드가 동시에 서로 다른 부분의 데이터를 수정할 수 있어, 동시성이 높은 환경에서도 매우 효율적입니다.
Map<String, Integer> map = new ConcurrentHashMap<>(); map.put("Apple", 1); map.put("Banana", 2); map.put("Orange", 3); // ConcurrentHashMap에서는 동기화 블록을 사용할 필요가 없습니다. for (Map.Entry<String, Integer> entry : map.entrySet()) { System.out.println(entry.getKey() + ": " + entry.getValue()); }
CopyOnWriteArrayList
CopyOnWriteArrayList는 쓰기 작업이 발생할 때 리스트의 전체 복사본을 생성하는 리스트입니다. 이로 인해 쓰기 작업은 리스트 수정할 때마다 새로운 리스트를 생성하기 때문에 메모리 오버헤드 발생 하지만, 읽기 작업이 매우 빈번하게 발생하고 데이터가 거의 변경되지 않는 환경에서는 효율적입니다.
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>(); list.add("Apple"); list.add("Banana"); // 읽기 작업은 매우 빠름 for (String fruit : list) { System.out.println(fruit); } // 쓰기 작업이 발생하면 리스트 전체가 복사됨 list.add("Orange");
ConcurrentLinkedQueue
ConcureentLinkedQueue는 락을 사용하지 않는 비동기적 큐로, 높은 동시성 환경에서도 성능이 좋습니다. 큐에 항목을 추가하거나 제거할 때 락을 걸지 않기 때문에 여러 스레드가 동시에 안전하게 사용할 수 있습니다.
ConcurrentLinkedQueue<String> queue = new ConcurrentLinkedQueue<>(); queue.add("Apple"); queue.add("Banana"); // 요소를 안전하게 제거 System.out.println("Polled: " + queue.poll()); System.out.println("Remaining: " + queue);
동기화된 컬렉션과 Concurrent 컬렉션 비교
특징 | 동기화된 컬렉션 | Concurrent 컬렉션 |
동기화 방법 | synchronized 키워드 사용 | 내부적으로 세분화된 락 또는 락-프리 알고리즘 사용 |
성능 | 높은 동시성에서는 성능 저하 | 동시성 환경에서도 높은 성능 유지 |
락 범위 | 컬렉션 전체에 락을 걸음 | 데이터 구조의 일부에만 락을 걸거나 락 없이 처리 |
적합한 환경 | 낮은 동시성, 적은 스레드 | 높은 동시성, 다수의 스레드 |
메모리 사용 | 일반적인 메모리 사용 | 경우에 따라 메모리 오버헤드 발생 ex. CopyOnWriteArrayList 수정 시 |
'Java' 카테고리의 다른 글
Java Enum 완벽 이해하기: 상수 관리를 위한 도구 (0) | 2024.10.07 |
---|---|
Java 제네릭(Generic) & 와일드카드 완벽 이해하기 (0) | 2024.10.05 |
Java 컬렉션 프레임워크 완벽 이해하기 (0) | 2024.09.26 |
Java 날짜 및 시간 포맷 다루기. SimpleDateFormat, DateTimeFormatter, FastDateFormat (1) | 2024.09.24 |
Java 날짜 시간 다루기: LocalDateTime, Instant, OffsetDateTime, ZonedDateTime의 차이. Duration, Period (0) | 2024.09.23 |