쓰레드 (Thread)
쓰레드(Thread)는 프로그램 내에서 실행되는 가장 작은 작업 단위입니다.
자바에서 쓰레드는 하나의 프로세스 내에서 여러 작업을 동시에 수행(병렬 처리)할 수 있도록 지원하는 기능입니다.
- 프로세스(Process) : 실행 중인 프로그램.
- 쓰레드(Thread) : 프로세스 내에서 실행되는 작업 단위
특징
- 멀티스레딩(Multithreading)
- 하나의 프로세스에서 여러 쓰레드가 동시에 실행되는 방식.
- 예: 웹 브라우저에서 하나의 쓰레드가 화면을 렌더링하고, 다른 쓰레드가 파일을 다운로드.
- 공유 메모리
- 쓰레드는 같은 프로세스 내에서 메모리를 공유하므로, 서로 데이터를 쉽게 주고받을 수 있음.
- 하지만 공유 자원을 동시에 접근할 때 동기화 문제가 발생할 수 있음.
- 경량성
- 쓰레드는 프로세스보다 가볍고 생성, 소멸 비용이 적음.
- 병렬 처리
- CPU 코어를 효율적으로 활용하여 동시에 여러 작업을 수행할 수 있음.
주요 메서드와 예제
* 주요 메서드
메서드 | 설명 | 사용 빈도 |
start() | 새로운 쓰레드를 생성하고 run() 메서드를 호출하여 실행. | ⭐⭐⭐ |
run() | 쓰레드가 실행할 작업을 정의. 일반적으로 start()에 의해 호출됨. | ⭐⭐ |
sleep(long millis) | 현재 쓰레드를 지정된 시간 동안 일시 정지 상태로 만듦. | ⭐⭐⭐ |
join() | 호출한 쓰레드가 종료될 때까지 현재 쓰레드를 대기 상태로 만듦. | ⭐⭐⭐ |
interrupt() | 현재 실행 중인 쓰레드를 인터럽트 상태로 변경. | ⭐⭐ |
isAlive() | 쓰레드가 실행 중인지 여부를 반환. | ⭐⭐ |
yield() | 현재 쓰레드가 실행 중인 상태를 양보하고 다른 쓰레드에게 실행 기회를 줌. | ⭐ |
wait() | 호출한 쓰레드를 대기 상태로 전환. 동기화 블록에서만 사용 가능. | ⭐⭐⭐ |
notify() | wait() 상태의 쓰레드 중 하나를 깨움. 동기화 블록에서만 사용 가능. | ⭐⭐⭐ |
notifyAll() | wait() 상태의 모든 쓰레드를 깨움. | ⭐⭐ |
setPriority(int priority) | 쓰레드의 우선순위를 설정 (1~10). 기본값은 5. | ⭐ |
getPriority() | 쓰레드의 현재 우선순위를 반환. | ⭐ |
* 예제
- start()와 run()
start()는 새로운 쓰레드를 생성하고, 내부적으로 run() 메서드를 호출합니다.
class MyThread extends Thread {
@Override
public void run() {
System.out.println("쓰레드 실행 중");
}
}
public class Main {
public static void main(String[] args) {
MyThread thread = new MyThread();
thread.start(); // 새로운 쓰레드 생성 및 실행
}
}
- sleep()
sleep()은 현재 쓰레드를 일정 시간 동안 멈추게 합니다.
public class SleepExample {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
System.out.println("쓰레드 시작");
try {
Thread.sleep(2000); // 2초 동안 대기
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("쓰레드 종료");
});
thread.start();
}
}
➡️ 출력
쓰레드 시작
(2초 대기)
쓰레드 종료
- join()
join()은 호출한 쓰레드가 종료될 때까지 기다립니다.
public class JoinExample {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
System.out.println("쓰레드 작업 중...");
try {
Thread.sleep(1000); // 1초 대기
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("쓰레드 작업 완료");
});
thread.start();
try {
thread.join(); // 쓰레드가 종료될 때까지 대기
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("메인 쓰레드 종료");
}
}
➡️ 출력
쓰레드 작업 중...
쓰레드 작업 완료
메인 쓰레드 종료
- wait()와 notify()
멀티스레드 간 통신을 구현하는 데 사용합니다.
class SharedResource {
private boolean ready = false;
public synchronized void waitForSignal() {
while (!ready) {
try {
wait(); // 다른 쓰레드가 notify() 호출할 때까지 대기
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("신호를 받았습니다!");
}
public synchronized void sendSignal() {
ready = true;
notify(); // 대기 중인 쓰레드 하나를 깨움
System.out.println("신호를 보냈습니다!");
}
}
public class WaitNotifyExample {
public static void main(String[] args) {
SharedResource resource = new SharedResource();
Thread waitingThread = new Thread(resource::waitForSignal);
Thread notifyingThread = new Thread(resource::sendSignal);
waitingThread.start();
try {
Thread.sleep(100); // 신호 보내기 전에 대기
} catch (InterruptedException e) {
e.printStackTrace();
}
notifyingThread.start();
}
}
➡️ 출력
신호를 보냈습니다!
신호를 받았습니다!
start()와 run()의 차이
- start()
- 새로운 쓰레드를 생성하고, 쓰레드의 run() 메서드를 실행.
- 병렬로 작업을 수행.
- run()
- 현재 실행 중인 쓰레드에서 run() 메서드를 호출.
- 병렬 작업이 아닌, 일반 메서드 호출처럼 동작.
public class Main {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
System.out.println("Thread 실행");
});
thread.start(); // 새로운 쓰레드에서 실행
thread.run(); // 현재 쓰레드(main)에서 실행
}
}
➡️ 출력
Thread 실행 (새로운 쓰레드에서 실행)
Thread 실행 (현재 쓰레드에서 실행)
기본 사용법 2가지
자바에서 쓰레드를 생성하고 실행하는 방법은 크게 두 가지가 있습니다.
- Thread 클래스 상속
- Runnable 인터페이스 구현
1. Thread 클래스 상속
Thread 클래스를 상속받아 쓰레드를 생성하는 방법입니다.
public class MyThread extends Thread {
@Override
public void run() {
// 쓰레드에서 실행할 작업
for (int i = 1; i <= 5; i++) {
System.out.println("Thread: " + i);
}
}
public static void main(String[] args) {
MyThread thread = new MyThread(); // 쓰레드 객체 생성
thread.start(); // 쓰레드 실행
}
}
➡️ 출력
Thread: 1
Thread: 2
Thread: 3
Thread: 4
Thread: 5
2. Runnable 인터페이스 구현
Runnable 인터페이스를 구현하여 쓰레드를 생성하는 방법입니다.
public class MyRunnable implements Runnable {
@Override
public void run() {
// 쓰레드에서 실행할 작업
for (int i = 1; i <= 5; i++) {
System.out.println("Runnable: " + i);
}
}
public static void main(String[] args) {
Thread thread = new Thread(new MyRunnable()); // Runnable 구현체 전달
thread.start(); // 쓰레드 실행
}
}
➡️ 출력
Runnable: 1
Runnable: 2
Runnable: 3
Runnable: 4
Runnable: 5
쓰레드 공유 객체(Thread Shared Object)
쓰레드 공유 객체(Thread Shared Object)는 여러 쓰레드가 동시에 접근하거나 사용하는 객체를 의미합니다.
쓰레드는 같은 프로세스 내에서 메모리를 공유하므로, 특정 객체를 여러 쓰레드가 동시에 사용할 수 있습니다.
공유 객체는 자원의 효율적인 활용을 가능하게 하지만, 동시에 데이터 무결성과 동기화 문제를 발생시킬 수 있습니다.
* 특징
- 공유 메모리
- 자바에서는 쓰레드가 같은 힙 메모리 공간을 공유하므로, 하나의 객체를 여러 쓰레드가 동시에 사용할 수 있습니다.
- 문제점
- 여러 쓰레드가 동시에 객체의 상태를 변경하려고 하면 데이터 충돌이나 일관성 문제가 발생할 수 있습니다.
- 해결책
- 동기화(Synchronization)를 통해 쓰레드가 공유 객체를 안전하게 접근하도록 제어합니다.
* 예제
다음은 두 쓰레드가 동일한 카운터 객체를 공유하며 값을 증가시키는 예제입니다.
class Counter {
private int count = 0;
public void increment() {
count++;
}
public int getCount() {
return count;
}
}
public class Main {
public static void main(String[] args) throws InterruptedException {
Counter counter = new Counter(); // 공유 객체 생성
Thread t1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
counter.increment();
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
counter.increment();
}
});
t1.start();
t2.start();
t1.join();
t2.join();
// 결과가 항상 2000이 아닐 수 있음 (데이터 충돌 발생 가능)
System.out.println("최종 카운트: " + counter.getCount());
}
}
➡️ BUT, 데이터 무결성에 문제 발생
- increment() 메서드가 여러 쓰레드에서 동시에 호출될 경우, count 값이 정확히 업데이트되지 않을 수 있습니다.
- count++는 다음과 같은 세 단계로 이루어지며, 이 과정이 중단되면 문제가 발생합니다:
- count 값을 읽음
- count 값을 증가
- count 값을 저장
➡️ 해결 방법 : 쓰레드 동기화 (Synchronization) -> synchronized 키워드를 사용
*공유 객체를 사용하는 이유
- 자원 효율성
- 여러 쓰레드가 동일한 객체를 공유하여 메모리 사용을 최소화
- 작업 분담
- 여러 쓰레드가 하나의 객체를 사용해 병렬로 작업을 처리
- 일관된 데이터
- 공유 객체를 사용하여 쓰레드 간에 데이터 전달 및 동기화
* 주의점
- 동기화 필수
- 공유 객체에 대한 쓰레드의 접근을 적절히 동기화하지 않으면 데이터 무결성 문제가 발생.
- 데드락(Deadlock)
- 잘못된 동기화로 인해 쓰레드가 서로 무한히 기다리는 상태가 발생할 수 있음.
- 경합 조건(Race Condition)
- 두 개 이상의 쓰레드가 동일한 자원에 동시에 접근하여 발생하는 비정상적인 결과.
- 성능 저하
- 지나친 동기화는 성능을 저하시킬 수 있으므로 필요 최소한으로 동기화 적용.
동기화의 주요 방법
1. synchronized 키워드
- 메서드나 블록 단위로 사용하여 공유 객체에 대한 접근을 제어
2. ReentrantLock 사용
- 더 세밀한 동기화 제어를 위해 java.util.concurrent.locks.ReentrantLock을 사용
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class Counter {
private int count = 0;
private final Lock lock = new ReentrantLock();
public void increment() {
lock.lock(); // 락 획득
try {
count++;
} finally {
lock.unlock(); // 락 해제
}
}
public int getCount() {
return count;
}
}
3. Atomic 클래스
- java.util.concurrent.atomic 패키지에서 제공하는 AtomicInteger, AtomicLong 등을 사용하면 동기화 없이 원자적 연산 가능
import java.util.concurrent.atomic.AtomicInteger;
class Counter {
private final AtomicInteger count = new AtomicInteger(0);
public void increment() {
count.getAndIncrement();
}
public int getCount() {
return count.get();
}
}
쓰레드 동기화 (Synchronization)
여러 쓰레드가 공유 자원에 동시에 접근하면 데이터가 일관성 없게 처리될 수 있습니다. 이를 해결하기 위해 동기화를 사용합니다.
- synchronized 키워드를 사용하여 공유 자원을 보호합니다.
public class Counter {
private int count = 0;
public synchronized void increment() {
count++;
}
public int getCount() {
return count;
}
public static void main(String[] args) throws InterruptedException {
Counter counter = new Counter();
Thread t1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) counter.increment();
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 1000; i++) counter.increment();
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("최종 카운트: " + counter.getCount());
}
}
➡️ 출력
최종 카운트: 2000
- 동기화 블록을 사용하여 increment() 메서드의 특정 부분을 동기화합니다.
public void increment() {
synchronized (this) { // 특정 부분만 동기화
count++;
}
}
멀티스레드(Multithreading)
멀티스레드(Multithreading)는 하나의 프로세스(Process)에서 여러 개의 쓰레드(Thread)를 생성하여 동시에 실행하는 것을 의미합니다. 이를 통해 하나의 프로그램이 여러 작업을 병렬로 처리할 수 있어 CPU 사용률을 최적화하고, 응답성과 처리 속도를 높일 수 있습니다.
* 장점
- 성능 향상
- 여러 작업을 동시에 처리하여 CPU 활용도를 극대화
- 응답성 개선
- 대기 시간이 긴 작업(예: 파일 다운로드)도 병렬로 처리하여 사용자 응답성을 높임
- 자원 공유
- 같은 프로세스 내에서 메모리를 공유하므로 효율적
* 단점
- 복잡성 증가
- 동기화, 경합 조건(Race Condition) 등으로 인해 코드가 복잡해질 수 있음
- 디버깅 어려움
- 병렬 처리로 인해 발생하는 문제를 재현하거나 디버깅하기 어려움
- 자원 소비
- 지나치게 많은 쓰레드를 생성하면, 문맥 교환(Context Switching) 오버헤드로 성능이 저하될 수 있음
데몬 스레드 (Daemon Thread)
데몬 스레드(Daemon Thread)는 백그라운드에서 실행되는 보조 쓰레드입니다.
주 쓰레드(Main Thread) 또는 사용자 쓰레드가 실행 중일 때 보조 역할을 수행하며, 모든 사용자 쓰레드가 종료되면 데몬 스레드도 자동으로 종료됩니다.
* 일반 스레드 (User Thread) VS 데몬 스레드 (Daemon Thread)
특징 | 일반 쓰레드(User Thread) | 데몬 쓰레드(Daemon Thread) |
역할 | 주요 작업 실행 | 백그라운드에서 보조 작업 수행 |
프로그램 종료 시 동작 | 모든 일반 쓰레드가 종료될 때까지 실행 유지 | 모든 일반 쓰레드가 종료되면 자동으로 종료 |
설정 | 기본적으로 일반 쓰레드 | setDaemon(true)로 설정해야 함 |
* 예시
public class DaemonThreadExample {
public static void main(String[] args) {
// 일반 쓰레드
Thread userThread = new Thread(() -> {
for (int i = 1; i <= 5; i++) {
System.out.println("사용자 쓰레드 실행: " + i);
try {
Thread.sleep(1000); // 1초 대기
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
// 데몬 쓰레드
Thread daemonThread = new Thread(() -> {
while (true) {
System.out.println("데몬 쓰레드 실행 중...");
try {
Thread.sleep(500); // 0.5초 대기
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
daemonThread.setDaemon(true); // 데몬 쓰레드로 설정
userThread.start(); // 사용자 쓰레드 시작
daemonThread.start(); // 데몬 쓰레드 시작
}
}
➡️ 출력
데몬 쓰레드 실행 중...
사용자 쓰레드 실행: 1
데몬 쓰레드 실행 중...
데몬 쓰레드 실행 중...
사용자 쓰레드 실행: 2
데몬 쓰레드 실행 중...
데몬 쓰레드 실행 중...
사용자 쓰레드 실행: 3
데몬 쓰레드 실행 중...
데몬 쓰레드 실행 중...
사용자 쓰레드 실행: 4
데몬 쓰레드 실행 중...
데몬 쓰레드 실행 중...
사용자 쓰레드 실행: 5
- userThread는 5번 출력 후 종료
- daemonThread는 백그라운드에서 계속 실행되지만, userThread가 종료되면 함께 종료
* 주의 사항
- start() 전에 설정해야 함
- setDaemon(true)는 쓰레드가 시작되기 전에만 호출 가능
- 이미 시작된 쓰레드에서는 IllegalThreadStateException 발생
- 데몬 쓰레드는 중요한 작업에 사용하지 말 것
- 사용자 쓰레드가 종료되면 데몬 쓰레드도 종료되므로, 중요한 데이터 처리나 작업에는 부적합
결론
- 쓰레드는 자바에서 멀티태스킹을 구현하는 기본 단위로, 하나의 프로세스에서 여러 작업을 동시에 처리할 수 있도록 합니다.
- 효율적인 작업 분할, 병렬 처리, 응답성 향상을 위해 자주 사용됩니다.
- 하지만, 공유 자원의 동기화와 같은 문제를 올바르게 처리하지 않으면 오류가 발생할 수 있으므로 신중하게 설계해야 합니다.
출처
OpenAI의 ChatGPT (https://openai.com)
'Java > Grammar' 카테고리의 다른 글
[Java 문법] 람다식 Lamda (2) | 2024.11.29 |
---|---|
[Java 문법] 어노테이션 (Annotation) (2) | 2024.11.28 |
[Java 문법] 래퍼 클래스 (Wrapper Class) (0) | 2024.11.25 |
[Java 문법] String.split() method (2) | 2024.11.24 |
[Java 문법] for-each 구문 (0) | 2024.11.22 |