
main() 코드 실행 후 return을 만나면 종료.public class MainThreadExample {
public static void main(String[] args) {
Thread worker = new Thread(() -> {
for (int i = 0; i < 5; i++) {
System.out.println("작업 스레드 실행 중: " + i);
try { Thread.sleep(500); } catch (InterruptedException e) {}
}
});
worker.start();
System.out.println("메인 스레드 종료"); // 메인 스레드가 끝나도 worker가 남아있음
}
}
멀티스레드로 동작하는 프로그램을 개발하려면, 먼저 몇 개의 작업을 병렬로 실행할지 결정하고 작업별로 스레드를 생성해야 한다.
자바 프로그램은 항상 메인 스레드가 존재하므로, 메인 작업 이외에 실행할 추가 작업의 수만큼 스레드를 생성하면 된다.
자바에서는 작업 스레드도 객체로 관리되므로, 스레드를 정의하려면 Runnable을 구현하거나 Thread를 상속하는 등의 클래스(혹은 익명 구현)가 필요하다.
Thread + Runnable 사용class Task implements Runnable {
@Override
public void run() {
System.out.println("작업 스레드 실행!");
}
}
public class ThreadExample {
public static void main(String[] args) {
Runnable task = new Task();
Thread thread = new Thread(task);
thread.start(); // run() 직접 호출이 아니라 start()로 실행
}
}
익명 클래스 활용:
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("익명 Runnable 실행!");
}
});
thread.start();
람다식 활용 (자주 사용됨):
Thread thread = new Thread(() -> {
System.out.println("람다식으로 실행!");
});
thread.start();
Thread 클래스 상속class MyThread extends Thread {
@Override
public void run() {
System.out.println("Thread 상속 실행!");
}
}
public class ExtendThreadExample {
public static void main(String[] args) {
Thread t = new MyThread();
t.start();
}
}
main, Thread-0, Thread-1 …setName("이름")Thread.currentThread()public class ThreadNameExample {
public static void main(String[] args) {
Thread mainThread = Thread.currentThread();
System.out.println("메인 스레드 이름: " + mainThread.getName());
Thread worker = new Thread(() -> {
System.out.println("작업 스레드 실행: " +
Thread.currentThread().getName());
});
worker.setName("Worker-1");
worker.start();
}
}
스레드의 주요 상태:
new Thread())start() 호출 시 진입sleep(), join(), wait()일시 정지
sleep(long millis) → 지정 시간 동안 정지 후 자동 대기 상태join() → 특정 스레드 종료까지 대기wait() → 동기화 블록 내 정지재실행
interrupt() → InterruptedException 발생시켜 복귀notify(), notifyAll() → wait() 상태 해제양보
yield() → 실행 상태 → 실행 대기 상태 전환sleep() & join()public class JoinExample {
public static void main(String[] args) {
Thread sumThread = new Thread(() -> {
int sum = 0;
for (int i = 1; i <= 5; i++) {
sum += i;
try { Thread.sleep(500); } catch (InterruptedException e) {}
}
System.out.println("합계: " + sum);
});
sumThread.start();
try {
sumThread.join(); // sumThread가 끝날 때까지 main 대기
} catch (InterruptedException e) {}
System.out.println("메인 스레드 종료");
}
}
synchronized 키워드로 동기화 메소드 또는 동기화 블록을 제공.public class SharedCounter {
private int count = 0;
public synchronized void increment() {
count++;
}
public int getCount() {
return count;
}
}
public class SyncMethodExample {
public static void main(String[] args) throws InterruptedException {
SharedCounter counter = new SharedCounter();
Runnable task = () -> {
for (int i = 0; i < 1000; i++) {
counter.increment();
}
};
Thread t1 = new Thread(task);
Thread t2 = new Thread(task);
t1.start(); t2.start();
t1.join(); t2.join();
System.out.println("최종 카운트: " + counter.getCount()); // 항상 2000
}
}
public void add(int value) {
// 여러 스레드가 접근 가능한 영역
synchronized (this) {
// 단 하나의 스레드만 실행 가능
count += value;
}
// 다시 여러 스레드 접근 가능
}
wait(): 현재 스레드를 일시 정지 상태로 보냄.notify(): 대기 중인 다른 스레드 하나를 깨움.class WorkObject {
public synchronized void methodA() {
System.out.println("ThreadA 실행");
notify(); // 상대 스레드 깨움
try { wait(); } catch (InterruptedException e) {}
}
public synchronized void methodB() {
System.out.println("ThreadB 실행");
notify();
try { wait(); } catch (InterruptedException e) {}
}
}
public class WaitNotifyExample {
public static void main(String[] args) {
WorkObject work = new WorkObject();
Thread threadA = new Thread(() -> {
for (int i = 0; i < 5; i++) work.methodA();
});
Thread threadB = new Thread(() -> {
for (int i = 0; i < 5; i++) work.methodB();
});
threadA.start();
threadB.start();
}
}
스레드는 run() 종료 시 자동으로 끝남. 하지만 즉시 멈춰야 할 경우 안전 종료가 필요.
stop() 메소드는 위험하므로 사용하지 않음.
public class SafeStopThread extends Thread {
private volatile boolean stop = false;
public void setStop(boolean stop) {
this.stop = stop;
}
@Override
public void run() {
while (!stop) {
System.out.println("스레드 실행 중...");
}
System.out.println("자원 정리 후 종료");
}
public static void main(String[] args) throws InterruptedException {
SafeStopThread thread = new SafeStopThread();
thread.start();
Thread.sleep(1000);
thread.setStop(true);
}
}
public class InterruptExample {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
try {
while (true) {
System.out.println("작업 중...");
Thread.sleep(200); // 일시 정지 상태
}
} catch (InterruptedException e) {
System.out.println("인터럽트 발생, 안전 종료");
}
});
thread.start();
try { Thread.sleep(1000); } catch (InterruptedException e) {}
thread.interrupt(); // 강제 종료 유도
}
}
public class DaemonExample {
public static void main(String[] args) {
Thread autoSave = new Thread(() -> {
while (true) {
System.out.println("자동 저장 실행...");
try { Thread.sleep(500); } catch (InterruptedException e) {}
}
});
autoSave.setDaemon(true); // 데몬 스레드 지정
autoSave.start();
try { Thread.sleep(2000); } catch (InterruptedException e) {}
System.out.println("메인 종료 → 데몬 스레드도 종료됨");
}
}
java.util.concurrent 패키지에서 ExecutorService 인터페이스와 Executors 클래스를 제공하여 스레드 풀을 쉽게 생성할 수 있다. Executors의 정적 메소드를 사용하면 간단히 ExecutorService 구현 객체를 만들 수 있다. ExecutorService의 다음 메소드 중 하나를 호출해야 한다. shutdown() : 현재 처리 중인 작업과 큐에 대기 중인 모든 작업을 마친 후 스레드 풀 종료 shutdownNow() : 현재 실행 중인 작업을 인터럽트하여 강제로 종료하고, 큐에 남아 있는 미처리 작업 목록을 반환 shutdown()을, 강제 종료할 경우에는 shutdownNow()를 호출하면 된다. Runnable 또는 Callable 구현 객체로 표현할 수 있다. Runnable은 작업 완료 후 반환값이 없으며, Callable은 작업 완료 후 결과 값을 반환한다. Callable의 반환 타입은 Callable<T>에서 지정한 T 타입과 동일해야 한다. ExecutorService의 작업 큐에 Runnable 또는 Callable 객체를 넣는 행위를 의미한다.import java.util.concurrent.*;
public class ThreadPoolExample {
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(3);
// Runnable 작업 제출
executor.execute(() -> {
System.out.println("Runnable 작업 실행: " + Thread.currentThread().getName());
});
// Callable 작업 제출
Future<Integer> future = executor.submit(() -> {
int sum = 0;
for (int i = 1; i <= 5; i++) sum += i;
return sum;
});
try {
System.out.println("Callable 결과: " + future.get());
} catch (Exception e) { e.printStackTrace(); }
executor.shutdown(); // 남은 작업 끝내고 종료
}
}