[TIL] 자바 동시성. 스레드 실습
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만 오버라이딩하면된다.
실습 : 금고 해킹 시뮬레이션
시나리오
금고 설계
임의 비밀번호가 설정된 금고 있음
목표
해커가 금고 비밀번호를 맞추기 전에 경찰이 잡으러 도착한다
스레드 역할
해커 스레드
금고 비밀번호를 추측한다
경찰 스레드
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();
}
}
}
스레드 종료 및 데몬 스레드
스레드 조정 필요성
스레드 리소스 정리
스레드는 실행 중이 아니더라도 메모리, CPU 캐시, 커널 리소스를 사용한다.
작업 완료 후에 종료되지 않은 스레드는 시스템 리로스를 낭비함
오작동 방지
스레드가 무한 루프, 긴 계산, 잘못된 요청 등을 실행하는 경우 중단이 필요하다
어플 종료
실행 중인 스레드가 하나라도 있으면 어플 종료 안됨.
모든 스레드 중단해야함
스레드 중단 법
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;
}
}
}