Open stemmmm opened 2 months ago
중요한 사실) 프로세스 자체는 운영체제의 스케줄러에 의해 직접 실행되지 않으며, 스레드가 실행됨
func main()
start()
메서드에 의해 자동으로 호출됨class CustomThread: Thread {
override func main() {
for i in 1...5 {
print("스레드 작업: \(i)")
}
}
}
let thread = CustomThread()
thread.start()
/*
스레드 작업: 1
스레드 작업: 2
스레드 작업: 3
스레드 작업: 4
스레드 작업: 5
*/
Thread.sleep(forTimeInterval: )
class CustomThread: Thread {
private let maxCount = 5
override func main() {
for i in 1...maxCount {
print("스레드 작업: \(i)")
if i == 3 {
print("스레드 5초 대기")
Thread.sleep(forTimeInterval: 5)
}
}
print("작업 완료")
}
}
let thread = CustomThread()
thread.start()
/*
스레드 작업: 1
스레드 작업: 2
스레드 작업: 3
스레드 5초 대기
스레드 작업: 4
스레드 작업: 5
작업 완료
*/
isCancelled
프로퍼티를 통해 취소 상태를 확인 가능class CustomThread: Thread {
private let maxCount = 10
override func main() {
for i in 1...maxCount {
if self.isCancelled {
print("스레드가 취소 요청을 받아서 종료합니다.")
break
}
print("스레드 작업: \(i)")
Thread.sleep(forTimeInterval: 1)
}
print("작업 완료")
}
}
let thread = CustomThread()
thread.start()
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
print("메인스레드에서 취소 요청")
thread.cancel()
}
/*
스레드 작업: 1
스레드 작업: 2
메인스레드에서 취소 요청
스레드가 취소 요청을 받아서 종료합니다.
작업 완료
*/
class CustomThread: Thread {
private let maxCount = 10
override func main() {
for i in 1...maxCount {
print("스레드 작업: \(i)")
if i == 5 {
print("작업 강제 종료")
Thread.exit()
}
}
print("작업 완료")
}
deinit {
print(#function)
}
}
let thread = CustomThread()
thread.start()
/*
스레드 작업: 1
스레드 작업: 2
스레드 작업: 3
스레드 작업: 4
스레드 작업: 5
작업 강제 종료
*/
DispatchSemaphore
class SemaphoreDemo {
private let semaphore = DispatchSemaphore(value: 1)
func performTask() {
print("\(Thread.current)가 접근 시도")
semaphore.wait() // 대기
print("\(Thread.current)가 작업 시작")
Thread.sleep(forTimeInterval: 2) // 작업 수행
print("\(Thread.current)가 작업 완료")
semaphore.signal() // 신호를 보냄으로써 다른 스레드의 접근을 허용
}
}
let demo = SemaphoreDemo()
let thread1 = Thread {
demo.performTask()
}
let thread2 = Thread {
demo.performTask()
}
thread1.start()
thread2.start()
/*
<NSThread: 0x600001725180>{number = 8, name = (null)}가 접근 시도
<NSThread: 0x6000017254c0>{number = 9, name = (null)}가 접근 시도
<NSThread: 0x6000017254c0>{number = 9, name = (null)}가 작업 시작
<NSThread: 0x6000017254c0>{number = 9, name = (null)}가 작업 완료
<NSThread: 0x600001725180>{number = 8, name = (null)}가 작업 시작
<NSThread: 0x600001725180>{number = 8, name = (null)}가 작업 완료
*/
Thread
클래스를 상속해 스레드를 정의할 수 있음run
메서드에 스레드가 실행할 코드를 정의하면 됨start
메서드를 반드시 호출해야함(run
메서드는 호출하면 안됨)public class MyThread extends Thread {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
public class Main {
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
}
}
Runnable
인터페이스를 구현해 스레드를 정의할 수 있으며, 익명 클래스나 람다를 사용할 수 있음Runnable
인터페이스의 구현체를 Thread
의 생성자로 전달하여 start
메서드를 실행하는 형태로 실행할 수 있음Thread
상속보다 Runnable
구현을 통해 스레드를 정의하는 방식이 권장됨
Thread
를 상속한 클래스는 다른 클래스를 상속받을 수 없어 유연성이 떨어지므로Runnable
객체를 공유할 수 있어 효율적인 자원 관리가 가능하므로public class Main {
public static void main(String[] args) {
Thread thread = new Thread(() -> System.out.println(Thread.currentThread().getName()));
thread.start();
}
}
start
하기 전 Thread
클래스의 daemon
필드를 true
로 설정해주면 데몬 스레드로 동작함join
join()
join()
메서드를 호출한 부모 스레드는 자식 스레드가 TERMINATED
상태가 될 때까지 WAITING
상태임TERMINATED
상태가 되면 부모 스레드는 다시 RUNNABLE
상태로 전환됨public class Join {
public static void main(String[] args) {
SumTask task1 = new SumTask(1, 50);
SumTask task2 = new SumTask(51, 100);
Thread thread1 = new Thread(task1);
Thread thread2 = new Thread(task2);
thread1.start();
thread2.start();
thread1.join(); // thread1 종료될 때까지 main 스레드 무한 대기
thread2.join(); // thread2 종료될 때까지 main 스레드 무한 대기
int totalSum = task1.result + task2.result;
System.out.println("total sum: " + totalSum);
}
static class SumTask implements Runnable {
int startValue;
int endValue;
int result = 0;
public SumTask(int startValue, int endValue) {
this.startValue = startValue;
this.endValue = endValue;
}
@Override
public void run() {
int sum = 0;
for (int i = startValue; i <= endValue; i++) {
sum += i;
}
result = sum;
}
}
}
join(ms)
join(ms)
메서드를 호출한 부모 스레드는 지정한 시간만큼 TIMED_WAITING
상태로 대기함RUNNABLE
상태로 전환됨public class Interrupt {
public static void main(String[] args) {
MyTask task = new MyTask();
Thread thread = new Thread(task);
thread.start();
Thread.sleep(100);
thread.interrupt();
System.out.println("스레드 인터럽트 상태1: " + Thread.currentThread().getState()); // true
}
static class MyTask implements Runnable {
@Override
public void run() {
while (!Thread.interrupted()) { // 스레드 인터럽트 상태 정상(false)으로 변경
System.out.println("작업 중...");
}
System.out.println("스레드 인터럽트 상태2: " + Thread.currentThread().getState()); // false
try {
Thread.sleep(1_000);
System.out.println("자원 정리 완료!");
} catch (InterruptedException e) {
System.out.println("인터럽트 발생하여 자원 정리 실패");
System.out.println("스레드 인터럽트 상태3: " + Thread.currentThread().getState());
}
}
}
}
yield
yield
메서드를 호출한 스레드는 RUNNABLE
상태를 유지하며 스케줄링 큐(대기 큐)로 들어감yield
메서드를 호출해도 계속 본인이 실행될 수 있음public class Yeild {
static final int THREAD_COUNT = 1_000; // 양보할 수 있도록 많은 스레드 생성
public static void main(String[] args) {
for (int i = 0; i < THREAD_COUNT; i++) {
Thread thread = new Thread(new MyTask());
thread.start();
}
}
static class MyTask implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + ": " + i);
Thread.yield();
}
}
}
}
다중 스레드 프로그래밍 - Producer and Consumer Problem
Java 에서는 ConcurrentLinkedQueue, CountDownLatch 등을 사용하여 스레드 간의 데이터를 공유하고, 일관성을 유지하도록 관리합니다. 여기서는 ConcurrentLinkedQueue 을 사용했습니다.
추가적으로 CountDownLatch 는 아래와 같이 사용하면 됩니다.
CountDownLatch producerLatch = new CountDownLatch(PRODUCER_THREAD_COUNT);
CountDownLatch consumerLatch = new CountDownLatch(CONSUMER_THREAD_COUNT);
CountDownLatch finishedLatch = new CountDownLatch(PRODUCER_THREAD_COUNT + CONSUMER_THREAD_COUNT);
producerLatch.await();
consumerLatch.await();
finishedLatch.await();
아래는 예시코드입니다.
@AllArgsConstructor
@Getter
public class Data {
private int num1;
private int num2;
private char operator;
}
@Slf4j
public class Consumer extends Thread {
private Queue<Data> queue;
public Consumer(Queue<Data> queue) {
this.queue = queue;
}
@Override
public void run() {
int cnt = 0;
while (cnt < 5) {
Data data = queue.poll();
if (data == null) {
try {
Thread.sleep(3000);
cnt++;
} catch (InterruptedException e) {
log.info("Data 에 값이 없습니다.");
e.printStackTrace();
}
} else {
int result = 0;
if (data.getOperator() == '+')
result = data.getNum1() + data.getNum2();
else if (data.getOperator() == '-')
result = data.getNum1() - data.getNum2();
log.info(data.getNum1() + " " + data.getOperator() + " " + data.getNum2() +
" = " + result);
cnt++;
}
}
}
}
@Slf4j
public class Producer extends Thread {
private Queue<Data> queue;
public Producer(Queue<Data> queue) {
this.queue = queue;
}
@Override
public void run() {
Random random = new Random();
int cnt = 0;
while (cnt < 5) {
int num1 = random.nextInt(10000);
int num2 = random.nextInt(10000);
char operator = random.nextBoolean() ? '+' : '-';
log.info("num1 : " + num1 + ", num2 : " + num2 + ", operator : " + operator);
Data data = new Data(num1, num2, operator);
queue.offer(data);
cnt++;
}
}
}
@Slf4j
public class Main {
private static final int PRODUCER_THREAD_COUNT = 4;
private static final int CONSUMER_THREAD_COUNT = 2;
private static final Queue<Data> queue = new ConcurrentLinkedQueue<>();
public static void main(String[] args) throws InterruptedException {
List<Producer> producerList = new ArrayList<>();
List<Consumer> consumerList = new ArrayList<>();
for (int i = 0; i < PRODUCER_THREAD_COUNT; i++) {
Producer producer = new Producer(queue);
producerList.add(producer);
producer.start();
producer.join();
}
for (int i = 0; i < CONSUMER_THREAD_COUNT; i++) {
Consumer consumer = new Consumer(queue);
consumerList.add(consumer);
consumer.start();
consumer.join();
}
log.info("작업 종료");
}
}
결과
22:46:46.259 [Thread-0] INFO com.example.demo.plusandminus.concurrent.Producer - num1 : 6042, num2 : 947, operator : -
22:46:46.264 [Thread-0] INFO com.example.demo.plusandminus.concurrent.Producer - num1 : 6424, num2 : 9308, operator : -
22:46:46.264 [Thread-0] INFO com.example.demo.plusandminus.concurrent.Producer - num1 : 6795, num2 : 1615, operator : +
22:46:46.264 [Thread-0] INFO com.example.demo.plusandminus.concurrent.Producer - num1 : 59, num2 : 3020, operator : +
22:46:46.264 [Thread-0] INFO com.example.demo.plusandminus.concurrent.Producer - num1 : 8810, num2 : 8627, operator : -
22:46:46.264 [Thread-1] INFO com.example.demo.plusandminus.concurrent.Producer - num1 : 7644, num2 : 9560, operator : +
22:46:46.264 [Thread-1] INFO com.example.demo.plusandminus.concurrent.Producer - num1 : 8588, num2 : 94, operator : -
22:46:46.264 [Thread-1] INFO com.example.demo.plusandminus.concurrent.Producer - num1 : 5790, num2 : 8704, operator : +
22:46:46.265 [Thread-1] INFO com.example.demo.plusandminus.concurrent.Producer - num1 : 1207, num2 : 6436, operator : +
22:46:46.265 [Thread-1] INFO com.example.demo.plusandminus.concurrent.Producer - num1 : 7600, num2 : 6645, operator : +
22:46:46.265 [Thread-2] INFO com.example.demo.plusandminus.concurrent.Producer - num1 : 6039, num2 : 2968, operator : +
22:46:46.265 [Thread-2] INFO com.example.demo.plusandminus.concurrent.Producer - num1 : 1763, num2 : 9907, operator : -
22:46:46.265 [Thread-2] INFO com.example.demo.plusandminus.concurrent.Producer - num1 : 1004, num2 : 6288, operator : +
22:46:46.265 [Thread-2] INFO com.example.demo.plusandminus.concurrent.Producer - num1 : 7891, num2 : 1750, operator : +
22:46:46.265 [Thread-2] INFO com.example.demo.plusandminus.concurrent.Producer - num1 : 4275, num2 : 3976, operator : +
22:46:46.265 [Thread-3] INFO com.example.demo.plusandminus.concurrent.Producer - num1 : 7064, num2 : 9714, operator : +
22:46:46.265 [Thread-3] INFO com.example.demo.plusandminus.concurrent.Producer - num1 : 8334, num2 : 59, operator : -
22:46:46.265 [Thread-3] INFO com.example.demo.plusandminus.concurrent.Producer - num1 : 271, num2 : 2286, operator : -
22:46:46.265 [Thread-3] INFO com.example.demo.plusandminus.concurrent.Producer - num1 : 47, num2 : 3478, operator : -
22:46:46.265 [Thread-3] INFO com.example.demo.plusandminus.concurrent.Producer - num1 : 7559, num2 : 1964, operator : +
22:46:46.283 [Thread-4] INFO com.example.demo.plusandminus.concurrent.Consumer - 6042 - 947 = 5095
22:46:46.283 [Thread-4] INFO com.example.demo.plusandminus.concurrent.Consumer - 6424 - 9308 = -2884
22:46:46.284 [Thread-4] INFO com.example.demo.plusandminus.concurrent.Consumer - 6795 + 1615 = 8410
22:46:46.284 [Thread-4] INFO com.example.demo.plusandminus.concurrent.Consumer - 59 + 3020 = 3079
22:46:46.284 [Thread-4] INFO com.example.demo.plusandminus.concurrent.Consumer - 8810 - 8627 = 183
22:46:46.284 [Thread-5] INFO com.example.demo.plusandminus.concurrent.Consumer - 7644 + 9560 = 17204
22:46:46.284 [Thread-5] INFO com.example.demo.plusandminus.concurrent.Consumer - 8588 - 94 = 8494
22:46:46.284 [Thread-5] INFO com.example.demo.plusandminus.concurrent.Consumer - 5790 + 8704 = 14494
22:46:46.284 [Thread-5] INFO com.example.demo.plusandminus.concurrent.Consumer - 1207 + 6436 = 7643
22:46:46.284 [Thread-5] INFO com.example.demo.plusandminus.concurrent.Consumer - 7600 + 6645 = 14245
22:46:46.284 [main] INFO com.example.demo.plusandminus.concurrent.Main - 작업 종료