[java] Collection 인스턴스 동기화

2022. 4. 7. 15:32[ 백엔드 개발 ]/[ Java,Kotlin ]

Collection 인스턴스 동기화

- 2개 이상의 쓰레드가 컬렉션에 동시에 접근한다는 가정이 필요할 땐 동기화를 해주어야 한다.

- 일반적인 대부분의 컬렉션들(ArrayList, HashMap 등)은 동기화가 되어있지 않다.

 

 

컬렉션 동기화 메소드

1) public static <T> Set<T> synchronizedSet(Set<T> s)

2) public static <T> List synchronizedList(List<T> list)

3) public static <K, V> Map<K, V> synchronizedMap(Map<K, V> m)

4) public static <T> Collection<T> synchronizedCollection(Collection<T> c)

 

- 위 메소드에서 동기화가 되어 있지 않은 컬렉션을 인자로 주면 동기화 기능이 추가된 컬렉션으로 반환한다.

 

cf. 사용법 예시

List<String> list = Collections.synchronizedList(new ArrayList<String>());

 

 

예제코드1

아래 예제코드는 리스트에 0~15 값을 저장하고 각각의 값들을 3씩 더한 값으로 변경하는 코드이다.

class Solution {
    public static List<Integer> list = Collections.synchronizedList(new ArrayList<>());

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 16; i++)
            list.add(i);

        System.out.println(list);

        Runnable task = () -> {
            ListIterator<Integer> itr = list.listIterator();
            while (itr.hasNext())
                itr.set(itr.next() + 1);
        };

        ExecutorService exsvc = Executors.newFixedThreadPool(3);
        exsvc.submit(task);
        exsvc.submit(task);
        exsvc.submit(task);

        exsvc.shutdown();
        exsvc.awaitTermination(100, TimeUnit.SECONDS);

        System.out.println(list);
    }
}

 

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
[2, 3, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 15, 16, 17]

 

위 결과를 보면 각각의 자리 수가 3씩 증가되기를 기대했으나 정상적으로 실행되지 않았다.

 

cf. awaitTermination(100, TimeUnit.SECONDS)

- 작업이 끝나기를 최대 100초 동안 기다리겠다는 설정이다.

 

 

문제 원인

 

 

synchronized list를 사용하면 list라는 필드에 대한 '접근'을 동기화한다. 마찬가지로 Runnable에서 ListIterator라는 반복자 참조변수로 리스트에 접근하고 있다. 결국 list라는 필드를 통해 접근하는 것이 아니라, 반복자 참조변수인 itr을 기반으로 리스트에 접근하고 있으니 동기화가 제대로 수행되지 않은 것이다.

 

 

반복자 참조변수 동기화

예제코드2

class Solution {
    public static List<Integer> list = Collections.synchronizedList(new ArrayList<>());

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 16; i++)
            list.add(i);

        System.out.println(list);

        Runnable task = () -> {
            synchronized (list) {
                ListIterator<Integer> itr = list.listIterator();
                while (itr.hasNext())
                    itr.set(itr.next() + 1);
            }
        };

        ExecutorService exsvc = Executors.newFixedThreadPool(3);
        exsvc.submit(task);
        exsvc.submit(task);
        exsvc.submit(task);

        exsvc.shutdown();
        exsvc.awaitTermination(100, TimeUnit.SECONDS);

        System.out.println(list);
    }
}

 

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
[3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18]

 

- 위와 같이 Runnable 구현부에서 synchronized block을 통해 반복자를 통해 리스트에 접근하는 부분을 동기화시켰다.

- synchronized (list)는 list 참조변수를 대상으로 동기화 범위를 지정한 것이다.