JAVA

스레드와 Yield

silver-w 2024. 11. 18. 23:02

여러 스레드가 동시에 접근하는 경우 동시성 컬렉션{ java.util.concurrent 패키지} 및 동시성 변수 volatile을 사용하는 것이 안전하다. 

 

ConcurrentLinkedQueue : QUEUE 상속, concuurent 패키지 / 여러 스레드에 의해 자료구조가 수정될 수 있음

volatile : 동시에 실행되는 여러 스레드에 의해 필드가 수정될 수 있음을 나타냄

package thread.control.printer;

import java.util.Queue;
import java.util.Scanner;
import java.util.concurrent.ConcurrentLinkedQueue;

import static util.MyLogger.log;
import static util.ThreadUtils.sleep;

public class MyPrinterV2 {

    public static void main(String[] args) {
        Printer printer = new Printer();
        Thread printerThread = new Thread(printer, "printer");
        printerThread.start();
        Scanner userInput = new Scanner(System.in);
        while(true) {
            log("프린터할 문서를 입력하세요. 종료 (q) : ");
            String input = userInput.nextLine();
            if (input.equals("q")) {
                printerThread.interrupt();
                break;
            }
            printer.addJob(input);
        }
    }
    
    public void addJob(String input) {
            jobQueue.offer(input);
    }
    
    static class Printer implements Runnable {
        Queue<String> jobQueue = new ConcurrentLinkedQueue<>();
        // 여러 스레드가 동시에 접근하는 경우에는 
        // 컬렉션 프레임워크에서 제공하는 자료구조는 안전하지 않다. 
        // 동시성 컬렉션 사용
        // Queue의 경우 ConcurrentLinkedQueue가 안전하다.

        @Override
        public void run() {
            while(!Thread.interrupted()) {
                if(jobQueue.isEmpty()) {
                    continue;
                }
                try {
                    String job = jobQueue.poll();
                    log("출력 시작 : " + job + ", 대기 문서 : " + jobQueue);
                    Thread.sleep(3000);
                    log("출력완료");
                } catch (InterruptedException e) {
                    log("인터럽트");
                    break;
                }
            }
            log("프린터 종료");
        }
    }
}

 

 

[ Thread.yield() ]

sleep을 지정한 경우는 CPU실행이 잠깐 멈추고, timed-waiting 상태로 들어가는 반면,

Thread.yield()로 설정된 스레드는 스케쥴링내에서 후순위로 지정되어,

다른 스레드가 더 선순위에 올라갈 수 있도록 양보함 ( 양보할 스레드가 없으면 양보 하지 않고 실행)

예로 들어 반복적인 실행을 위해 반복문을 포함하는 경우가 많은데, 이 반복문들이 무의미한 반복을 하는 경우에 사용

※ Thread.join()은 다른 스레드를 아예 기다리는 것

※ Runnable : Ready(CPU 실행될 수 있는 스케쥴링 대기) + Running(CPU에서 실행)

package thread.control.yield;

import java.util.Queue;
import java.util.Scanner;
import java.util.concurrent.ConcurrentLinkedQueue;

import static util.MyLogger.log;

public class MyPrinterV3 {

    public static void main(String[] args) {
        Printer printer = new Printer();
        Thread printerThread = new Thread(printer, "printer");
        printerThread.start();
        Scanner userInput = new Scanner(System.in);
        while(true) {
            log("프린터할 문서를 입력하세요. 종료 (q) : ");
            String input = userInput.nextLine();
            if (input.equals("q")) {
                printerThread.interrupt();
                break;
            }
            printer.addJob(input);
        }
    }

    static class Printer implements Runnable {
        Queue<String> jobQueue = new ConcurrentLinkedQueue<>();

        @Override
        public void run() {
            while(!Thread.interrupted()) {
                if(jobQueue.isEmpty()) {
                    Thread.yield(); 
                    // 추가 해당 if문의 Runnable 우선순위로 
                    // 뒤로 넘겨서 CPU 자원을 덜 쓸 수 있다.
                    continue;
                }
                try {
                    String job = jobQueue.poll();
                    log("출력 시작 : " + job + ", 대기 문서 : " + jobQueue);
                    Thread.sleep(3000);
                    log("출력완료");
                } catch (InterruptedException e) {
                    log("인터럽트");
                    break;
                }
            }
            log("프린터 종료");
        }

        public void addJob(String input) {
            jobQueue.offer(input);
        }
    }
}