seaking110 님의 블로그
Thead 본문
프로세스와 스레드
- 프로세스
- 운영체제로부터 작업을 할당받는 작업의 단위
- 스레드
- 프로세스가 할당받은 자원의 실행의 단위
프로세스 구조
- Os가 프로그램 실행을 위한 프로세스를 할당 시 프로세스 안에 코드, 데이터, 메모리 영역 (stack, heap)을 할당
- 코드
- 바이트 코드
- 데이터
- 프로그램이 실행 중 저장 가능한 저장공간을 의미
- 전역변수 정적변수(static), 배열 등 초기화된 데이터를 저장
- 메모리
- stack : 지역변수, 매개변수 리턴변수를 저장
- Heap : 동적으로 필요한 변수를 저장하는 공간
- 코드
스레드의 생성
- 실행요청이 들어오면 스레드를 만들어 명령을 처리
- 프로세스 안에는 여러 스레드가 존재
- 스레드는 프로세스안에 있는 자원을 공유
자바에서의 스레드
- 일반 스레드와 동일하나 JVM 프로세스 위에서 실행
- 무조건 Java Main 스레드부터 실행
- JVM에 의해 실행
멀티스레드
- 자바는 메인 스레드가 main 메서드를 실행시키면서 시작
- main 스레드는 필요에 따라서 작업 스레드들을 생성해서 병렬로 코드를 실행 가능
- 싱글 스레드
- 프로세스 안에서 하나의 스레드만 실행되는 것
- JVM의 메인 스레드가 종료 시 JVM도 같이 종료
- 멀티 스레드
- 자바 프로그램이 메인 스레드 외에 다른 작업 스레드를 생성
- 장점
- 여러개의 작업을 동시에 할 수 있어서 성능이 좋아짐
- 스택을 제외한 모든 영역에서 메모리를 공유하여 자원을 효율적으로 사용 가능
- 응답 스레드와 작업 스레드를 분리하여 빠르게 응답 가능 (비동기)
- 단점
- 프로세스의 자원을 공유하므로 서로 자원을 사용하려는 동기화 문제가 발생
- 둘 이상의 스레드가 서로 자원을 원하는 상태가 되어 상대의 작업이 종료되기를 기다리며 모든 작업이 진행되지 못하게 되는 상태인 교착 상태 (deadlock)이 발생
스레드 구현 방법
- 스레드 클래스를 직접 상속 받고 사용
- 스레드 클래스에서 run() 메서드를 오버라이딩하여 작업 내용 정의
- 객체 생성 후 start() 호출 시 스레드 실행
// 스레드 클래스를 상속
public class TestThread extends Thread{
@Override
public void run() {
// 실제 우리가 쓰레드에서 수행할 작업
for (int i=0; i<100; i++){
System.out.print("*");
}
}
}
// 메인 메서드
public static void main(String[] args) {
// 객체 생성
TestThread thread = new TestThread();
// start 호출
thread.start();
}
2. Rnnable 인터페이스를 구현해서 사용
- Runnable 인터페이스를 직접 구현하여 작업 내용을 run() 메서드에 정의
- Runnable 객체를 쓰레드 생성자에 매개변수로 전달하여 thread 실행
- 다른 클래스를 상속 받을 수 있으므로 더욱 유연함
- 작업과 스레드 실행을 분리하여 설계가 명확하고 재사용성이 높아 실무에 자주 사용
// Runnable 인터페이스 구현
public class TestRunnable implements Runnable {
@Override
public void run() {
for (int i=0; i<100; i++){
System.out.print("$");
}
}
}
// 메인 메서드
public static void main(String[] args) {
Runnable run = new TestRunnable();
Thread thread = new Thread(run);
thread.start();
}
3. 람다식을 이용해 Runnable 객체를 만들어서 사용
- 가장 간단하고 따로 클래스를 만들지 않아 유용
public static void main(String[] args) {
Runnable task = () -> {
for (int i = 0; i < 100; i++) {
System.out.print("$");
}
};
Thread thread1 = new Thread(task);
thread1.setName("thread1");
thread1.start();
}
데몬 스레드
- 보이지 않는 곳에서 실행되는 낮은 우선순위를 가진 스레드
- 보조적인 역할을 담당
- EX) 가비지 컬렉터
public static void main(String[] args) {
Runnable demon = () -> {
for (int i = 0; i < 1000000; i++) {
System.out.println("demon");
}
};
Thread thread = new Thread(demon);
// 스레드를 데몬 스레드로 변경
// 리소스를 적게 할당받기 때문에 느리게 발동
thread.setDaemon(true);
thread.start();
for (int i = 0; i < 100; i++) {
System.out.println("task");
}
}
사용자 스레드
- 보이는 곳에서 실행되는 높은 우선순위를 가진 스레드
- 프로그램 기능을 담당
- EX) 메인 스레드
- JVM 사용자 스레드의 작업이 끝나면 데몬 스레드도 자동으로 종료
스레드의 우선순위
- 작업의 중요도에 따라서 스레드의 우선순위를 부여
- 최대 10 보통 5 최소 1
- 스레드의 우선 순위는 setPriority(숫자) 로 부여
Thread thread1 = new Thread(task1);
thread1.setPriority(8);
스레드 그룹
- 스레드를 그룹으로 관리
- 가장 큰 그룹은 system
- 메인 스레드는 system 그룹의 하위 그룹인 main 그룹에 포함
- 모든 스레드는 반드시 하나의 그룹에 포함
- 스레드는 자신을 생성한 부모 스레드의 그룹과 우선순위를 상속
스레드 상태와 제어
- 스레드의 객체가 생성되면 실행과 대기를 반복하며 run 메서드 수행

