14장 Thread
1. 스레드?
프로그램 vs 프로세스 vs 쓰레드
프로그램 : 프로세스를 실행할 수 있는 파일로, cpu를 할당 받기 전의 상태를 의미한다.
프로세스 : 실행 중인 프로그램으로 작업을 위한 cpu 자원을 할당 받은 상태. 각 프로세스는 독립된 메모리 공간을 가짐.
스레드: 프로세스 내에서 실제로 작업을 수행하는 실행 단위. 여러 쓰레드가 하나의 메모리 공간(Heap)을 공유해서 사용 한다. 중심 쓰레드를 main스레드라 부르며 , 그 외의 쓰레드를 서브스레드라고 부른다.
모든 프로세스는 1개 이상의 쓰레드를 가지고 있다.
멀티쓰레드
자바는 한번에 여러 개의 스레드를 실행할 수 있는 멀티쓰레드 환경을 지원한다.
멀티스레드는 하나의 쓰레드가 해야할 작업을 여러개의 쓰레드가 분담하여 작업할 수 있으므로 작업 효율이 향상 된다.
멀티쓰레드로 인해 사용자는 빠른 응답을 받을 수 있다.
멀티쓰레드 환경에서는 자원 공유에 따른 동기화 문제가 발생하며, DeadLock상태가 발생할 수 있으므로 주의해야 한다.
쓰레드의 특징
동시성 (Concurrency)
여러 작업을 교대로 빠르게 처리하여 동시에 여러 작업이 실행되는 것 처럼 보이는 특징.
실제로는 스케쥴러에 의해 실행할 쓰레드를 순간적으로 전환하며 실행하기 때문에 "동시"에 실행되지는 않는다.(싱글 코어 기준)
병렬성 (Parallelism)
CPU코어가 여러개인 경우 CPU코어별로 작업내용이 할당 되므로, 코어의 개수만큼 실제로 여러 작업을 동시에 실행한다.
즉, CPU코어가 여러 개라면 쓰레드가 실제로 동시에 여러 작업을 수행할 수 있게 되는 것.
독립성(Isolation)
하나의 스레드에서 발생한 에러는 다른 스레드에 영향을 끼치지 않는다.
개별적인 실행을 보장하기 위해 스레드는 고유한 stack영역을 할당 받는다.
2. 쓰레드 생성 방법
1. Thread 클래스 상속
2. Runnable 인터페이스 구현
3. 람다 표현식 사용
3. 스레드 스케줄링
스레드는 OS에 의해 스케줄링되며, 작업 순서를 개발자가 완전히 제어할 수는 없지만, 우선순위를 설정하거나 데몬 여부(종속설정)를 조절하여 실행 흐름에 관여할 수 있습니다.
1. 우선순위 기반 스케줄링
자바에서
Thread.setPriority(int)
메서드를 통해 우선순위를 설정할 수 있습니다.우선순위는 1 (MIN_PRIORITY) ~ 10 (MAX_PRIORITY) 사이의 값이며 기본값은 5입니다.
단, 우선순위가 높다고 먼저 실행된다는 보장은 없습니다. JVM은 스케줄링을 운영체제에 위임하기 때문에 결과는 OS마다 다르게 나올 수 있습니다.
2. Round-Robin 방식 (순환 할당)
운영체제가 각 스레드에 동일한 시간만큼 CPU를 할당하는 기본 스케쥴링 방식입니다.
자바의 JVM은 운영체제의 스레드 스케줄러에 위임하므로 Round-Robin은 운영체제의 스케쥴링 방식이라고 볼 수 있습니다.
자바 코드로는 제어할 수 없으며, 같은 우선순위 스레드들 사이에 적용됩니다.
우선순위에서 같은 값을 가진 스레드 사이에서는 Round-Robin이 적용됩니다.
3. 데몬 스레드
setDaemon(true)
설정 시 메인 스레드가 종료되면 자동으로 같이 종료됩니다.백그라운드 작업(로그 저장, 자동 저장 등)에 자주 사용됩니다.
4. 동기화(Synchronization)
멀티스레드 환경에서는 여러 스레드가 동시에 하나의 공유 자원(Heap영역의 객체 등)에 접근할 수 있습니다. 이 상태를 경쟁상태(race condition) 라고 부릅니다. 경쟁상태에서는 데이터 충돌이나 예기치 못한 결과가 발생할 수 있습니다. 이 문제를 방지하기 위해서는 경쟁상태의 자원을 통제하기 위한 통제영역(임계영역)을 지정하기 위해 사용하는 것이 "동기화(synchronization)"입니다.
synchronized
synchronized
키워드를 사용하여 특정 메서드나 블록을 한 번에 하나의 스레드만 접근할 수 있도록 제한합니다.synchronized예약어로 여러 스레드가 동시에 접근해서는 안 되는 메서드를 임계 영역 (Critical Section)을 지정한다.
예시코드:
위 예제에서
withdraw
메서드는 임계 영역이므로synchronized
를 사용하여 한 번에 하나의 스레드만 접근하게 합니다.
동기화 비유
여러분들이 화장실을 이용한다고 가정해봅시다. 화장실은 칸당 1명만 들어갈 수 있는 구조로, 누군가 화장실에 들어가면 문을 잠그고(Lock) 사용이 끝나면 문을 열고(UnLock)나옵니다. 이때 화장실 밖에는 화장실칸에 들어가려는 사람들이 순서에 맞춰 대기중일 겁니다.
코딩적 관점에서 보면 화장실 칸은 공유자원으로, 이를 이용하는 사람은 스레드입니다. 화장실을 이용할 때는 한번에 한명의 사람이 이용하듯, 동기화 처리가 완료된 공유자원은 한번에 한개의 스레드만 접근할 수 있습니다. 이때 공유자원을 사용 중 인 스레드는 Lock을 얻고, 사용 완료한 스레드는 UnLock후 다음 스레드가 Lock을 획득합니다.
이 과정을 락(Lock) 메커니즘이라고 부릅니다. 자바에서는 동기화(
synchronized
)를 통해 락 메커니즘을 구현할 수 있습니다.
동기화의 단점
모든 메서드에
synchronized
를 걸면 병목 현상이 발생할 수 있습니다.병목현상 : 하나의 공유자원에 너무 많은 스레드가 몰려 , 전체 시스템 성능이 하향되는 현상
8차선 도로로 주행하던 차들이 1차선 도로로 주행해야 하는 경우를 생각하시면 됩니다.
스레드 수가 많아질수록 전체 시스템성능이 하향될 수 있으므로 , 반드시 필요한 부분에만
synchronized
를 걸어야 합니다.
데드락
두개 이상의 스레드가 서로 락을 얻기 위해 무한으로 대기하는 현상
데드락 예시 코드
5. 쓰레드의 생명 주기 (Life Cycle)와 제어메서드
생명주기
NEW:
new Thread()
로 생성만 된 상태RUNNABLE:
start()
호출 후 운영체제에 의해 실행 가능 상태RUNNING: 실제로 CPU를 점유하고 실행 중인 상태
BLOCKED / WAITING / TIMED_WAITING: 일시 정지된 상태 (sleep, wait, join 등)
BLOCKED ⇒ 동기화 블럭에서 스레드가 Lock을 얻기 위해 대기하는 상태.
TERMINATED: 실행이 종료된 상태
스레드 상태 제어 메서드
sleep(ms)
: 지정 시간 동안 일시 정지 (예: Thread.sleep(1000))스레드는 RUNNING -> TIME_WAITING->RUNNABLE 상태로 변경
join()
: 특정 스레드가 끝날 때까지 대기(일시정지)RUNNING -> (WAITING/TIME_WAITING)
interrupt()
: 일시정지 상태의 스레드를 깨움 (InterruptedException
발생)RUNNING -> TIME_WAITING -> RUNNABLE
wait()
: 동기화 블록(synchronized)에서 사용되는 스레드 간 통신으로 스레드를 대기상태로 변경RUNNING -> WAITING
notify()
/notifyAll()
: 동기화 블록에서 사용되는 스레드 간 통신으로 WAITING상태의 쓰레드를 깨움WAITING -> RUNNABLE
6. 스레드 풀
스레드를 필요할때마다 생성하는 것이 아닌, 미리 여러개의 스레드를 생성하여 보관해두고, 재사용하는 스레드 저장공간을 스레드 풀이라고 부른다.
스레드가 생성/소멸할때 발생하는 자원의 낭비를 줄일 수 있으며, 사용자의 스레드 요청시 생성 없이 만들어둔 스레드를 즉시 반환할 수 있으므로 빠른 응답이 가능해진다.
치킨가게에서 메뉴주문시 조리된 치킨을 즉시 주는 것과, 주문이 들어간 후 치킨을 만들어서 주는 것의 차이.
사용자의 요청이 들어올때 마다 스레드를 계속 생성한다면, 과도하게 많은 스레드가 생성될 수 있으나, 미리 생성할 총 스레드의 갯수를 정해두고 미리 생성한다면 이러한 현상을 방지할 수 있음.
Last updated