자바 Java
9. 멀티스레딩과 동시성 프로그래밍
안녕하세요! 태마입니다.
Java 기초 강좌입니다.
강좌의 경우
1. 주제 간단 정리
2. 상세 주제 정리
으로 이루어져 있습니다.
자바 Java
포스팅 시작하겠습니다 :)
1. 주제 간단 정리
1. 멀티스레딩(Multithreading) 개념
멀티스레딩은 하나의 프로그램 내에서 여러 개의 스레드(Thread)가 동시에 실행되는 기법입니다.
✔ 여러 작업을 동시에 처리할 수 있어 프로그램의 성능과 효율성이 향상됩니다.
✔ 스레드(Thread) 는 프로그램 내에서 실행되는 작은 작업 단위로, 멀티스레딩은 여러 스레드가 동시에 실행되는 환경을 의미합니다.
📌 멀티스레딩의 장점
✔ 자원 활용 극대화 → CPU를 여러 스레드가 번갈아가며 사용
✔ 응답성 향상 → 사용자 입력 대기 중 다른 작업을 수행할 수 있음
✔ 대기 시간 감소 → I/O 작업을 처리하는 동안 다른 작업을 동시에 수행 가능
2. 스레드(Thread)와 프로세스(Process)의 차이
- 프로세스 : 실행 중인 프로그램 자체로, 각 프로세스는 독립된 메모리 공간을 가짐
- 스레드 : 프로세스 내에서 실행되는 작은 작업 단위로, 같은 메모리 공간을 공유
✅ 스레드와 프로세스 비교
특징프로세스스레드
메모리 공간 | 독립적인 메모리 공간 | 같은 메모리 공간 공유 |
속도 | 느림 | 빠름 |
자원 | 독립적 | 프로세스 자원 공유 |
📌 스레드는 같은 프로세스 내에서 자원을 공유하므로 프로세스보다 빠르고 효율적
3. 스레드 생성 방법
Java에서는 Thread 클래스 또는 Runnable 인터페이스를 사용하여 스레드를 생성할 수 있습니다.
✅ Thread 클래스 사용 예제
class MyThread extends Thread {
public void run() {
System.out.println("스레드 실행 중");
}
}
public class ThreadExample {
public static void main(String[] args) {
MyThread thread = new MyThread();
thread.start(); // 스레드 실행
}
}
📌 Thread 클래스는 run() 메서드를 오버라이드하여 스레드에서 실행할 코드를 작성
4. Runnable 인터페이스 사용 예제
Runnable 인터페이스를 구현하여 스레드를 생성할 수 있습니다.
✔ Runnable을 사용하면 다중 상속을 지원하기 때문에 클래스 상속과 함께 사용할 수 있습니다.
class MyRunnable implements Runnable {
public void run() {
System.out.println("스레드 실행 중");
}
}
public class RunnableExample {
public static void main(String[] args) {
MyRunnable task = new MyRunnable();
Thread thread = new Thread(task);
thread.start(); // 스레드 실행
}
}
📌 Runnable 인터페이스는 스레드의 작업을 정의하는 데 사용되며, Thread 클래스와 함께 실행
2. 상세 주제 정리
1. 동시성 프로그래밍(Concurrency)
동시성 프로그래밍은 여러 작업을 동시에 실행하는 방법입니다.
✔ **병렬 처리(Parallelism)**과 혼동할 수 있지만, 동시성은 여러 작업이 겹쳐서 진행되는 개념이며, 병렬 처리란 작업들이 진짜로 동시에 실행되는 것을 의미합니다.
✅ 동시성 프로그래밍 예제
public class ConcurrencyExample {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 5; i++) {
System.out.println("작업 1");
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 5; i++) {
System.out.println("작업 2");
}
});
t1.start(); // 스레드 1 실행
t2.start(); // 스레드 2 실행
}
}
📌 동시성 프로그래밍을 사용하면 여러 작업을 동시에 처리할 수 있지만, 순서 보장이 어려움
2. Executor 프레임워크
Executor는 스레드 풀을 사용하여 스레드를 효율적으로 관리할 수 있는 Java의 동시성 처리 프레임워크입니다.
✔ 스레드 풀을 사용하면 스레드를 미리 생성하고 재사용하여 성능을 최적화할 수 있습니다.
✔ ExecutorService 인터페이스를 사용하여 스레드를 제출하고 관리할 수 있습니다.
✅ Executor 예제
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ExecutorExample {
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(2); // 2개의 스레드 풀
executor.submit(() -> {
System.out.println("작업 1");
});
executor.submit(() -> {
System.out.println("작업 2");
});
executor.shutdown(); // 스레드 풀 종료
}
}
📌 **Executors.newFixedThreadPool(n)**은 n개의 스레드를 미리 생성하여 재사용합니다.
3. ExecutorService의 다양한 활용
ExecutorService는 스레드를 관리하고 다양한 작업을 처리할 수 있는 방법을 제공합니다.
✔ submit() : 작업을 큐에 제출
✔ invokeAll() : 여러 작업을 한 번에 실행하고 결과를 기다림
✔ invokeAny() : 여러 작업 중 가장 먼저 완료된 작업의 결과를 반환
✅ ExecutorService 활용 예제
import java.util.concurrent.*;
public class ExecutorServiceExample {
public static void main(String[] args) throws InterruptedException, ExecutionException {
ExecutorService executor = Executors.newFixedThreadPool(2);
Callable<String> task1 = () -> {
Thread.sleep(1000);
return "작업 1 완료";
};
Callable<String> task2 = () -> {
Thread.sleep(500);
return "작업 2 완료";
};
// 여러 작업을 동시에 처리하고 결과를 기다림
List<Future<String>> results = executor.invokeAll(Arrays.asList(task1, task2));
for (Future<String> result : results) {
System.out.println(result.get());
}
executor.shutdown();
}
}
📌 invokeAll()은 여러 작업을 동시에 실행하고, 모든 작업이 완료될 때까지 기다림.
4. 동기화(Synchronization) 및 경쟁 상태(Race Condition)
동시성 프로그래밍에서 여러 스레드가 같은 자원에 동시에 접근하면 경쟁 상태가 발생할 수 있습니다.
✔ 이를 해결하려면 **동기화(synchronization)**를 사용하여 한 번에 하나의 스레드만 자원에 접근할 수 있도록 해야 합니다.
✅ 동기화 예제
class Counter {
private int count = 0;
// synchronized 키워드로 메서드 동기화
public synchronized void increment() {
count++;
}
public int getCount() {
return count;
}
}
public class SyncExample {
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 출력
}
}
📌 synchronized 키워드를 사용하면 하나의 스레드만 해당 메서드를 실행하게 되어 경쟁 상태를 방지
✅ 지금까지 멀티스레딩과 동시성 프로그래밍에 대해 배웠어요!
👉 "이제 입출력 스트림과 파일 처리에 대해 배우고 싶다면?"
✅ 다음 회차에서 Java 입출력 스트림을 다뤄봅시다!
'IT Developer > Java' 카테고리의 다른 글
Java 기초 <13. Java 18~현재 최신 트렌드 – Virtual Threads, GraalVM, CRaC> (1) | 2025.03.14 |
---|---|
Java 기초 <12. Java 5~17 주요 기능 변화 (제네릭, 스트림, 람다, 레코드 등)> (1) | 2025.03.13 |
Java 기초 <11. Java 네트워크 프로그래밍 – Socket, HTTPClient, WebSocket> (0) | 2025.03.12 |
Java 기초 <10. Java 입출력 스트림 (I/O) 기본과 활용> (1) | 2025.03.11 |
Java 기초 <8. 예외 처리와 사용자 정의 예외> (0) | 2025.03.09 |
Java 기초 <7. 컬렉션 프레임워크 (List, Set, Map) 활용 및 성능 비교> (2) | 2025.03.08 |
Java 기초 <6. 인터페이스와 추상 클래스의 차이> (0) | 2025.03.07 |
Java 기초 <5. 객체지향 4대 원칙 (캡슐화, 상속, 다형성, 추상화)> (0) | 2025.03.06 |