qkrtkdwns3410

[TIL] 자바 동시성. 스레드 실습

5 days ago
·
8 min read

1. 스레드 생성 방법

1-1. Runnable 인터페이스 사용

방법

  • Runnable 인터페이스를 구현한 클래스를 생성한다

  • 해당 객체를 Thread 의 생성자에 전달함

public class Thread implements Runnable { // Runnable 구현

//생성자에 전달됨.
public Thread(Runnable task) {
   this(null, null, 0, task, 0, null);
}

1-2. Thread 클래스를 확장

  • 방법

    • Thread 클래스를 확장한 새로운 클래스를 생성한다

  • 특징

    • Thread 가 이미 Runnable 클래스를 구현하고 있음.

    • run만 오버라이딩하면된다.

실습 : 금고 해킹 시뮬레이션

시나리오

  1. 금고 설계

    • 임의 비밀번호가 설정된 금고 있음

  2. 목표

    1. 해커가 금고 비밀번호를 맞추기 전에 경찰이 잡으러 도착한다

  3. 스레드 역할

    1. 해커 스레드

      • 금고 비밀번호를 추측한다

    2. 경찰 스레드

      • 10초 후 도착하여 해커를 체포

public class Main {
    public static final int MAX_PASSWORD = 9999;

    public static void main(String[] args) {
        Random random = new Random();

        Vault vault = new Vault(random.nextInt(MAX_PASSWORD));

        List<Thread> threads = List.of(
                new AscendingHackerThread(vault),
                new DescendingHackerThread(vault),
                new PoliceThread()
        );

        threads.forEach(Thread::start);
    }

// 금고 클래스
// 비밀번호 맞는지 체크
// 해커의 속도 늦추기 위해 5ms 지연
    private static class Vault {
        private int password;

        public Vault(int password) {
            this.password = password;
        }

        public boolean isCorrectPassword(int guess) {
            try {
                Thread.sleep(5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            return this.password == guess;
        }
    }

//해커 스레드 추상체
// Vault 라는 금고 객체가 있으며
// 해당 스레드 객체의 우선순위는 TOP 이다.
    private static abstract class HackerThread extends Thread {
        protected Vault vault;

        public HackerThread(Vault vault) {
            this.vault = vault;
            this.setName(this.getClass().getSimpleName());
            this.setPriority(Thread.MAX_PRIORITY);
        }

        @Override
        public void start() {
            System.out.println("Starting thread: " + this.getName());
            super.start();
        }
    }

//구현체 1 0 부터 9999까지 맞추기
    private static class AscendingHackerThread extends HackerThread {
        public AscendingHackerThread(Vault vault) {
            super(vault);
        }

        @Override
        public void run() {
            for (int guess = 0; guess < MAX_PASSWORD; guess++) {
                if (vault.isCorrectPassword(guess)) {
                    System.out.println(this.getName() + " guessed the password: " + guess);
                    System.exit(0);
                }
            }
        }
    }

// 구현체 2 9999 부터 0까지 맞추기
    private static class DescendingHackerThread extends HackerThread {
        public DescendingHackerThread(Vault vault) {
            super(vault);
        }

        @Override
        public void run() {
            for (int guess = MAX_PASSWORD; guess >= 0; guess--) {
                if (vault.isCorrectPassword(guess)) {
                    System.out.println(this.getName() + " guessed the password: " + guess);
                    System.exit(0);
                }
            }
        }
    }

// 경찰 스레드
// 총 10의 숫자를 센다. ( 10초 )
    private static class PoliceThread extends Thread {
        @Override
        public void run() {
            for (int i = 10; i > 0; i--) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(i);
            }

            System.out.println("Game over for you hackers");
            System.exit(0);
        }
    }
}

실습 : 코드1

코딩 연습 1: 스레드 생성 - MultiExecutor

이번 연습에서는 MultiExecutor를 구현해 보겠습니다.

이 클래스의 클라이언트는 Runnable 작업의 목록을 생성해서 해당 목록을 MultiExecutor의 생성자에게 제공할 것입니다.

클라이언트가 executeAll()을 실행하면, MultiExecutor가 주어진 모든 작업을 실행하게 됩니다.

멀티코어 CPU를 최대한 활용하기 위해, 우리는 각 작업을 서로 다른 스레드로 전달해서 MultiExecutor가 모든 작업을 동시에 진행하게 하려고 합니다.

import java.util.ArrayList;
import java.util.List;

public class MultiExecutor {

