qkrtkdwns3410

[TIL]유데미 자바 스레드 조정 등

유데미자바스레드병렬 계산스레드 관리스레드 안전성비동기 프로그램이스레드 의존성성능 최적화데몬 스레드
4 days ago
·
11 min read

스레드의 실행 제어

  • 스레드

    • 프로그램에서 독립적으로 실행되는 작은 작업 단위임

    • 한 번 시작된 스레드는

      • 다른 스레드와 독립적으로 실행

      • 실행 순서를 예측할 수 없음

    • 스레드 간 의존 관계 가 있는 경우

      • 이를 제어 해야 안전한 결과를 얻을 수 있음.

스레드 의존성과 효율

  • 의존하는 스레드 문제

    • 스레드 A 의 결과가 스레드 B 의 입력으로 필요한 경우

      • 스레드 B는 스레드 A 가 작업을 완료했는지 알아야 함.

      • 스레드 B 가 계속적으로 스레드 A 가 작업을 완료했는지 확인하는 방법은

        • 엄청 비효율

  • better

    • 스레드 B 를 대기 상태 로 두고, 스레드 A가 완료되는 경우 다시 깨어나게 한다.

    • Thread.join 활용

예시

package com.thread.coordination.join;

import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;

/**
 * packageName    : com.thread.coordination.join
 * fileName       : Mian
 * author         : ipeac
 * date           : 24. 12. 19.
 * description    :
 * ===========================================================
 * DATE              AUTHOR             NOTE
 * -----------------------------------------------------------
 * 24. 12. 19.        ipeac       최초 생성
 */
public class Main {
    public static void main(String[] args) {
        List<Long> inputNumbers = List.of(0L, 3435L, 2324L, 554L, 34L, 23525L, 542L, 245L, 245L);
        
        List<FactorialThread> threads = new ArrayList<>();
        
        for (Long inputNumber : inputNumbers) {
            threads.add(new FactorialThread(inputNumber));
        }
        
        for (Thread thread : threads) {
            thread.start();
        }
        
        for (int i = 0; i < inputNumbers.size(); i++) {
            FactorialThread thread = threads.get(i);
            if (thread.isFinished()) {
                System.out.println("Factorial of " + inputNumbers.get(i) + " is " + thread.getResult());
            } else {
                System.out.println("The calculation for the factorial of " + inputNumbers.get(i) + " is still in progress.");
            }
        }
    }
    
    public static class FactorialThread extends Thread {
        private long inputNumber;
        private BigInteger result = BigInteger.ZERO;
        private boolean isFinished = false;
        
        public FactorialThread(long inputNumber) {
            this.inputNumber = inputNumber;
        }
        
        @Override
        public void run() {
            this.result = factorial(inputNumber);
            this.isFinished = true;
        }
        
        public BigInteger factorial(long n) {
            BigInteger tempResult = BigInteger.ONE;
            
            for (long i = n; i > 0; i--) {
                tempResult = tempResult.multiply(new BigInteger(Long.toString(i)));
            }
            
            return tempResult;
        }
        
        public boolean isFinished() {
            return isFinished;
        }
        
        public BigInteger getResult() {
            return result;
        }
    }
    

}

//
Factorial of 0 is 1
The calculation for the factorial of 3435 is still in progress.
The calculation for the factorial of 2324 is still in progress.
Factorial of 554 is //생략
Factorial of 34 is 295232799039604140847618609643520000000
The calculation for the factorial of 23525 is still in progress.
Factorial of 542 is //길어서 생략
Factorial of 245 is 3446381088548184667326770790875951922006976951375582384611003611981639529782351868763804143237672379379846868628577527599609043944010849343355183826916579274876093562728501050027821075609832035303654996749361753195163012769070700485723141551669720900953728011558584003035375928212655846940281367061125645619508966404523085638743763256980870494465156859819224514274661087972929242817912092409671474491631797677547338910924800000000000000000000000000000000000000000000000000000000000
Factorial of 245 is 3446381088548184667326770790875951922006976951375582384611003611981639529782351868763804143237672379379846868628577527599609043944010849343355183826916579274876093562728501050027821075609832035303654996749361753195163012769070700485723141551669720900953728011558584003035375928212655846940281367061125645619508966404523085638743763256980870494465156859819224514274661087972929242817912092409671474491631797677547338910924800000000000000000000000000000000000000000000000000000000000
  • 해당 코드에서 메인 스레드 - 여럿 계산 스레드가 존재한다.

    • 계산 스레드같은 경우 run() 으로 메인 스레드와 별도로 돌아감.

    • 메인 스레드는 계산 스레드가 작업이 완료되었는지까지 기다려주지 않기에

    • 거의 조금 계산이 길어지는 경우

      • still in progress 가 뜨면서

      • 계산중임을 알려준다..

  • 스레드가 끝날때까지 기다리는 Thread.join() 이 필요하다.