- 또한 일시정지 상태도 가능
- 일시정지 상태에서는 다시 실행 대기 상태로 넘어가야 실행 가능
|
상태
|
Enum
|
설명
|
|
객체생성
|
NEW
|
쓰레드 객체 생성, 아직 start() 메서드 호출 전의 상태
|
|
실행대기
|
RUNNABLE
|
실행 상태로 언제든지 갈 수 있는 상태
|
|
일시정지
|
WAITING
|
다른 쓰레드가 통지(notify) 할 때까지 기다리는 상태
|
|
일시정지
|
TIMED_WAITING
|
주어진 시간 동안 기다리는 상태
|
|
일시정지
|
BLOCKED
|
사용하고자 하는 객체의 Lock이 풀릴 때까지 기다리는 상태
|
|
종료
|
TERMINATED
|
쓰레드의 작업이 종료된 상태
|
- 스레드 제어

- sleep()
- 현재 스레드를 지정된 시간동안 멈추게 한다.
- 스레드 자신만 멈추게 한다!
- sleep이라는 메서드는 interruptedException 을 throws 하고 있기 때문에 반드시 예외 처리를 해줘야한다.
- interrupted 는 일시정지를 Runnable 상태로 만드는 코드로 sleep 중엔 runnable 상태가 되면 안되기 때문에!
- interrupt()
- 일시정지 상태인 스레드를 실행대기 상태로 만드는 것
- sleep 단계에서 interrupt를 만나면 예외가 발생한다!
- interrupted 된 상태인지 아닌지 여부
thread.isInterrupted(); // true or false
- join()
- 정해진 시간동안 지정한 스레드가 작업하는 것을 기다리는 것
- 시간을 지정하지 않으면 지정한 스레드의 작업이 끝날 때 까지 기다림
- join 역시 예외 처리를 해줘야한다!
- yield()
- 남은 시간을 다음 스레드에게 양보하고 스레드 자신은 실행 대기 상태가 되는 코드
- synchronized()
- 멀티스레드의 경우 여러 스레드가 한 프로세스의 자원을 공유하여 사용하기 때문에 서로에게 영향을 준다.
- 이로 인해 장애나 버그가 발생하는데 이를 방지하는 방법이 스레드 동기화 (Synchronization)
- 한 스레드가 진행중인 작업을 다른 스레드가 침범하지 못하도록 막는 것
- 임계영역으로 침범을 막아야하는 코드를 설정
- 임계영역에는 Lock을 가진 단 하나의 스레드만 출입
- 임계 영역 지정
- 메서드 전체를 임계영역으로 지정
public synchronized void asyncSum() {
코드
}
- 특정 영역을 임계영역으로 지정
synchronized(해당 객체의 참조변수) {
}
- wait() 와 notify()
- 침범을 막은 코드를 수행하다가 작업을 더 이상 진행할 상황이 아니면 wait를 호출하여 lock을 반납
- 추후 작업을 진행할 수 있다면 notify()를 호출하여 다시 진행
- Lcok()
- synchronized 블럭은 같은 메서드 내에서만 걸 수 있기때문에 Lcok 클래스를 직접 사용한다
- ReentrantLock
- 재진입 가능한 Lock, 가장 일반적인 배타 Lock
- 특정 조건에서 Lock을 풀고 나중에 다시 Lock을 얻어 임계영역으로 진입이 가능
- 같은 스레드가 이미 락을 가지고 있더라도 락을 유지하며 계속 실행하기 때문에 데드락 발생 x
- 코드 유연성을 높일 수 있음
- ReentrantReadWriteLock
- 읽기를 위한 Lock과 쓰기를 위한 Lock을 따로 제공
- 읽기에는 공유적이고 쓰기에는 베타적인 Lock
- StampedLock
- 낙관적인 Lock으로 데이터를 변경할 때만 락을 걸고 푸는 Lock
- 데이터 변경 시 충돌이 일어날 가능성이 적은 상황에서만 사용
- 속도가 빠르지만 여러 스레드가 동시에 변경하는 건 쉽지 않다
- condition
- waiting pool 내 스레드를 구분하는 방법!
- 특정 조건이 만족한 스레드만 깨우는것!
- ReentrantLock 클래스와 함께 사용
- wait와 notify 대신 await().와 signal() 을 사용
'Today I Learned' 카테고리의 다른 글
| 계산기 만들기 (0) | 2025.01.10 |
|---|---|
| 모던 자바 (0) | 2025.01.08 |
| Generic (0) | 2025.01.06 |
| 예외 처리! (2) | 2025.01.03 |
| 객체지향 프로그래밍 이란? (1) | 2025.01.02 |