Java/Java 를 파헤쳐보자

[Java 파헤쳐보기] Vector vs ArrayList

동구름이 2024. 4. 11. 14:51

 

 이전 포스팅에서  Vector, Hashtable과 같이 포함되지 않는 클래스들이 있는데, 이것은 컬렉션 프레임 워크 이전에 만들어진 클래스들로 호환을 위해 남겨진 것을 언급했습니다.

 

 이번 포스팅에서는 레거시 클래스인 Vector를 간단히 살펴보고 ArrayList와 비교한 것을 정리했습니다.

 

레거시 클래스: Vector

 

Vector는 JDK 1.0부터 있었던 자료구조로 호환성을 위해 남겨진 클래스입니다. 

 

Vector  vs ArrayList

 Vector 자체는 ArrayList와 기능이 거의 동일하지만, 한 가지 다른 것은 ArrayList는 비동기 방식이고 Vector는 동기 방식이라는 것에 있습니다.

 실제 벡터의 메서드 내부를 들여다보면, synchroized가 선언되어있는 것을 볼 수 있습니다. 그래서 두 개 이상의 스레드가 하나의 메서드에 동시에 접근할 때, Race Condition이 발생하는 것을 막습니다.

 

 반면 ArrayList같은 경우는 synchroized가 선언되어있지 않습니다. 그래서 멀티 스레드 환경에서 한 메서드에 여러 개의 스레드가 접근할 수 있습니다.

 

 

Vector의 단점

  동기 방식이 스레드 안전을 위해서 사용되는 것을 생각하면, Vector가 ArrayList보다 안정적인 클래스 같지만 그렇지 않습니다.  Vector는 대부분의 메서드에 동기화 처리가 되어있긴하지만, 반대로 메서드마다 동기화가 되어있기 때문에 동기화가 완벽하지 못하고 성능이 느리기 때문입니다. 

 

 1. 완벽하지 않은 동기화

Vector는 메서드마다 동기화가 되어있지만, 정작 인스턴스 자체에는 동기화 처리가 되어있지 않다는 것에서 스레드 안전하지 못합니다. 

 

 이것은 아래 처럼 코드를 이용해 확인해볼 수 있습니다.

 

  아래 코드는 첫 번째 스레드에서는 Vector에 값을 add하고 두번째 스레드에서는 Vector에서 값을 remove하며 동작하는 코드입니다.

더보기

코드

public class test {
        public static void main(String[] args) {
            Vector<Integer> vec = new Vector<>();

            new Thread(() -> {
                vec.add(1);
                vec.add(2);
                System.out.println(vec.get(0));
                System.out.println(vec.get(1));
            }).start();

            new Thread(() -> {
                vec.remove(0);
                vec.remove(0);
            }).start();


            new Thread(() -> {
                try {
                    Thread.sleep(1000); // 1초 대기
                    System.out.println("Vector size : " + vec.size());
                } catch (InterruptedException ignored) {
                }
            }).start();
        }
}

 

결과는 ArrayIndexOutofBoundsException 예외가 발생하게 됩니다. 

 

이유는 다음과 같습니다.

 

 첫 번째 스레드에서 요소를 추가하고 get를 통해 요소를 가져오려했지만, 두 번째 스레드에서는 remove를 통해 내부 배열이 줄어, 범위가 벗어난 인덱스를 참조했기 때문입니다. 즉, 인스턴스 자체에는 동기화가 되어있지 않기 때문에 여러 스레드에서 객체에 접근할 수 있게 되어 객체에 대한 race condition 현상이 생기게 되는 것입니다.

 

 이런 현상이 생기지 않게 하기 위해서는 vector 객체에 synchronized를 걸어주어야합니다.

 

 

2. 동기화로 인한 성능 저하

 동기화를 한다는 것은 당연하게도 오버헤드를 소요하는 일입니다. 동시에 처리할 수 있는 일들도 순서를 정해 처리함으로써 속도가 느려지는 문제가 발생하게 됩니다.

 

 

 

 이처럼 Vector는 ArrayList에 비해 항상 동기화를 하기 때문에 성능도 떨어지는데다가, 동기화 처리도 제대로 이루어지지 않기 때문에, 사용하지 않는 것이 좋습니다. 

 

 

그렇다면, 멀티 스레드 환경에서 ArrayList를 어떻게 사용해야할지 고민해볼 수 있습니다.

 

ArrayList 동기화 처리

멀티 스레드 환경에서 동기화 처리를 위해 자바에서는 Collections.synchronizedList() 메서드를 제공합니다.

 

이를 통해 ArrayList 뿐 아니라, LinkedList도 특성을 유지한 채로 동기화 처리가 이루어진 컬렉션을 생성해줍니다.

 

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

List<String> list2 = Collections.synchronizedList(new LinkedList<>());

 

 

 

 

 

참고자료

https://inpa.tistory.com/entry/JCF-🧱-ArrayList-vs-Vector-동기화-차이-이해하기 [Inpa Dev 👨‍💻:티스토리]

https://okky.kr/questions/442436

https://stackoverflow.com/questions/14932034/in-java-vector-and-collections-synchronizedlist-are-all-synchronized-whats-th