Thread.join() 적용

package com.thread.coordination.join;

import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;

public class Main {
    public static void main(String[] args) throws InterruptedException {
        List<Long> inputNumbers = List.of(0L, 3435L, 2324L, 554L, 34L, 23525L, 542L, 245L, 245L);
        
        List<FactorialThread> threads = new ArrayList<>();
        
        for (Long inputNumber : inputNumbers) {
            threads.add(new FactorialThread(inputNumber));
        }
        
        for (Thread thread : threads) {
            thread.start();
        }
        
        for (Thread thread : threads) {
            thread.join();
        }
        
        for (int i = 0; i < inputNumbers.size(); i++) {
            FactorialThread thread = threads.get(i);
            
            if (thread.isFinished()) {
                System.out.println("Factorial of " + inputNumbers.get(i) + " is " + thread.getResult());
            } else {
                System.out.println("The calculation for the factorial of " + inputNumbers.get(i) + " is still in progress.");
            }
        }
    }
    
    public static class FactorialThread extends Thread {
        private long inputNumber;
        private BigInteger result = BigInteger.ZERO;
        private boolean isFinished = false;
        
        public FactorialThread(long inputNumber) {
            this.inputNumber = inputNumber;
        }
        
        @Override
        public void run() {
            this.result = factorial(inputNumber);
            this.isFinished = true;
        }
        
        public BigInteger factorial(long n) {
            BigInteger tempResult = BigInteger.ONE;
            
            for (long i = n; i > 0; i--) {
                tempResult = tempResult.multiply(new BigInteger(Long.toString(i)));
            }
            
            return tempResult;
        }
        
        public boolean isFinished() {
            return isFinished;
        }
        
