Stack 사용을 왜 지양해야 할까?
Stack을 지양해야 한다는 말을 들었으나, 그 이유를 정확히 몰라서 학습하고자 이 글을 작성하였다.
들어가기 전 스택의 구조는 모두 알고 있겠지만 한 번 더 보자!
스택
스택의 가장 큰 특징은 LIFO 후입 선출구조로 가장 최근(마지막)에 들어간 값이 제일 먼저 나온다.
Stack을 지양해야 하는 이유로는 총 4가지로 구성해 보았다.
스택을 왜 지양해야 할까?
1. Stack 클래스는 Vector 클래스를 상속한다
Stack은 상위 클래스로 Vector를 상속받는다.
Vector는 동적 배열로 설게되었으며, 특정 인덱스를 기반으로 삽입, 삭제하는 기능을 지원한다. 하지만 스택은 후입선출 구조로 요소를 추가, 삭제하 할 때 마지막 요소를 기준을 동작한다.
따라서 Vector의 get(), set() 메서드, insertElementAt()와 같은 메서드는 스택의 목적에 맞지 않고, 불필요한 복잡성을 초래해 동작에 혼란을 줄 수 있다.
2. 메모리 누수
Vector는 동적 배열로 크기를 자동으로 조절하는데, 따라서 배열이 꽉 찰 경우 크기를 자동으로 두 배로 확장한다.
이 과정에서 기존 배열의 요소를 새로운 배열로 복사하기 때문에 추가적인 메모리와 시간이 걸린다. 이 필요하다. Java는 GC가 있어 메모리 누수의 가능성은 낮지만, Vector에 사용하지 않는 객체 참조가 남아 있는 경우 메모리가 해제되지 않아 메모리 낭비가 발생할 수 있다. 특히 요소를 삭제할 때 참조를 명시적으로 제거하지 않는 경우에 문제가 될 수 있다.
3. 동기화 오버헤드
Vector는 ArrayList와 같이 List 인터페이스를 구현한 컬렉션 프레임워크로
데이터를 저장하는 방식, 동적 크기 조정, 삽입 및 삭제 기능에서 ArrayList와 비슷하지만, thread-safe하다는 부분에서 차이가 있다.
Vector는 모든 메서드에 synchronized를 사용한다.
Vector는 모든 메서드에 synchronized 키워드를 사용하여 동기화를 처리하는데, 이 방식은 단일 스레드 환경에서 불필요한 동기화로 인해 성능 저하를 야기한다.
그러면 멀티 스레드 환경에서는 효율적이지 않을까?
멀티스레드 환경에서는 보통 특정 작업 단위로 동기화를 묶어서 처리하는데, Vector는 메서드 단위로 동기화된다. 따라서 온전한 동기화 처리를 제공하지 못한다.
예를 들어, 여러 메서드 호출(add와 get)을 하나의 작업으로 묶어야 안전하게 동작하는 경우가 많다. 하지만 Vector는 메서드 단위로만 동기화를 제공하기 때문에, 이런 복합 작업을 수행할 때 경쟁 상태(Race Condition)가 발생할 수 있다.
결과적으로, Vector는 성능과 안정성 면에서 모두 비효율적이다. 따라서, 멀티스레드 환경에서는 Vector 대신 ConcurrentHashMap, CopyOnWriteArrayList 등이 좀 더 효율적이고 안전하다.
4. 대체재
위의 문서에서 보듯이 자바에서는 Stack 대신 Deque 인터페이스를 사용할 것을 권장한다. Deque(Double-Ended Queue) 인터페이스는 양방향 큐로 스택(LIFO)과 큐의 기능(FIFO)을 모두 지원하며, 구현제로 ArrayDeque나 LinkedList가 있다.
Deque는 더 나은 성능을 제공하며, 멀티스레딩 환경에서 Stack보다 더 효율적이다. 현재 Stack은 레거시 코드와의 호환성을 위해 유지되고 있는 것 같다.
만약 멀티스레드 상황에서 사용해야 한다면 ConcurrentLinkedDeque를 사용하자~!
그러면 왜 Vector를 상속한 거지?
Stack이 Vector를 상속한 이유는 자바의 초기 설계 방식에 있다. 자바가 처음 설계될 때, 스택을 배열 기반으로 구현하려 했고, 그 시점에서 Vector는 동적 배열을 지원하는 대표적인 자료구조 였다.
Vector는 동적 크기 확장 기능을 제공해 배열의 고정 크기 한계를 극복하였고, 스택의 LIFO 동작을 구현하기에도 적합했다.
또한, Vector는 List 인터페이스를 구현하여, 요소 관리에 필요한 기본적인 기능을 제공할 수 있다. 이런 여러 이유로 자바 초기에 Stack이 Vector를 상속하는 방식으로 구현된 것이다.
자바의 컬렉션 프레임워크가 발전하면서 Stack을 대신할 Deque와 같은 더 나은 대체제가 등장하였다. Deque는 스택과 큐의 기능을 모두 제공하면서 성능과 효율성 면에서 Stack보다 더 좋다.
Stack은 SOLID 원칙 중 리스코프 치환 원칙에 위배하는 사례이다.