개념

[Java] Thread - 바쁜 대기, Method이해하기, 주기적 실행

디벨로펄 2023. 10. 20.
반응형
권한을 얻을 때까지 확인한다.
Busy Waiting


주기적인 실행, 특정 시간 이후 실행 등의 작업을 하고 싶을 때는

Timer, TimerTask 같은 클래스를 활용도 고려해보자~ 

(java.util 에서 제공한다.)

동기화 문제와 바쁜 대기를 해결하여 구현되어 있다.

 

 

최근 Web으로 interactive하게 편집할 수 있는 편집기를 프로젝트로 진행하고 있다.

미리캔버스, Figma 등과 같은 느낌이라고 보면 될 것이다.

편집기에는 임시저장이 필수인데, 이를 구현하다보니

while을 사용한 무한루프로 특정 시간(10분)이 지날때마다 임시저장을 하도록 구현했다.

Thread threadA = new Thread(()->{
    while(flag){
        if(LocalDateTime.now().getSecond()%5==0){
            System.out.println("저장!" + LocalDateTime.now());
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
});

Thread.sleep(1000)으로 1초씩 기다리게 했지만, 결국 5초에 한 번만 발생하는 일이 5번발생하게 된다.

-> IDE에서 바쁜 대기 상태일 수도 있다고 경고를 띄워준다.

 

이러한 상황을 바쁜 대기 상태라 한다.

바쁜대기
원하는 자원을 얻기 위해 기다리는 것이 아니라 권한을 얻을 때까지 확인하는 을 의미합니다.

 

BusyWaiting기법이 필요한 경우

  • 자원 얻는데 소모되는 시간이 적을 경우
  • Context Switching 비용보다 성능적으로 더 우수한 상황인 경우
  • 공유 자원에 대한 권한 획득이 아주 빠른 시간 내에 이루어질 수 있다는 확신이 있는 상황 또는 뮤텍스세마포어 등의 동기화 객체등을 이용하기에는 그 오버헤드가 큰 상황에서 간단히 쓸 수 있다.(출처 : 위키)

단점 : CPU가 낭비된다

 

 

이 단점을 해결할 수 있는 방법을 알아봅시다.

  • sleep : 시간 예측이 가능한 경우~ 
  • wait, notify : thread를 재우고, 깨우고
  • join메서드 : 실행 순서를 보장한다.

 

해결책 - sleep : 시간 예측이 가능한 경우~ 
  • 시간 예측이 가능한 경우 sleep()을 활용하여, 현재 thread가 불필요한 동작을 하지 않도록 blocked상태로 만들어준다.
Thread threadA = new Thread(()->{
    while(flag){
        System.out.println("저장!" + LocalDateTime.now());
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
});

매 5초마다 실행하는 프로그램은 위와 같이도 설정이 가능하다.

 

해결책 - wait, notify : thread를 재우고, 깨우고

wait()은 java의 최상위 객체 타입인 Object에 포함되어 있는 method이다.

  • wait : 현재 실행 중인 Thread를 일시정지(Blocked) 상태로 변경한다. wait()을 호출한 thread를 중지시킴
    -> 아래 코드에서 waitThread.wait()을 호출한 mainThread가 정지 상태로 변경된다.
    -> wait 메서드는 동기화 블록 내에서만 사용이 가능하다.
  • notify : wait하고 있는 thread를 깨운다.
public static void main(String[] args) {
    WaitThread waitThread = new WaitThread();
    waitThread.start(); //스레드 실행

    // 동기화 블록
    synchronized (waitThread) {
        System.out.println("동기화블록 진입");
        try {
            // 동기화 시켜놓고 waitThread wait호출 -> mainThread가 중지됨
            //-> wait메서드를 호출한 객체가 실행되는 thread를 중지시키는 것(=mainThread)
            System.out.println("wait~!!");
            waitThread.wait();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

        System.out.println("종료");
    }
}

- 10초 동안 쉬고, notify가 호출되면서 mainThread가 깨어난다.

public static class WaitThread extends Thread {
    @Override
    public void run() {
        synchronized (this) {
            try {
                System.out.println("sleep 10초");
                Thread.sleep(10000);
                System.out.println("notify!");
                this.notify();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

실행흐름

 

main코드 실행 console

동기화블록 진입
wait~!!
sleep 10 
notify!
종료

 

공부하는 김에 join도 알아보자

Thread - join메서드 : 실행 순서를 보장한다.

다음 코드를 실행시키면 이미 실행시킨 thread가 실행 중인 상태에서 MainThread가 먼저 종료된 것을 확인할 수 있다.

join은 join메서드를 호출한(main Thread) join대상 thread가 종료 될 때까지 기다린다.

public static void main(String[] args) {
    for(int i=0; i<5; i++){
        Thread thread = new JoinThread();
        thread.setName("join_"+i);
        thread.start();
    }
    System.out.println("MainThread End");
}
MainThread End
join_1 is Processing : 2023-10-20T21:43:56.075742600
join_3 is Processing : 2023-10-20T21:43:56.075742600
join_2 is Processing : 2023-10-20T21:43:56.075742600
join_0 is Processing : 2023-10-20T21:43:56.075742600
join_4 is Processing : 2023-10-20T21:43:56.075742600
join_1 is Terminated : 2023-10-20T21:43:57.085685400
join_2 is Terminated : 2023-10-20T21:43:57.085685400
join_3 is Terminated : 2023-10-20T21:43:57.085685400
join_0 is Terminated : 2023-10-20T21:43:57.099547700
join_4 is Terminated : 2023-10-20T21:43:57.099547700

위 코드를 실행하면 아래와 같은 흐름으로 실행된다.

start() 메서드를 통해 실행된 Thread 사이에는 순서를 보장할 수 없다. 

뭐가 먼저 실행될 지 모른다는 얘기...

join메서드가 포함된 실행코드 : 실행 순서를 보장할 수 있다.

public static void main(String[] args) {
    for(int i=0; i<5; i++){
        Thread thread = new JoinThread();
        thread.setName("join_"+i);
        thread.start();
        try {
            thread.join();
        }catch (InterruptedException interruptedException){
            interruptedException.printStackTrace();
        }
    }
    System.out.println("MainThread End");
}

join이 있을 때 : 위 코드의 실행 순서는 다음과 같다. 

 

 

 

 

추가 공부 필요

  • 뮤텍스, 세마포어
  • lock
  • thread 세부
  • Timer, TimerTask
반응형

댓글