[Java] 멀티스레딩 - 스레드 조정

2024. 10. 25. 22:49·백엔드/Java

 

스레드 조정이란, 하나의 스레드를 다른 스레드에서 제어(시작 or 종료)하는 작업 이다.

 

 

스레드 종료


 

 

첫번째로 다뤄볼 조정은 스레드 종료 이다.

 

 

스레드 종료 로직이 따로 필요한 이유는 크게 3가지 이다.

 

  1. 스레드는 고정된 리소스(메모리, 커널)를 할당받아야 하고, CPU 시간과 캐시 공간도 사용한다. 따라서 생성한 스레드가 이미 작업을 완료했는데 애플리케이션이 작동중이라면 사용하지 않는 스레드는 정리되어야 하는 것이다.

  2. 스레드가 작업 도중 응답이 없는 외부 서버에 요청을 계속 보내거나, 내부 문제로 정상적으로 동작하지 않는다면 해당 스레드를 강제로 중지시켜야 한다.

  3. 애플리케이션 전체를 중지하기 위해서이다. 만약 애플리케이션 내부의 스레드 중, 하나라도 실행되고 있다면 애플리케이션은 종료되지 않는다. 메인 스레드가 종료되었어도, 다른 일반 스레드가 실행되고 있다면 애플리케이션은 종료되지 않는다.
    결국 “애플리케이션 종료 = 모든 스레드의 종료” 인 것이다.

 

Thread.interrupt()

 

Thread를 interrupt, 말 그대로 정지시키는 명령어이다.

public class Main {

    public static void main(String [] args) {
        Thread thread = new Thread(new BlockingTask());
        thread.setName("interruptedThread");
        thread.start();
        thread.interrupt();
    }

    private static class BlockingTask implements Runnable {
        @Override
        public void run() {
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                System.out.println("interrupt로 인한 스레드 종료");
            }
        }
    }
}

 

메인 스레드가 실행시킨 interruptedThread 스레드를 interrupt() 메서드를 사용해 종료하는 코드이다.

 

 

thread.interrupt() 메서드가 실행되면 InterruptedException이 발생하고 BlockingTask.run() 메서드의 catch가 해당 예외를 처리한다.

 

 

 

 

Thread.setDaemon()

 

데몬이 설정된 스레드는 백그라운드에서 실행되는 스레드로, 애플리케이션 종료를 막지 않는 스레드이다.

 

 

만약 메인 스레드, 일반 스레드가 종료되고 데몬 스레드만 종료되지 않은 경우엔 애플리케이션이 종료되는 것이다.

 

 

데몬 스레드를 사용해 처리하기 좋은 작업은 크게 두 가지 있다.

  1. 백그라운드 작업 (자동저장, 주기적인 리소스 정리(예: 캐시 청소), 로깅, 모니터링)
  2. 외부 라이브러리를 호출하는 작업
    • 왜나하면 외부에선 interrupt 신호를 처리할 방법이 없기 때문이다.

 

public class Main {

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new LongComputationTask(new BigInteger("10000000"), new BigInteger("10000000")));
        thread.setDaemon(true); // 메인 스레드 종료 시 함께 종료될 데몬 스레드로 설정
        thread.start();
        thread.interrupt(); // 작업중인 계승 스레드에게 중지 요청
    }

    private static class LongComputationTask implements Runnable {

        private BigInteger base;
        private BigInteger power;

        public LongComputationTask(BigInteger base, BigInteger power) {
            this.base = base;
            this.power = power;
        }

        @Override
        public void run() {
            System.out.println(base + "^" + power + " = " + pow(base, power));
        }

        private BigInteger pow(BigInteger base, BigInteger power) {
            BigInteger result = BigInteger.ONE;

            for (BigInteger i = BigInteger.ZERO; i.compareTo(power) != 0; i = i.add(BigInteger.ONE)) {
                result = result.multiply(base);
            }

            return result;
        }
    }
}

 

위의 코드는 입력받은 base와 power를 토대로 base^power를 구하는 코드이다. (숫자가 커질 것을 대비해 BigInteger를 사용)

 

 

코드를 보면 LongComputationTask 스레드에 base는 10000000, power는 10000000 을 입력했다.

 

 

딱 봐도 굉장히 오랜 시간이 걸릴 것 같다.

 

 

물론 위의 코드에서 데몬 스레드로 설정해주는 코드가 없다면 메인 스레드가 종료되지 않을 것이다.

 

thread.setDaemon(true); // 메인 스레드 종료 시 함께 종료될 데몬 스레드로 설정
thread.start();
thread.interrupt(); // 작업중인 스레드에게 중지 요청

 

위의 코드에선 계승 스레드를 데몬 스레드로 선언했기 때문에 계승 계산 작업이 종료되지 않았어도 interrupt() 요청이 들어오면 작업의 완료 유무에 관계없이 계승 스레드가 중지되고, 애플리케이션이 종료된다.

 

 

 

