스레드(Thread)
Q. 자바에서 스레드(Thread)에 대해 설명해주세요.
스레드는 프로세스 내에서 실행되는 가장 작은 실행 단위입니다. 하나의 프로그램에서 여러 작업을 동시에 수행하기 위해 사용됩니다. 예를 들어, 파일 다운로드하면서 동시에 화면을 표시하는 등의 작업이 가능해집니다. 스레드를 생성하는 방법은 Thread 클래스를 상속받는 방법과 Runnable 인터페이스를 구현하는 두 가지 방법이 있습니다. 보통 Runnable 인터페이스를 구현하는 것이 더 권장되는데, 자바가 단일 상속만 지원하기 때문에 Thread 상속은 다른 클래스 상속이 필요할 때 제한이 될 수 있기 때문입니다. 자바의 스레드는 Concurrent(동시성)하게 동작합니다. Concurrent는 여러 작업이 동시에 실행되는 것처럼 보이지만, 실제로는 CPU가 빠르게 여러 스레드를 번갈아가며 처리하는 방식입니다. 즉, 논리적으로는 동시 실행처럼 보이지만, 물리적으로는 시분할(Time Slicing) 방식으로 CPU 시간을 나누어 처리하는 것입니다.
Q. 그렇다면 스레드로 여러 작업을 동시에 수행할 때 발생할 수 있는 문제점에 대해 설명해주세요.
가장 대표적인 문제로는 Race Condition과 DeadLock이 있습니다. 먼저 Race Condition은 여러 스레드가 공유 자원에 동시에 접근할 때 발생하는 문제입니다. 예를 들어, 두 스레드가 동시에 하나의 변수 값을 증가시키려 할 때 예상치 못한 결과가 나올 수 있습니다. 그다음으로 DeadLock은 두 개 이상의 스레드가 서로가 점유한 자원을 기다리며 무한 대기하는 상태를 말합니다. 쉽게 설명하면, A 스레드가 자원 1을 들고 자원 2를 기다리는데, B 스레드는 자원 2를 들고 자원 1을 기다리는 상황이라고 보시면 됩니다. 자바에서는 synchronized 키워드를 사용해서 임계 영역을 설정하거나, Lock 인터페이스를 구현한 ReentrantLock을 사용하여 해결할 수 있습니다. 또한 DeadLock의 경우에는 락을 획득하는 순서를 일정하게 가져가거나, 타임아웃을 설정하는 방법으로 해결할 수 있습니다. 추가로 Starvation이라고 하는 기아 상태 문제가 있습니다. 이는 특정 스레드가 계속해서 자원을 할당받지 못하는 상황을 말합니다. 이런 경우에는 우선순위를 조정하거나 공정한 Lock을 사용하는 방법으로 해결할 수 있습니다.
Q. synchronized 키워드를 사용하는 것과 Reentrant Lock을 사용하는 것은 어떠한 차이점이 있나요?
먼저 Synchronized는 현재 데이터를 사용하고 있는 해당 스레드를 제외하고 나머지 스레드들은 데이터에 접근 할 수 없도록 막는 것으로 자바에서 기본적으로 제공하는 Lock 메커니즘이라고 볼 수 있습니다. 반면에 ReentrantLock은 synchronized 보다 더 유연한 기능을 제공합니다. 예를 들어 Lock의 획득과 해제를 다른 범위에서 할 수 있고, 타임아웃을 설정할 수도 있습니다. 또한 tryLock() 메서드를 통해 현재 Lock을 획득할 수 있는지 확인할 수 있씁니다. 가장 큰 차이점은 사용 방식입니다. synchronized는 블록 구문이나 메서드 선언에 키워드로 사용되는 반면, ReentrantLock은 lock()과 unlock() 메서드를 명시적으로 호출해야 합니다. 이 때문에 finally 블록에서 반드시 unlock()을 호출하여 락을 해제해주어야 하는 책임이 있습니다. 일반적으로는 단순한 동기화가 필요한 경우 synchronized를 사용하고, 더 세밀한 제어가 필요한 경우에는 ReentrantLock을 사용하는 것이 권장됩니다.
Last updated