멀티 스레드, 멀티 프로세스
스레드: 작업 흐름
프로세스: 실행중인 프로그램
멀티 스레드: 프로세스 내에 여러 작업 흐름
멀티 프로세스: 컴퓨터 내 여러 프로그램
메인 스레드, 작업 스레드
프로그램의 시작 지점인 main 함수가 메인스레드 런 코드의 구현체이다? -> XXX
하지만 비슷하다 -> ㅇㅇㅇ
main()은 메인스레드가 실행될때 진입점 코드..
run()은 작업 스레드가 실행될때 진입점 코드..
왜 다른가? 음.. 일단 메인스레드와 작업스레드는 이름만 스레드이지 완전 다른 종자라고 생각하는게 편함. 애초에 Thread 클래스를 상속하는 코드가 있는것도 아니고 JVM에 의해 그저 진입하고 모든 동작이 끝나면 return되어 끝나버리는게 메인 스레드의 숙명이니까. 마치 쥐랑 땃쥐?
스레드 상태 , 그리고 컨트롤 방법 여러가지
오라클 공식 스레드 상태 https://docs.oracle.com/javase/8/docs/api/java/lang/Thread.State.html
결국 상태 컨트롤의 핵심을 정리하자면 : RUNNABLE, WAITTING, TERMINATED, run()매서드
- NEW: 한번도 start() 되지 않은 따끈한 상태. 여기서 start()를 호출하면 RUNNABLE
- RUNNABLE: 언제든 run() 할 수 있는 상태. 자원만 점유되면 run() 실행. 멀티 스레드에서 run()은 말도 안되는 빠른 속도로 자원을 스위칭하며 다른 스레드와 동시에 run()하는 방식으로 마치 동시에 실행되는것 같은 모습을 보여준다.
- WAITTING: 기약없는 기다림. 깨워주지 않는 이상 절대 안깨어남.
- TIMED_WAITTING: 시간을 자체적으로 걸고 시간이 끝나면 RUNNABLE로
- BLOCKED: 모니터? 개념이 이때 등장. 모니터락에 의해 차단당하여 일시정지 된 상태 즉 모니터락이 풀리면 바로 RUNNABLE
- TERMINATED: 걍 종료. 이걸 이용해서 멀티 스레드 컨트롤한다는건 바보같은짓. new를 새로 파지 않는 이상 TERMINATED -> Start()?? 그냥 에러임.
컨트롤 방법 타입 정리
메서드 | 설명 |
start() | NEW 상태를 RUNNABLE로 체인지 |
yield() | 상태를 바꾸는건 아니고, 실행 순서를 잠깐 양보하는 행위 like continue~ not return |
sleep(long millis) | 스레드 객체에 구현되어 있으며, 해당 스레드를 TIMED_WAITTING 상태로 |
join() | 스레드 A에서 스레드 B의 join()을 호출하면 A는 B의 작업이 종료될때까지 WAITTING |
join(long millis) | sleep과 다른점은 호출하는쪽을 TIMED_WAITTING 상태로 만든다는 점. |
wait() | (이게 제일 골머리를 썩힌 녀석 후..) wait()은 어디에 있는 함수이냐? Object를 상속했다면 전부 가지고 있다. 목적은 무엇이냐? synchronized 블럭 안에서만 호출이 가능하며 해당 object에 락과 키를 쥐어줌. 그 중 Object.wait() 함수를 만난 스레드는 그냥 일단 WAITTING 상태로 만든다. |
wait(long miilis) | TIMED_WAITTING 상태로 |
notify | synchronize 블럭 안에서 object.notify()를 호출할 수 있고, 해당 ojbect.wait()에 의해 WAITTING상태로 간 스레드를 RUNNABLE 상태로 풀어주는 역할을 하는 매서드임. 그런데 대기중인 무수히 많은 스레드중 스케쥴러에의해 가장 우선순위가 앞인 녀석 하나만 풀어짐 |
notifyAll() | 해당 오브젝트를 기준으로 WAITTING 상태가 걸린 무수히 많은 스레드 전부 다 풀어줌 |
interruput() | WAITTING 상태의 스레드를 예외 발생시켜 RUNNABLE 화 시키는 유쾌한 방법 |
와우...
스레드 동기화..
동기화: 같은 데이터를 공유하고 일치시키는 것
동기화가 필요한 시점: 공용 데이터를 사용해야할때 다른 스레드의 사용이 타이밍이 겹칠때 이상현상 방지
동기화 원리: 스레드가 사용중인 객체를 다른 스레드가 사용못하게 잠금을 건다.
케이스1 : 이럴 경우엔 어떨까? 서로 다른 싱크로나이즈 함수를 호출하였고 사용하는 변수도 다를때
public class P613_Calculator {
private int memory1;
private int memory2;
public int getMemory1() {
return memory1;
}
public int getMemory2() {
return memory2;
}
public synchronized void setMemory1(int memory) {
this.memory1 = memory;
try {
Thread.sleep(2000);
} catch (InterruptedException e) {}
System.out.println(Thread.currentThread().getName() + ": " + this.memory1);
}
public void setMemory2(int memory) {
synchronized (this) {
this.memory2 = memory;
try {
Thread.sleep(2000);
} catch (InterruptedException e) {}
System.out.println(Thread.currentThread().getName() + ": " + this.memory2);
}
}
}
작업스레드 1은 setMemory로 memory1을 사용하고, 작업스레드2는 setMemory2로 memory2를 사용한다 하더라도 내부적으로 어찌되든 뭐가되든 전혀 상관없이. 싱크로나이즈 블록이 컨트롤하는 대상은 전체 객체이다.
즉 객체1의 synchronized 매서드 및 블록을 스레드1이 사용하고 있다면 스레드2가 다른 synchronize 함수를 사용하고 싶어도 잠겨서 못사용한다는 뜻
이렇게 동기화를 컨트롤함.
케이스2 : wait()과 notify()를 통한 동시성 제어
public class P617_WorkObject {
private int cnt = 0;
public synchronized void method() {
cnt++;
Thread thread = Thread.currentThread();
System.out.println(thread.getName() + ": method 작업 실행 " + cnt);
notify(); //깨우고
try {
wait(); //대기상태로 들어감
} catch (InterruptedException e) {}
}
}
P617을 사용하여 method()를 반복 호출하게 되면 기존에 notify가 없었다면 Thread 실행순서는 뒤죽박죽이었을것
하지만 철저하게 notify() wait()을 통해 완전 제어
케이스3: 스레드 종료시에 사용하던 자원들을 안전하게 처리하기
스레드가 막 엄청나게 돌고 있는데 갑자기 스탑잇 명령을 내려버리면 어지러워질것 그래서 자바에서는 이제 스레드에 stop을 금지시키고 있다.
조건 이용법과 interrupt() 법이 주로 사용됨.
public class P621_SafeStop {
public static void main(String[] args) {
P621_SafeStop safeStop = new P621_SafeStop();
boolStopThread thread = safeStop.new boolStopThread();
thread.start();
try {
Thread.sleep(500);
} catch (InterruptedException e) {}
thread.setStop(true);
interruptStopThread thread2 = safeStop.new interruptStopThread();
thread2.start();
try {
Thread.sleep(500);
} catch (InterruptedException e) {}
thread2.interrupt();
}
public class boolStopThread extends Thread {
private boolean stop=false;
public void setStop(boolean stop) {
this.stop = stop;
}
public void run() {
while (!stop) { //반복조건 탈출로 안전하게 종료
System.out.println("조건 종료 스레드 아직 실행 중");
}
System.out.println("자원 정리");
System.out.println("조건 종료 스레드 실행 종료");
}
}
public class interruptStopThread extends Thread {
public void run() {
try {
while (true) {
System.out.println("인터럽트 종료 스레드 아직 실행 중");
Thread.sleep(1);
}
} catch (InterruptedException e) {}
System.out.println("자원 정리");
System.out.println("인터럽트 종료 스레드 실행 종료");
}
}
}
데몬 스레드
거창한 이름을 가진것과 달리 별거 없다. 서브 스레드이다. 스레드A에서 스레드B를 생성하든 받아오든 하여간
스레드B.setDaemon(true) 를 호출한 순간 주-서브 관계는 성립되어진것.
주 스레드가 종료될때 데몬 스레드도 종료된다.
예) 자동저장 기능, 가비지 컬렉터 등이 이러한 서브 스레드이다.
스레드 풀
기본적으로 병렬 처리가 늘어날 수록 서비스의 동시성이 확장되어 고급진? 프로그램이 되는건 자명한 사실.
cpu의 성능이 증가하여 동시처리 어마무시하게 많아도 괜찮다는 사실.
하지만 그렇게 많아지면 너무 어지러워진다느 사실.
스레드 풀은 많은 수의 작업스레드를 관리할때 유용하다.
스레드 풀은 스레드를 풀에 등록해놓고, 작업이 들어오면 처리하고 다시 빠지는 형태이다. 이게 만약에 없다면 작업이 들어올때마다 new로 생성하고 작업 맡기고 죽이고 다시 new로 생성하고 이짓거리를 하느라 소모적인 자원을 사용할건데 얘는 미리 올려놓고 처리 처리 리턴 처리 리턴 리턴 리턴 처리 리턴 이런 식
생성 방식 : 그때마다 생성 후 60초동안 놀면 죽여, 정예멤버를 계속 굴려, 커스텀
- 그때마다 생성 후 60초 동안 놀면 죽여
ExecutorService excutorService = Excutors.newCachedThreadPool();
- 정예 멤버를 계속 굴려
ExecutorService excutorService = Excutors.newFixedThreadPool();
- 커스텀
ExecutorService customThreadPool = new ThreadPoolExecutor( 5, //corePoolSize: 작업이 추가될때 corePoolSize까지는 일단 증가 100, //maximumPoolSize: 작업이 얼마나 많이 생성될 수 있는지 제한 수 60L, //해당 작업이 유후상태를 얼만큼 대기할지 TimeUnit.SECONDS, //위의 대기 시간의 단위 new SynchronousQueue<Runnable>() //workQueue: 결국 쓰레드 작업이 저장되는 방식을 결정짓는거임. BlockingQueue 구현체는 여러개가 있다. ); /* ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) */
스레드풀 종료
스레드풀 내부에 생성된 스레드는 메인의 데몬 스레드가 아니기에 무한정 대기 상태가 프로그램 종료와 함께 동시에 일어나지 않음.
그래서 따로 처리를 해줘야함.
shutdown() : 대기풀에 있는 작업까지 모두 처리가 완료될때 비로소 종료. 들어간 작업내용까지는 처리하게 두자~
shutdownNow(): 현재 작업중인 스레드를 interrupt()처리로 중지시키고 종료. 해당 함수의 반환값을 List<Runnable>로 아직 대기중인 녀석들을 리스트로 반환함. 즉.. 개발자가 처리된 작업과 미처리된 작업을 구분할 수 있음~
스레드 풀 케이스
케이스1 Runnable을 구현하여 사용
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class P631_RunnablePool {
public static void main(String[] args) {
//미리 생성된 노가다 작업
String[][] mails = new String[1000][3];
for (int i = 0; i < mails.length; i++) {
mails[i][0] = "admin@my.com";
mails[i][1] = "member" + i + "@my.com";
mails[i][2] = "신상품 입고";
}
//스레드풀을 생성해놓고 총 5개의 정예 멤버
ExecutorService executorService = Executors.newFixedThreadPool(5);
//천개의 노가다 시작
for (int i = 0; i < 1000; i++) {
final int idx = i;
//스레드풀서비스에 execute매서드에 매개변수는 Runnable인터페이스를 구현한 익명 객체
executorService.execute(new Runnable() {
@Override
public void run() {
Thread thread = Thread.currentThread();
String from = mails[idx][0];
String to = mails[idx][1];
String content = mails[idx][2];
System.out.println("[" + thread.getName() + "] " + from + " ==> " + to + " : " + content);
}
});
//반복문을 돌며 execute는 계속해서 실행되고, 그때마다 5개의 스레드가 착착 처리
}
executorService.shutdown();
}
}
케이스2 Callable을 구현하여 처리 스레드 작업의 반환값이 필요할때
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class P633_CallablePool {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(5);
for (int i = 1; i <= 100; i++) {
final int idx = i;
//스레드풀의 서밋 함수는 Future<T>를 반환하고 내부적으로 Callable<T> 익명객체를 필요로함
Future<Integer> future = executorService.submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 1; i <= idx; i++) {
sum += i;
}
Thread thread = Thread.currentThread();
System.out.println("[" + thread.getName() + "] 1부터 " + idx + "까지의 합 = " + sum);
//이 리턴이 담김 future에
return sum;
}
});
try {
//Future는 비동기 작업을 처리하기 위한 클래스로 futre.get()은 내부적으로 Blocking을걸어서
//스레드의 작업 순서를 보장함
int result = future.get();
System.out.println("\t결과 = " + result);
} catch (Exception e) {}
}
executorService.shutdown();
}
}
만약 future.get()을 안하는순간 스레드1의 휘황찬란 막강한 파워를 볼수 있고 작업 순서가 뒤죽박죽 된다.
특히 리턴값이 있다는거는 해당 리턴값으로 뭔가를 하기 때문인거기 때문에 작업순서 보장을 간단히 할 수 있는 Future클래스를 리턴하도록 만들어진거 같다.
'Learn > 이것이 자바다' 카테고리의 다른 글
[이것이 자바다] chapter 13 확인문제 (0) | 2024.12.04 |
---|---|
제네릭, 와일드카드 (0) | 2024.12.03 |
[이것이 자바다] chapter 12 확인 문제 (0) | 2024.12.03 |
자바 라이브러리, base 모듈, 리플렉션, 어노테이션 등 정리 (2) | 2024.12.02 |
[이것이 자바다]chapter14 확인문제 (0) | 2024.11.30 |