        public BigInteger getResult() {
            return result;
        }
    }
}
for (Thread thread : threads) {
    thread.join();
}
  • 을 통하여 메인 스레드와 계산 스레드를 연결한다.

    • 메인 스레드는 계산 스레드의 계산이 병행적으로 모두 완료된 경우에

      • 동작할 것이다

  • 다만,

    • 주의해야할 케이스가 있다.

    List<Long> inputNumbers = List.of(100000000000L, 3435L, 2324L, 554L, 34L, 23525L, 542L, 245L, 245L);
    
    • 에서 100000000000L 의 값에 대해

      • 계산 스레드가 수행하는 경우

      • 왠종일 계산하고 쳐 앉아있다.

    • 그래서 해당 스레드마다 타임아웃을 어느정돈 둬야한다.

  • Duration 옵션을 줄 수 있느데

    • 해당 위치에 2000 을 적으면 2초 동안 계산이 이루어지지 않으면 해당 스레드는 기다리지 않겠다는 의미임

  • 결과

    The calculation for the factorial of 100000000000 is still in progress.
    Factorial of 3435 is // 길어서 생략
    Factorial of 2324 is // 길어서 생략
    Factorial of 554 is 1191727136806837244882582283106805387770005755965908047983081245960480213330563853528955253930547421215039291311599076798092426516053875895821245375462369728252493061843487510963049523488888754539791837452943408399859647327147502547052379636286273055010465806029110009104713085157860683741002669049888655075983983852711560875259367330433663761130741971283507845930972311843631259051730384176605689209336690194461503742469785985047389464131210379943162027387213523218050067026223431883424750837023717093059254089678160619956112022737746587181813173857534345979459157068479550808846993437317436702137496149440224189233486485246017846204920360701166257511560201387840137612887313560409842277481196061085397629797577251875676163423104849316410147126590756098226138000160385539304989876897873673358364697848479376024982124602315498644546485373020148670929575725115041050733030044378143663458513596302213255150637965270579688496576332377878020448108210609176193410494935339163990414001675136639483740017525779300407406500286260875156438834048234394302618852929817122189693614338599864305049618766373265235382830808586093265282193823184945426913474641920000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
    Factorial of 34 is 295232799039604140847618609643520000000
    Factorial of 23525 is //너무길어서 생략
    Factorial of 542 is 1607535095378011891056328484225529595243838798499054994766025156939288169810835272449306354580237969682411561904659247352233869578500369085015849202525200176033722982248939071551000445386157616140174101436646697998310453314131718849668026597339962830038313202330305128176688853506387490682735113517976086814097483502994870323946561908276072060979277468132673766174329569237354871039018414225133977033767828902564327303205632246854879683635732298203740589425046261382371639739486392287792345081093595873758904816605811552325582679996192624783892220035029850790339575675172367310968287156079757464668269000854651621014536329222770032994629808829842306684806739303129202118931252425617709020165318344315725608628457669138904568258434726321656634035862803596978425264476046798283242280066301566642768649189395076269718280978615189118280004812666753362943638722532280788300931624201743200983787393440774364471798212114625772668425190618929367461976989738835188658583210597078950867099896465084703122130411827593805066916468242618448796850243328574235699250768859321062302677755711983880776617227228201878615112377392168960000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
    Factorial of 245 is 3446381088548184667326770790875951922006976951375582384611003611981639529782351868763804143237672379379846868628577527599609043944010849343355183826916579274876093562728501050027821075609832035303654996749361753195163012769070700485723141551669720900953728011558584003035375928212655846940281367061125645619508966404523085638743763256980870494465156859819224514274661087972929242817912092409671474491631797677547338910924800000000000000000000000000000000000000000000000000000000000
    Factorial of 245 is 3446381088548184667326770790875951922006976951375582384611003611981639529782351868763804143237672379379846868628577527599609043944010849343355183826916579274876093562728501050027821075609832035303654996749361753195163012769070700485723141551669720900953728011558584003035375928212655846940281367061125645619508966404523085638743763256980870494465156859819224514274661087972929242817912092409671474491631797677547338910924800000000000000000000000000000000000000000000000000000000000
    

다만! 어플리케이션이 종료되지 않는 문제가 발생한다

  • 이는 계산 스레드 하나가 아직 완료가 되지 않고 종료되지 않아서 발생하는 문제이다.

    • 어플리케이션은 스레드가 하나라도 살아있는 경우 동작하지 않음을 알아야 한다.

      • 종료를 시키려면 어떻게 해야할까?

  • 바로 데몬 스레드 로 계산 스레드들을 설정하는 것이다

    • 데몬 스레드는 프로그램이 종료되는 경우 자동으로 종료되는 백그라운드 스레드이다.

    public static void main(String[] args) throws InterruptedException {
        List<Long> inputNumbers = List.of(100000000000L, 3435L, 2324L, 554L, 34L, 23525L, 542L, 245L, 245L);
        
        List<FactorialThread> threads = new ArrayList<>();
        
        for (Long inputNumber : inputNumbers) {
            threads.add(new FactorialThread(inputNumber));
        }
        
        for (Thread thread : threads) {
            thread.setDaemon(true); // 데몬으로 설정
            thread.start();
        }
        
        for (Thread thread : threads) {
            thread.join(2000);
        }
        
        for (int i = 0; i < inputNumbers.size(); i++) {
            FactorialThread thread = threads.get(i);
            
            if (thread.isFinished()) {
                System.out.println("Factorial of " + inputNumbers.get(i) + " is " + thread.getResult());
            } else {
                System.out.println("The calculation for the factorial of " + inputNumbers.get(i) + " is still in progress.");
            }
        }
    }

