JAVA

[JAVA] List와 동기화

인생아 2024. 11. 18. 09:58
반응형

Java의 List는 기본적으로 동기화되지 않아 다중 스레드 환경에서 예기치 않은 문제가 발생할 수 있습니다. 여기서는 동기화가 필요한 상황, Collections.synchronizedList(), 동기화된 List의 장단점을 상세히 다루며, 적절한 Java 동기화 처리List 동기화 활용 예제를 제공합니다.

1. 동기화가 필요한 상황

다중 스레드 환경에서는 여러 스레드가 동시에 동일한 List 객체에 접근하거나 수정하면 데이터 불일치예외가 발생할 수 있습니다. 대표적인 상황은 아래와 같습니다.

  • 데이터 추가와 삭제 작업이 동시에 이루어질 때
  • 반복문으로 데이터를 처리하는 동안 다른 스레드가 수정하는 경우
  • 다중 스레드에서 비동기적으로 데이터를 읽거나 쓰는 경우

문제가 발생하는 예제

import java.util.ArrayList;

public class Main {
    public static void main(String[] args) {
        ArrayList<String> names = new ArrayList<>();
        names.add("이민수");
        names.add("김철수");

        Runnable task = () -> {
            for (String name : names) {
                System.out.println(name);
                names.add("박영희"); // ConcurrentModificationException 발생 가능
            }
        };

        Thread thread1 = new Thread(task);
        Thread thread2 = new Thread(task);

        thread1.start();
        thread2.start();
    }
}

위 코드는 ConcurrentModificationException을 유발할 수 있습니다. 이를 방지하기 위해 동기화가 필요합니다.

반응형

2. Collections.synchronizedList()

Java는 Collections.synchronizedList() 메서드를 제공하여 동기화된 List를 생성합니다. 이를 사용하면 스레드 간 데이터 일관성을 보장할 수 있습니다.

기본 사용법

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<String> names = Collections.synchronizedList(new ArrayList<>());
        names.add("서울");
        names.add("부산");

        // 동기화된 List 사용
        synchronized (names) { // 동기화 블록으로 안전하게 처리
            for (String name : names) {
                System.out.println("도시: " + name);
            }
        }
    }
}

출력 결과

도시: 서울
도시: 부산

중요한 점: 반복문에서 동기화 블록을 사용해야 예외 발생을 방지할 수 있습니다.

다중 스레드 환경 예제

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<String> names = Collections.synchronizedList(new ArrayList<>());

        Runnable task1 = () -> {
            synchronized (names) {
                names.add("고양이");
                names.add("강아지");
                System.out.println("스레드1 추가 완료");
            }
        };

        Runnable task2 = () -> {
            synchronized (names) {
                names.add("토끼");
                System.out.println("스레드2 추가 완료");
            }
        };

        Thread thread1 = new Thread(task1);
        Thread thread2 = new Thread(task2);

        thread1.start();
        thread2.start();
    }
}

출력 결과

 
스레드1 추가 완료
스레드2 추가 완료

Collections.synchronizedList()의 장점

  1. 다중 스레드 환경에서 데이터 안정성을 보장
  2. Java 표준 라이브러리를 사용하므로 구현이 간편

3. 동기화된 List의 장단점

장점

  1. 데이터 안정성 보장: 스레드 간 동기화를 제공하여 데이터 손상을 방지
  2. 간단한 구현: Collections.synchronizedList()를 사용하면 별도의 동기화 로직 없이 동기화를 적용 가능
  3. Java 표준 기능 사용: 추가 라이브러리나 외부 의존성 없이 사용 가능

단점

  1. 성능 저하: 모든 작업이 동기화 블록 안에서 실행되므로 성능이 떨어질 수 있음
  2. 복잡성 증가: 동기화 블록 사용을 신경 써야 하며, 실수로 동기화를 누락할 경우 문제가 발생 가능
  3. 대안보다 덜 유연함: ConcurrentHashMap, CopyOnWriteArrayList와 같은 대체 동기화 구조체들이 더 유연하고 성능이 우수한 경우가 있음

CopyOnWriteArrayList 대안

import java.util.concurrent.CopyOnWriteArrayList;

public class Main {
    public static void main(String[] args) {
        CopyOnWriteArrayList<String> names = new CopyOnWriteArrayList<>();
        names.add("이민수");
        names.add("김철수");

        Runnable task = () -> {
            for (String name : names) {
                System.out.println(name);
                names.add("박영희"); // 예외 발생하지 않음
            }
        };

        Thread thread1 = new Thread(task);
        Thread thread2 = new Thread(task);

        thread1.start();
        thread2.start();
    }
}

출력 결과

이민수
김철수
박영희
...

CopyOnWriteArrayList는 읽기 작업이 많은 경우 더 좋은 성능을 제공합니다.

참고 사이트

Java 공식 문서: https://docs.oracle.com/javase/8/docs/api/java/util/Collections.html

반응형