    private final List<Runnable> tasks;

    /*
     * @param tasks to executed concurrently
     */
    public MultiExecutor(List<Runnable> tasks) {
        this.tasks = tasks;
    }

    /**
     * Executes all the tasks concurrently
     */
    public void executeAll() {
        List<Thread> threads = new ArrayList<>(tasks.size());

        for (Runnable task : tasks) {
            Thread thread = new Thread(task);
            threads.add(thread);
        }

        for(Thread thread : threads) {
            thread.start();
        }
    }
}

스레드 종료 및 데몬 스레드

스레드 조정 필요성

  1. 스레드 리소스 정리

    • 스레드는 실행 중이 아니더라도 메모리, CPU 캐시, 커널 리소스를 사용한다.

    • 작업 완료 후에 종료되지 않은 스레드는 시스템 리로스를 낭비함

  2. 오작동 방지

    • 스레드가 무한 루프, 긴 계산, 잘못된 요청 등을 실행하는 경우 중단이 필요하다

  3. 어플 종료

    • 실행 중인 스레드가 하나라도 있으면 어플 종료 안됨.

    • 모든 스레드 중단해야함

스레드 중단 법

  1. Thread.interrupt

    public class Main {
        public static void main(String[] args) {
            Thread thread = new Thread(new BlockingTask());
            thread.start();
    
            thread.interrupt();
        }
    
        private static class BlockingTask implements Runnable {
            @Override
            public void run() {
                try {
                    Thread.sleep(500000);
                } catch (InterruptedException e) {
                    System.out.println("Exiting blocking thread");
                }
            }
        }
    }
    
    public class Main {
        public static void main(String[] args) {
            Thread thread = new Thread(new LongComputationTask(new BigInteger("2"), new BigInteger("10")));
    
            thread.start();
    
            thread.interrupt();
        }
    
        private static class LongComputationTask implements Runnable {
            private final BigInteger base;
            private final 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) {
                // 0
                BigInteger result = BigInteger.ONE;
    
                //의미 : power만큼 반복
                for (BigInteger i = BigInteger.ZERO; i.compareTo(power) != 0; i = i.add(BigInteger.ONE)) {
                    result = result.multiply(base);
                }
    
                return result;
            }
        }
    }
    
  • 위와 같은 코드에서 2 ^ 10은 얼마 안걸림

    • 근데 20000 ^ 100000 같은 경우 겁나 오래걸림

  • thread.interrupt(); 를 수행해도

    • interrupt 를 받는 부분이 없어서 무한 루프에 걸리는 건 여전함

  • 그래서 for문 중간에

                //의미 : power만큼 반복
                for (BigInteger i = BigInteger.ZERO; i.compareTo(power) != 0; i = i.add(BigInteger.ONE)) {
                    // 스레드가 interrupt되었는지 확인
                    if (Thread.currentThread().isInterrupted()) {
                        System.out.println("Prematurely interrupted computation");
                        return BigInteger.ZERO;
                    }
    
                    result = result.multiply(base);
                }
    
    • 해당 내용을 추가해야한다.

데몬 스레드

  • 긴 계산 작업을 수행하는 스레드를 Daemon 으로 설정한다

  • 메인 스레드가 종료되는 경우 Daemon 스레드도 자동으로 종료된다.

public class Main {
    public static void main(String[] args) {
        Thread thread = new Thread(new LongComputationTask(new BigInteger("20000"), new BigInteger("100000")));
        
        //데몬 설정으로 -> 메인 스레드 종료시 같이 스레드가 종료되게 설정
        thread.setDaemon(true);
        thread.start();
        
        //interrupt 가 수행되는 경우 메인 스레드가 종료되게 되는데
        thread.interrupt();
        //메인 스레드 종료와 함께 새로운 데몬 스레드도 같이 종료처리.
    }
    
    private static class LongComputationTask implements Runnable {
        private final BigInteger base;
        private final 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) {
            // 0
            BigInteger result = BigInteger.ONE;
            
            //의미 : power만큼 반복
            for (BigInteger i = BigInteger.ZERO; i.compareTo(power) != 0; i = i.add(BigInteger.ONE)) {
                result = result.multiply(base);
            }
            
            return result;
        }
    }
}






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