기억해야할 점

  1. 스레드 실행 순서는 제어가 불가능하다

    • 스레드 간의 의존 관계가 있는 경우 항상 이를 제어할 필요성이 있다

  2. thread.join 으로 기다리는 시간을 설정해 프로그램이 멈추지 않게 해야 한다

  3. 방어적으로 프로그래밍하라.

실습

코딩 연습 2: 멀티스레딩 계산

이번 코딩 연습에서는 지난 강의에서 배운 모든 지식을 활용해 보겠습니다.

연습을 시작하기 전에, 특히 다음 주제를 복습해주시기 바랍니다.

  1. 스레드 생성 - Thread 클래스와 start()메서드를 이용해 스레드를 생성하고 시작하는 방법입니다.

  2. 스레드 조인 - Thread.join() 메서드를 활용해 다른 스레드를 기다리는 방법입니다.

이번 연습에서는 다음 식을 효율적으로 계산해 보겠습니다. result = base1 ^ power1 + base2 ^ power2

여기서 a^b는 a를 b 제곱했다는 뜻입니다.

예를 들어, 10^2 = 100입니다.

숫자를 거듭제곱을 하는 작업은 복잡한 연산이므로, 다음 식을 실행하려 합니다.

result1 = x1 ^ y1

result2 = x2 ^ y2

둘을 동시에 실행하고,

마지막에는 다음과 같이 결과값을 더합니다. result = result1 + result2

이렇게 하면 전반적인 계산 속도를 더 빠르게 할 수 있습니다.

참고 :

base1 >= 0, base2 >= 0, power1 >= 0, power2 >= 0

import java.math.BigInteger;

public class ComplexCalculation {
    public BigInteger calculateResult(BigInteger base1, BigInteger power1, BigInteger base2, BigInteger power2) {
        BigInteger result;
        /*
            Calculate result = ( base1 ^ power1 ) + (base2 ^ power2).
            Where each calculation in (..) is calculated on a different thread
        */
        return result;
    }

    private static class PowerCalculatingThread extends Thread {
        private BigInteger result = BigInteger.ONE;
        private BigInteger base;
        private BigInteger power;
    
        public PowerCalculatingThread(BigInteger base, BigInteger power) {
            this.base = base;
            this.power = power;
        }
    
        @Override
        public void run() {
           /*
           Implement the calculation of result = base ^ power
           */
        }
    
        public BigInteger getResult() { return result; }
    }
}
  • 위는 베이스 코드임.

  • 아래 내 제출

package com.thread.coordination.join;

import java.math.BigInteger;
import java.util.List;

public class ComplexCalculation {
    public BigInteger calculateResult(BigInteger base1, BigInteger power1, BigInteger base2, BigInteger power2) {
        BigInteger result;
        /*
            Calculate result = ( base1 ^ power1 ) + (base2 ^ power2).
            Where each calculation in (..) is calculated on a different thread
        */
        List<Thread> threads = List.of(
                new PowerCalculatingThread(base1, power1),
                new PowerCalculatingThread(base2, power2)
        );
        
        threads.forEach(thread -> { // 데몬 스레드로 만들고
            thread.setDaemon(true);
            thread.start();
        });
        
        for (Thread thread : threads) {
            try {
                thread.join(); // 스레드 메인스레드에 연결
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        
        //계산
        result = ((PowerCalculatingThread) threads.get(0)).getResult().add(((PowerCalculatingThread) threads.get(1)).getResult());
        
        return result;
    }
    
    private static class PowerCalculatingThread extends Thread {
        private BigInteger result = BigInteger.ONE;
        private final BigInteger base;
        private final BigInteger power;
        
        public PowerCalculatingThread(BigInteger base, BigInteger power) {
            this.base = base;
            this.power = power;
        }
        
        @Override
        public void run() {
            this.calculateResult();
        }
        
        private void calculateResult() {
            this.result = this.base.pow(this.power.intValue());
        }
        
        public BigInteger getResult() {
            return result;
        }
    }
}







새는 알에서 빠져나오려고 몸부림친다. 알은 세계다. 태어나려고 하는 자는 하나의 세계를 파괴하지 않으면 안 된다. 그 새는 신을 향해 날아간다