티스토리 뷰

JVM/JAVA

[JAVA] ArrayList thread safe하지 않다는 뜻이 뭘까?

글을 쓰는 개발자 2022. 1. 8. 08:42
반응형

우리는 항상 ArrayList가 thread safe하지 않다는 것을 들어봤을 것이다.

저도 항상 인지만 했지 왜 thread safe 하지 않다는 것이지? 우선 synchroinzed 제어자를 사용하면서 해당 메소드 또는 크래스 단위에서 락을 걸어서 멀티 스레드에 대해서 안전성을 보장하는 것은 대표적으로 Vector 가 있다. 하지만 ArrayList의 경우에는 이러한 제어자가 없다.

 

그러면 ArrayList에서 thread에 대한 체크를 하지 않는다는 것인가?
체크를 한다면 어떻게 체크를 하지?
그러면 에러가 날까?

 

이렇게 생각을 하다 ArrayList에 대해서 자세히 볼려고 한다.

 

우선 공통적으로 Collection 프레임 워크에서 modCount라는 것을 통해 값이 추가되거나 삭제 될 때 modCount의 값을 추가한다.

 

이 때 eqauls 메소드에서 modCount 값이 차이가 나면  ConcurrentModificationException() 에러를 발생한다. ( 그 이외에도 많은 곳에서 체크를 한다.)

 

    public boolean equals(Object o) {
        if (o == this) {
            return true;
        }

        if (!(o instanceof List)) {
            return false;
        }

        final int expectedModCount = modCount;
        // ArrayList can be subclassed and given arbitrary behavior, but we can
        // still deal with the common case where o is ArrayList precisely
        boolean equal = (o.getClass() == ArrayList.class)
            ? equalsArrayList((ArrayList<?>) o)
            : equalsRange((List<?>) o, 0, size);

        checkForComodification(expectedModCount);
        return equal;
    }
    
        private void checkForComodification(final int expectedModCount) {
        if (modCount != expectedModCount) {
            throw new ConcurrentModificationException();
        }
    }

 


1. Runner.java

import java.util.ArrayList;
import java.util.Random;

public class Runner implements Runnable {
    private static final Random random = new Random();
    public static ArrayList<Integer> store = new ArrayList<>();
    private ArrayList<Integer> list;
    public Runner(ArrayList<Integer> list) {
        this.list = list;
    }
    @Override
    public void run() {
        for (int i = 0; i < 1_000_000; i++) {
            int value = random.nextInt(100);
            list.add(value);
            store.add(value);
            store.equals(list);
        }
    }
}

제가 테스트하고자 하는 포인트는 list하고 store하고 같이 저장하면서 마지막에 equals로 비교하는 것이다.

이 때 store는 다량의 스레드에서 들어온 것도 받기 위해 static으로 처리하였고, list는 변수로 받아서 처리하도록 했다.

 

2. TEST

public class ArrayListTest {

    @Test
    void arrayList테스트() throws InterruptedException {
        //given
        ArrayList<Integer> list = new ArrayList<>();
        Runner runner = new Runner(list);
        Runner runner2= new Runner(list);
        Thread first = new Thread(runner);
        Thread second = new Thread(runner2);
        //when
        first.start();
        second.start();
        Thread.sleep(2000);
        System.out.println(Runner.store.size());
        //then
    }
}

다음과 같이 checkForComodification 메소드에서 기대되는 modCount하고 실제 modCount의 차이로 에러가 나는 모습을 볼 수 있다.

 

그러면 Vector로 했을 때에는 어떨까?

 

확인해보자

 


1. VectorRunner.java

import java.util.Random;
import java.util.Vector;

public class VectorRunner implements Runnable {
    private static final Random random = new Random();
    public static Vector<Integer> store = new Vector<>();
    private Vector<Integer> list;
    public VectorRunner(Vector<Integer> list) {
        this.list = list;
    }
    @Override
    public void run() {
        for (int i = 0; i < 1_000_000; i++) {
            int value = random.nextInt(100);
            list.add(value);
            store.add(value);
            store.equals(list);
        }
    }
}

 

2. TEST

    @Test
    void vector테스트() throws InterruptedException {
        //given
        Vector<Integer> list = new Vector<>();
        VectorRunner runner = new VectorRunner(list);
        VectorRunner runner2= new VectorRunner(list);
        Thread first = new Thread(runner);
        Thread second = new Thread(runner2);
        //when
        first.start();
        second.start();
        Thread.sleep(2000);
        System.out.println(Runner.store.size());
        //then
    }

물론 Vector에서도 똑같이 에러가 난다.

 

근데 차이점이 있다면 무엇이 있을까?

 

그렇다. 바로 store의 크기가 다르다!!

 

항상 ArrayList가 좀 더 빠르다 라는 것은 알았지 이런 면에서 더 빠르다는 것은 인지하지 못했었는데 이번 기회를 통해 좀 더 알게 된 것 같다.

 

그렇다고 ArrayList가 무조건 사용해야한다고 그런 말은 아니다. 예를 들어 여러 스레드가 동시에 접근 했을 때 하나의 스레드에서 리스트에 대한 처리를 하는 동안 크기에 대해서 validation하는데 다른 스레드가 값을 증가시키거나 감소시키면 안되기 때문이다.

 


추가사항

 

우선 ArrayList와 Vector 두 부분에서 에러나는 부분이 다르다.

 

ArrayList의 경우에는 equals 안에 있는 checkForComodification 메서드 부분이 에러가 났고,

Vector의 경우에는 AbstractList에 있는 equals에서 next()하는 부분에서 에러가 났는데 Vector 안에 Itr라는 iterator를 상속해서 구현한 클래스에서 next 하는 부분에 checkForComodification이 있는데 이 부분에서 에러가 난 것이다.

    private class Itr implements Iterator<E> {
        int cursor;       // index of next element to return
        int lastRet = -1; // index of last element returned; -1 if no such
        int expectedModCount = modCount;

        public boolean hasNext() {
            // Racy but within spec, since modifications are checked
            // within or after synchronization in next/previous
            return cursor != elementCount;
        }

        public E next() {
            synchronized (Vector.this) {
                checkForComodification();
                int i = cursor;
                if (i >= elementCount)
                    throw new NoSuchElementException();
                cursor = i + 1;
                return elementData(lastRet = i);
            }
        }
   }

 

싱글톤 + 멀티쓰레드에 대해 알고 싶으시면 아래 링크를 참고해주시면 될 것 같습니다.

읽어주셔서 감사합니다.

https://vixxcode.tistory.com/225

 

[JAVA] Singleton + multiThread 를 취할 때 고려해야 할 것

싱글톤 인스턴스에서 가장 조심해야 할 것은 자원을 공유하는 것이다. 우선 싱글톤에 대해서 모르시면 이 글을 참고해주시길 바랍니다. https://vixxcode.tistory.com/190 [Pattern][Java][Python] Singleton 생성..

vixxcode.tistory.com

 

 

 

반응형
반응형
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함