스레드 동기화


 

두번째 스레드 조정은 스레드 동기화 이다.

 

 

작업을 수행중인 스레드A와 스레드B가 있다.

 

 

만약 스레드B의 작업이 스레드A 작업의 결과값을 필요로 하는 작업이라면, 반드시 스레드A가 정상적으로 완료된 후, 스레드B가 실행되어야 한다. 이럴 때 사용하는 것이 스레드 동기화 이다.

 

 

만약 스레드 동기화를 하지 않으면, 스레드B는 계속해서 스레드A의 작업의 완료되었는지 확인하는 작업을 해야한다. 이러한 작업을 반복한다면 CPU 사이클을 계속해서 태우기 때문에 스레드A를 느리게 만들 수 있다.

 

 

이럴바엔 차라리 스레드B를 스레드A의 작업이 완료되기 전까지 재워버리는게 더 나을 듯 하다…

 

 

그럼 한번 재워보자!

 

public class Main {

    static int threadAResult;
    static int threadBResult;

    public static void main(String[] args) {
        Thread threadA = new Thread(new ThreadA(10));
        Thread threadB = new Thread(new ThreadB());

        threadA.start();
        threadB.start();

        System.out.println(threadBResult);
    }

    static class ThreadA implements Runnable {

        private int num;

        public ThreadA(int num) {
            this.num = num;
        }

        @Override
        public void run() {
            try {
                Thread.sleep(100000);
                threadAResult = this.num;
            } catch (InterruptedException e) {
                System.out.println("interrupt 발생");
            }
        }
    }

    static class ThreadB implements Runnable {
        @Override
        public void run() {
            for (int i = 0 ; i < 10 ; i++) {
                threadBResult += threadAResult;
            }
        }
    }
}

 

 

위의 코드는 스레드A, B의 상황을 대략적으로 구현한 것이다.

 

 

과연 이 코드를 실행하면 어떤 결과가 나올까? 바로 0 이다.

 

 

보다시피 threadAResult에 num을 넣기 위해선 100,000ms(100초)을 대기해야 한다.

 

 

하지만 스레드 동기화를 하지 않으면 스레드B는 100초라는 시간이 소요된다는 것을 모른채 결과값이 할당되기 전인 threadAResult을 사용해 threadBResult의 값을 계산할 것이다.

 

 

이러한 상황을 방지하기 위해선 스레드B는 스레드A의 작업이 종료될 때 까지 기다려야 한다.

 

 

이럴 때 사용하는 것이 join() 메서드이다.

대략적인 로직은 위의 이미지와 같다.

 

public class Main {

    static int threadAResult = 0;
    static int threadBResult = 0;

    public static void main(String[] args) throws InterruptedException {
        Thread threadA = new Thread(new ThreadA(10));
        Thread threadB = new Thread(new ThreadB());

        threadA.start();
        threadA.join(); // threadA가 완료될 때까지 대기
        threadB.start();
        threadB.join(); // threadB가 완료될 때까지 대기
        System.out.println(threadBResult);
    }

		...
		
}

 

 

위의 코드를 실행하면 100이라는 결과값이 잘 출력된다.

 

 

 

참조

https://www.udemy.com/course/java-multi-threading/?couponCode=24T6MT102824

 

'백엔드 > Java' 카테고리의 다른 글

[Java] 멀티스레딩 - 가상 스레드  (0) 2024.11.04
[Java] 멀티스레딩 - 스레드 간 통신  (2) 2024.11.04
[Java] 멀티스레딩 - 락 심화  (0) 2024.11.04
[Java] 멀티스레딩 - 스레드간 데이터 공유  (0) 2024.10.31
[Java] 멀티스레딩 - 성능 최적화 (Feat : 스레드 풀)  (0) 2024.10.30
'백엔드/Java' 카테고리의 다른 글
  • [Java] 멀티스레딩 - 스레드 간 통신
  • [Java] 멀티스레딩 - 락 심화
  • [Java] 멀티스레딩 - 스레드간 데이터 공유
  • [Java] 멀티스레딩 - 성능 최적화 (Feat : 스레드 풀)
오도형석
오도형석
  • 오도형석
    형석이의 성장일기
    오도형석
  • 전체
    오늘
    어제
    • 분류 전체보기 N
      • MSA 모니터링 서비스
        • DB
      • 스파르타 코딩클럽
        • SQL
        • Spring
      • 백엔드
        • Internet
        • Java
        • DB
      • 캡스톤
        • Django
        • 자연어처리
      • Spring
        • JPA
        • MSA
      • ETC
        • ERROR
      • 개발 일기 N
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 인기 글

  • 태그

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
오도형석
[Java] 멀티스레딩 - 스레드 조정
상단으로

티스토리툴바