byunyourim / TIL

오늘 공부한 CS 개념을 정리하는 공간입니다.
0 stars 0 forks source link

Stack의 사용을 지양하는 이유 #2

Closed byunyourim closed 6 days ago

byunyourim commented 1 week ago

이슈: Stack 사용을 왜 지양해야 할까?

배경:

등록일: 2024.11.18

참고:


문제점:


해결 방안:

추가 정보:


문서화 결과

추진 일정:

byunyourim commented 1 week ago

스택의 구조?

image 스택의 가장 큰 특징은 LIFO 후입 선출구조로 가장 최근(마지막)에 들어간 값이 제일 먼저 나온다.




스택은 왜 사용되지 않을까?

1. Stack 클래스는 Vector 클래스를 상속한다

image

Stack은 상위 클래스로 Vector를 상속받는다.

Vector는 ArrayList와 같이 List 인터페이스를 구현한 컬렉션 프레임워크로 thread-safe하다.

Vector는 데이터를 저장하는 방식이나 동적 크기 조정, 삽입 및 삭제 기능에서 ArrayList와 비슷하지만, thread-safe하다는 부분에서 차이가 있다.

Vector는 요소를 임의로 접근하거나 수정할 수 있는 get(), set() 메서드, insertElementAt()와 같은 메서드를 제공하는데, 이로인해 인덱스를 사용해 중간 값에 접근하거나 조작할 수 있다. 이러한 메서드는 스택의 특성과 맞지 않는다.

이러한 Vector의 추가적인 메서드는 스택의 목적에 맞지 않고, 복잡성을 초래하여, 스택의 동작에 혼란을 줄 수 있다.


2. 메모리 누수

Vector는 동적 배열을 사용하여 요소를 저장한다. 요소를 추가할 때마다 배열의 크기를 자동으로 늘리지만, 이 과정에서 불필요하게 많은 메모리를 할당할 수 있다. 예를 들어, Vector는 배열의 크기가 꽉 차면 배열을 두 배로 늘리는 방식으로 크기를 확장하는데, 이로인해 메모리 낭비를 초래할 수 있다. 스택 자료 구조의 특성상, 이렇게 불필요한 메모리 할당을 최소화하고, 더 효율적인 메모리 관리 방식이 필요하다.

Vector의 메모리 관리 방식은 스택의 요구에 적합하지 않으며, 장기적으로 메모리 누수를 일으킬 수 있다.

byunyourim commented 1 week ago

2. 동기화 오버헤드

Vector는 모든 메서드에 synchronized를 사용한다.

image

Vector는 모든 메서드에 synchronized 키워드를 사용하여 동기화를 처리하는데, 이 방식은 멀티스레드 환경에서 효율적이지 않으며, 단일 스레드 환경에서는 불필요한 동기화로 인해 성능 저하를 야기한다.

멀티스레드 환경에서는 보통 특정 작업 단위로 동기화를 묶어서 처리해야 하지만, Vector는 메서드 단위로 동기화되어 있어, 온전한 동기화 처리를 제공하지 못한다.

결과적으로, Vector는 성능과 안정성 면에서 모두 비효율적이기 때문에, 이를 사용해야 할 이유가 없다. 멀티스레드 환경에서는 Vector 대신 ConcurrentHashMap, CopyOnWriteArrayList 등 더 효율적이고 안전한 대안이 존재한다.

왜 멀티스레딩에서 환경에서 동기화가 효율적이지 않지?

동기화 단위가 비효율적이기 때문이다.

Vector는 메서드 단위로 동기화를 적용하는데, 멀티스레드 환경에서는 일반적으로 작업 단위를 묶어서 동기화하는 것이 더 효율적이다.

예를 들어, 여러 메서드 호출(add와 get)을 하나의 작업으로 묶어야 안전하게 동작하는 경우가 많다. 하지만 Vector는 메서드 단위로만 동기화를 제공하기 때문에, 이런 복합 작업을 수행할 때 여전히 경쟁 상태(Race Condition)가 발생할 수 있다.

byunyourim commented 1 week ago

3. 대체재

image

위의 문서에서 보듯이 자바에서는 Stack을 대신 Deque 관련 컬렉션을 사용할 것을 추천한다. Deque(Double-Ended Queue) 인터페이스는 양방향 큐를 지원하며, 스택(LIFO)과 큐의 기능(FIFO)을 모두 지원하며, 구현제로 ArrayDeque나 LinkedList가 있다.

Deque는 더 나은 성능을 제공하며, 멀티스레딩 환경에서 Stack보다 더 효율적이다.

만약 멀티스레드 상황에서 사용해야 한다면 ConcurrentLinkedDeque를 사용하자~!

byunyourim commented 6 days ago

그러면 왜 Vector를 상속한 건지?

Stack이 Vector를 상속한 이유는 자바의 초기 설계 방식에 있다. 자바가 처음 설계될 때, 스택을 배열 기반으로 구현하려 했고, 그 시점에서 Vector는 동적 배열을 지원하는 대표적인 자료 구조 였다.

Vector는 동적 크기 확장 기능을 제공해 배열의 고정 크기 한계를 극복하였고, 스택의 LIFO 동작을 구현하기에도 적합했다.

또한, Vector는 List 인터페이스를 구현하여, 요소 관리에 필요한 기본적인 기능을 제공할 수 있다. 이러한 이유로 자바 초기 설계에서 Stack이 Vector를 상속하는 방식으로 구현되었다.

자바의 컬렉션 프레임워크가 발전하면서 Stack을 대신할 Deque와 같은 더 나은 대체제가 등장하였다. Deque는 스택과 큐의 기능을 모두 제공하면서 성능과 효율성 면에서 Stack보다 더 좋다.

image

Stack은 SOLID 원칙 중 리스코프 치환 원칙에 위배하는 사례이다.

참고