Java Threads
자바 스레드 (Java Threads): 개념 및 사용법
- *스레드(Thread)**는 자바에서 병렬 처리를 구현하는 중요한 개념입니다. 자바 애플리케이션은 기본적으로 하나의 메인 스레드에서 실행되지만, 멀티스레드를 사용하면 여러 작업을 동시에 수행할 수 있습니다. 자바에서는 스레드를 이용해 CPU 자원을 효율적으로 활용하고, 작업의 응답성을 개선할 수 있습니다.
1. 스레드의 기본 개념
- 프로세스(Process): 실행 중인 하나의 애플리케이션. 각각의 프로세스는 별도의 메모리 공간을 가집니다.
- 스레드(Thread): 프로세스 내에서 독립적으로 실행되는 흐름. 여러 스레드는 같은 메모리 공간을 공유합니다.
자바에서 스레드를 구현하는 방법에는 두 가지가 있습니다:
Thread
클래스를 상속받아 스레드를 생성하는 방법Runnable
인터페이스를 구현하여 스레드를 생성하는 방법
2. Thread
클래스를 상속하여 스레드 생성
Thread
클래스를 상속받아 스레드를 구현할 수 있습니다. Thread
클래스는 run()
메서드를 가지고 있으며, 이 메서드 안에
스레드가 실행할 작업을 정의합니다. start()
메서드를 호출하면 새로운 스레드가 시작됩니다.
Thread
클래스를 상속한 예제
class MyThread extends Thread {
@Override
public void run() {
// 스레드가 수행할 작업
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + ": " + i);
try {
Thread.sleep(1000); // 1초 대기
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class Main {
public static void main(String[] args) {
MyThread thread1 = new MyThread();
MyThread thread2 = new MyThread();
// 스레드 실행
thread1.start();
thread2.start();
}
}
출력 예시:
Thread-0: 0
Thread-1: 0
Thread-0: 1
Thread-1: 1
Thread-0: 2
Thread-1: 2
...
run()
메서드: 스레드가 실행할 코드를 정의합니다.start()
메서드: 새로운 스레드를 생성하고,run()
메서드를 실행합니다.start()
메서드를 호출해야 스레드가 독립적으로 실행됩니다.
3. Runnable
인터페이스를 구현하여 스레드 생성
Runnable
인터페이스를 구현하는 방법은 Thread
클래스를 상속받는 방법보다 더 유연합니다. 이 방식은 다중 상속이 불가능한 자바에서 다른
클래스를 상속받으면서 스레드를 생성할 수 있는 장점을 제공합니다.
Runnable
인터페이스를 구현한 예제
class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + ": " + i);
try {
Thread.sleep(1000); // 1초 대기
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class Main {
public static void main(String[] args) {
MyRunnable runnable = new MyRunnable();
Thread thread1 = new Thread(runnable);
Thread thread2 = new Thread(runnable);
// 스레드 실행
thread1.start();
thread2.start();
}
}
출력 예시:
Thread-0: 0
Thread-1: 0
Thread-0: 1
Thread-1: 1
Thread-0: 2
Thread-1: 2
...
Runnable
인터페이스: 스레드가 수행할 작업을 정의하는run()
메서드를 구현합니다.Thread
생성자:Runnable
구현체를Thread
생성자의 인수로 전달해 스레드를 생성하고 실행합니다.
4. 스레드의 상태 (Thread States)
스레드는 실행되는 동안 다양한 상태를 가집니다. 자바의 스레드는 다음과 같은 상태를 가집니다:
- NEW: 스레드가 생성되었지만 아직 시작되지 않은 상태.
- RUNNABLE: 스레드가 실행 중이거나, 실행 준비가 된 상태.
- BLOCKED: 스레드가 리소스를 기다리며, 실행되지 못하는 상태.
- WAITING: 스레드가 다른 스레드의 작업을 기다리며 대기 중인 상태.
- TIMED_WAITING: 일정 시간 동안 대기하는 상태.
- TERMINATED: 스레드의 작업이 완료되어 종료된 상태.
5. 스레드 동기화 (Synchronization)
멀티스레드 환경에서 여러 스레드가 동시에 공유 자원에 접근할 경우, 데이터의 일관성을 유지하기 위해 스레드 동기화가 필요합니다. 자바는
synchronized
키워드를 통해 동기화된 블록이나 메서드를 제공하여 한 번에 하나의 스레드만 공유 자원에 접근할 수 있게 합니다.
synchronized
블록 예제
class Counter {
private int count = 0;
// 동기화된 메서드
public synchronized void increment() {
count++;
}
public int getCount() {
return count;
}
}
public class Main {
public static void main(String[] args) throws InterruptedException {
Counter counter = new Counter();
Thread thread1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
counter.increment();
}
});
Thread thread2 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
counter.increment();
}
});
thread1.start();
thread2.start();
thread1.join(); // 스레드가 종료될 때까지 대기
thread2.join();
System.out.println("최종 카운트 값: " + counter.getCount()); // 출력: 2000
}
}
synchronized
키워드: 이 키워드는 한 번에 하나의 스레드만 해당 블록이나 메서드에 접근할 수 있게 보장합니다.join()
메서드: 이 메서드는 호출한 스레드가 종료될 때까지 기다립니다.
6. sleep()
메서드
Thread.sleep()
메서드는 스레드를 일정 시간 동안 멈추게 합니다. 이 메서드는 스레드를 지정한 시간 동안 "휴식"
상태로 만들고, 그 시간이 지나면 다시 실행됩니다.
sleep()
메서드 예제
public class Main {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
for (int i = 0; i < 5; i++) {
System.out.println("Thread: " + i);
try {
Thread.sleep(1000); // 1초 동안 스레드를 멈춤
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread.start();
}
}
출력 예시:
Thread: 0
(1초 대기)
Thread: 1
(1초 대기)
...
7. join()
메서드
join()
메서드는 한 스레드가 다른 스레드의 작업이 끝날 때까지 대기하도록 만듭니다. 예를 들어, 두 개의 스레드 중 하나가 다른 스레드가 종료될 때까지
기다려야 하는 경우 유용합니다.
join()
메서드 예제
public class Main {
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(() -> {
for (int i = 0; i < 5; i++) {
System.out.println("Thread 1: " + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread thread2 = new Thread(() -> {
for (int i = 0; i < 5; i++) {
System.out.println("Thread 2: " + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread1.start();
thread1.join(); // thread1이 끝날 때까지 thread2 대기
thread2.start();
}
}
8. 스레드 간 통신 (wait
, notify)
자바에서 스레드 간의 통신은 wait()
, notify()
,
notifyAll()
메서드를 통해 이루어집니다. 이 메서드들은 스레드를 일시적으로 대기 상태로 만들거나, 대기 중인 스레드를 깨우는 역할을 합니다. 이
기능은 주로 생산자-소비자 문제와 같은 상황에서 유용합니다.
wait()
와 notify()
예제
class Message {
private String message;
public synchronized void writeMessage(String msg) {
message = msg;
notify(); // 대기 중인 스레드를 깨움
}
public synchronized String readMessage() throws InterruptedException {
wait(); // 메시지가 작성될 때까지 대기
return message;
}
}
public class Main {
public static void main(String[] args) {
Message message = new Message();
// 소비자 스레드 (메시지 읽기)
Thread reader = new Thread(() -> {
try {
String msg = message.readMessage();
System.out.println("읽은 메시지: " + msg);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
// 생산자 스레드 (메시지 쓰기)
Thread writer = new Thread(() -> {
try {
Thread.sleep(2000); // 2초 대기 후 메시지 작성
message.writeMessage("Hello, World!");
} catch (InterruptedException e) {
e.printStackTrace();
}
});
reader.start();
writer.start();
}
}
출력 예시:
(2초 대기)
읽은 메시지: Hello, World!
요약
- 자바에서 멀티스레딩은
Thread
클래스를 상속하거나,Runnable
인터페이스를 구현하여 사용할 수 있습니다. - 스레드는 **동기화(synchronized)**를 통해 공유 자원에 대한 동시 접근을 제어할 수 있으며, 데이터 일관성을 유지할 수 있습니다.
sleep()
,join()
,wait()
,notify()
등의 메서드를 사용해 스레드 간의 실행 순서를 제어하거나 대기 상태를 관리할 수 있습니다.- 스레드 동기화는 스레드 안전성을 보장하지만, 잘못된 사용은 **교착 상태(Deadlock)**를 유발할 수 있으므로 주의가 필요합니다.
자바에서 멀티스레딩을 적절하게 활용하면 응답성 향상, 성능 최적화 등 여러 이점을 얻을 수 있습니다.