Thread 제어
- Thread는 Round Robin 방식을 사용하여 빨리 처리한 Thread가 다음 일을 받는 방식
- 먼저 시작했다고 먼저 끝나지 않기 때문에 Thread를 여러개 실행하다 보면 순서가 제멋대로임
- Thread는 여러일을 동시에 주기적으로 처리해 줄 수 있으나 순서를 제어하기 힘
Synchronized(동기화)
- Thread는 memory를 공유하기 때문에 객체 간의 데이터 간섭이 일어남
- 한 Thread가 사용하고 있 데이터를 다른 Thread가 사용하여 값이 바뀔 수도 있음
- 그래서 한 Thread의 작업이 다 끝나기 전에는 아무도 접근하지 못하게 하는 것이 동기화
예) Vector, HashTable
Synchronized 방법
- Synchronized method : 오직 하나의 스레드만 입장 가능하도록 함(나머지는 메서드 밖에 줄세움)
- Synchronized block : 메서드 안까지는 들어울 수 있으나 특정 영역에서는 줄을 세움
- User1
package chap01.ex04;
public class User1 extends Thread {
//Computer 객체가 클래스 끝날 때 까지 남아있으면 실선
private Computer com;
public User1(Computer com) {
setName("user1");
this.com = com;
}
@Override
public void run() {
com.setScore(500); //user1은 Computer를 이용해 500점을 만듦
}
}
- User2
package chap01.ex04;
public class User2 extends Thread {
private Computer com;
public User2(Computer com) {
setName("user2");
this.com = com;
}
@Override
public void run() {
//user2는 Computer를 이용해 100점을 만듦
com.setScore(100);
}
}
- Computer
package chap01.ex04;
public class Computer {
private int score;
synchronized 하지 않았을 때
public void setScore(int score) {
this.score = score; //들어온 스레드가 Computer를 사용해서 점수를 입력
try {
Thread.sleep(2000); //2초간 잠든후
} catch (InterruptedException e) {
e.printStackTrace();
}
//점수확인
System.out.println(Thread.currentThread().getName()+this.score);
}
//synchronized 사용
//Thread가 PC를 완전히 쓰고 나갈때까지 다른 Thread가 접근하지 못하도록 막음
//1.synchronized mothod : 오직 하나의 스레드만 입장 가능하도록 함(나머지는 줄세움)
public synchronized void setScore(int score) {
this.score = score; //들어온 스레드가 Computer를 사용해서 점수를 입력
try {
Thread.sleep(2000); //2초간 잠든후
} catch (InterruptedException e) {
e.printStackTrace();
}
//점수확인
System.out.println(Thread.currentThread().getName()+this.score);
}
//2.synchronized block : 누구나 메서드 안까지는 들어울 수 있음 but 특정 영역에서는 줄을 세움
public void setScore(int score) {
//여기서 줄서기
synchronized(this) {
this.score = score; //들어온 스레드가 Computer를 사용해서 점수를 입력
try {
Thread.sleep(2000); //2초간 잠든후
} catch (InterruptedException e) {
e.printStackTrace();
}
//점수확인
System.out.println(Thread.currentThread().getName()+this.score);
}
//두 방법의 차이첨은 줄세우는 위치
//mothod : 가게 밖 / block : 가게 안
}
}
- PcRoom
package chap01.ex04;
public class PcRoom {
public static void main(String[] args) {
Computer com = new Computer(); //컴퓨터를 가져옴
//두명의 유저를 호출해 사용
User1 user1 = new User1(com);
User2 user2 = new User2(com);
user1.start();
user2.start();
}
}
Thread State
- Thread는 생성부터 종료까지의 상태값이 있음
- 우리는 getState()를 통해 현재 상태를 알 수 있음
- NEW >> RUNNABLE >> 실행 >> TERMINATED
상태 | 열거상수 | 설명 |
객체생성 | NEW | 스레드 객체 생성, start( )호출 전 |
실행대기 | RUNNABLE | 실행 상태로 언제든지 이동 할 수 있는 상태 |
일시 정지 | WATING | 다른 스레드가 통지 할 때 까지 기다리는 상태 |
TIMED_WATING | 주어진 시간 동안 기다리는 상태 | |
BLOCKED | 사용하려는 객체의 Lock이 풀릴 때 까지 기다리는 상태 | |
종료 | TERMINATED | 실행을 마친 상태 |
- WorkThread
package chap01.ex05;
public class WorkThread extends Thread{
@Override
public void run() {
//실행 >> 1.5초 휴식 >> 실행
long cnt = 0;
for(int i = 0; i<1000000000; i++) {
cnt++;
}
try {
Thread.sleep(1500);
} catch (InterruptedException e) {
e.printStackTrace();
}
cnt = 0;
for (int i = 0; i < 1000000000; i++) {
cnt++;
} //for문이 한번 돌고 쉬는 동안의 상태가 콜솔에 찍힘
}
}
- Main
package chap01.ex05;
public class Main {
private static Thread.State state;
public static void main(String[] args) {
Thread work = new WorkThread();
state = work.getState();
//start하기 전에는 NEW
System.out.println("thread state : " + state);
work.start();
while (true) {
state = work.getState();
System.out.println("thread state : " + state); // console - thread state : RUNNABLE
//for문이 한번 돌고 쉬는동안의 상태가 콜솔에 찍힘(실행+RUNNABLE 반복)
if(state == Thread.State.TERMINATED) {
break;
}
}
}
}
# Thread가 실행될 때 모든 동작이 한번에 이어져서 실행되는 것이 아니라 한동작씩 끊어서 실행하는 것이 이어진 것 처럼 보임 / 그래서 for문이 한 번 돌 때마다 잠깐 RUNNABLE 상태가 되는데 그 때 상태가 콘솔에 찍힌 것
Thread Control
- Thread는 유용한 기능이지만 예상대로 움직이지 않아 Thread Control을 위한 method들이 존재
sleep | 주어진 milliseconds 동안 Thread를 일시정지 | Thread.sleep(1000) |
yield | 특정 Thread에게 제어권을 양보(즉각 반응하지 않으면 자신이 실행) | Thread.yield() |
join | 다른 Thread의 종료를 기다린 후에 실행할 때 사용 | Thread.join() |
wait | 스레드를 일시정지 상태로 만듦(다른 Thread notify() 후 해당 Thread가 쉼) | |
notify | 일시정지된 Thread 중 하나를 실행 대기상태로 바꿈(다른 Thread를 깨움) | |
notifyAll | 일시정지된 모든 Thread 를 실행 대기 상태로 바꿈 | |
Stop flag | 무한 반복문 강제 종료(소수) | |
interrupt |
무한 반복문 강제 종료(Thread 그룹) |
# wait(), notify(), notifyAll()은 synchronized 영역 안에서 사용해야함
Yield - 양보하지만 들어올 시간을 길게 주지 않음(바로 실행하지 않으면 자신이 다시 실행)
- WorkThread
package chap01.ex06;
public class WorkThread extends Thread {
boolean stop = false;
boolean yield = false;
@Override
public void run() {
while(!stop) {
System.out.println(getName()+"동작");
if(yield) {
System.out.println(getName()+"가 양보");
Thread.yield();
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(getName()+"중지");
}
}
- MainThread
package chap01.ex06;
public class MainThread {
public static void main(String[] args) throws InterruptedException {
WorkThread thA = new WorkThread();
thA.setName("thread A");
WorkThread thB = new WorkThread();
thB.setName("thread B");
//스레드 실행
thA.start();
thB.start();
thA.yield = true; //A에게 양보시키고
Thread.sleep(500); //0.5초 후
thA.yield = false; //B에게 양보시키고
thB.yield = true; //0.5초 후
Thread.sleep(500);
//둘다 정지
thA.stop = true;
thB.stop = true;
}
}
Join
- OperThread
package chap01.ex07;
public class OperThread extends Thread {
private int sum;
public int getSum() {
return sum;
}
public void setSum(int sum) {
this.sum = sum;
}
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
sum += i;
}
}
}
- Main
package chap01.ex07;
public class Main {
public static void main(String[] args) throws InterruptedException {
System.out.println("1~100까지의 합");
OperThread oper = new OperThread();
oper.start();
//계산이 될때까지 기다리라고 해야함
oper.join(); //blocking(가장 강력)
// 답 : 0인 이유 - 계산하는 스레드보다 출력하는 스레드가 빨랐기 때문
System.out.println("답 : " + oper.getSum());
}
}
Wait, Notify
- wait(), notify(), notifyAll()은 synchronized 영역 안에서 사용해야함
- synchronized 하지 않으면 동시에 여러개의 스레드가 wait()해버릴 수 있음(모든 동작 멈춤)
- WorkObj
package chap01.ex08;
public class WorkObj {
public synchronized void work() {
System.out.println(Thread.currentThread().getName()+"가 들어옴");
notify(); //1.누군가를 깨움
try {
wait(); //스스로 잠듦
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
- WorkThread
package chap01.ex08;
public class WorkThread extends Thread {
private WorkObj obj;
public WorkThread(WorkObj obj) {
this.obj = obj;
}
@Override
public void run() {
for (int i = 0; i <= 5; i++) {
obj.work();
}
}
}
- Main
package chap01.ex08;
public class Main {
public static void main(String[] args) {
WorkObj obj = new WorkObj();
WorkThread thA = new WorkThread(obj);
WorkThread thB = new WorkThread(obj);
thA.setName("thread A");
thB.setName("thread B");
thA.start();
thB.start();
}
}
Stop flag, Interrupt
- thread는 run()의 실행내용이 모두 실행 되면 종료
- 무한 반복문으로 이루어진 경우는 강제종료 필요
- stop()으로 강제종료할 수 있으나 현재는 사용중지를 권고(다 처리되지 못하고 종료되기 때문)
- Inter
package chap01.ex09;
public class Inter extends Thread {
@Override
public void run() {
//인터럽트 예외 활용(sleep을 사용하고, try-catch를 활용해야 함)
try {
while(true) {
System.out.println("inter thread 실행중...");
Thread.sleep(1); //sleep이 동작되는 동안은 외부 인터럽트에 의한 예외가 발생할 수 있음
}
} catch (InterruptedException e) {
System.out.println("자원 정리 또는 반납");
System.out.println("안전하게 실행종료");
}
=====================================================================================================
//if문으로 interrupted가 실행될 경우 break
while(true) {
System.out.println("stop flag thread 실행중...");
if(Thread.interrupted()) {
break;
}
}
System.out.println("자원 정리 또는 반납");
System.out.println("안전하게 실행종료");
}
}
- StopFlag
package chap01.ex09;
public class StopFlag extends Thread {
private boolean stop = false;
public void setStop(boolean stop) {
this.stop = stop;
}
@Override
public void run() {
while(!stop) {
System.out.println("stop flag thread 실행중...");
}
System.out.println("자원 정리 또는 반납");
System.out.println("안전하게 실행종료");
}
}
- MainThread
package chap01.ex09;
public class MainThread {
public static void main(String[] args) throws InterruptedException {
//stop flag 활용(소수일때)
StopFlag stopFlag = new StopFlag();
stopFlag.start();
Thread.sleep(1000);
//stopFlag.stop(); : 사용 금지 권고(사라질 예정)
stopFlag.setStop(true);
//interruped exception 활용(thread 그룹 전체에 적용할때)
Inter inter = new Inter();
inter.start();
Thread.sleep(1000);
inter.interrupt(); //강제로 인터럽트 예외를 발생시키는 메서드
}
}
Demon Thread
- WorkThread와 비슷하나 life cycle이 다름
- WorkThread는 MainThread의 종료와 관계없이 자신의 일이 다 끝난 후 종료됨
- but DemonThread는 MainThread가 종료될 때 함께 종료됨
- DemonThread
package chap01.ex10;
public class DemonThread extends Thread {
@Override
public void run() {
while(true) {
System.out.println("demon thread");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
- Main
package chap01.ex10;
public class Main {
public static void main(String[] args) throws InterruptedException {
System.out.println("mainThread 시작");
DemonThread demon = new DemonThread();
//workThread는 mainThread가 죽어도 계속 활동
//demonThread로 변경하면 mainThread 종료 시 함께 종료시킬 수 있음
demon.setDaemon(true); //demonThread로 변경
demon.start();
Thread.sleep(3000); //mainThread
System.out.println("mainThread 종료");
}
}
Thread Pool
- Thread Pool은 Thread 대여소 역할
- Thread를 계속 유지하거나 한번 사용하고 버릴 경우 효율적이지 못함
- Thread Pool을 빌려서 사용하거나 Thread를 재활용할 경우 효율적임
- Thread를 생성해 가지고 있다가 주어진 작업을 줄세워 순서대로 빌려주고, 사용 후 돌려 받거나 제거함
- Thread Pool은 ExecutorService 객체를 통해 생성
- 생성시에는 초기/코어/최대 스레드 수를 명시
- 초기 스레드 수 : 객체 생성 시 기본적으로 생성되는 스레드 수
- 코어 스레드 수 : 풀에 최소한으로 유지해야 할 스레드 수
- 최대 스레드 수 : 스레드 풀에서 관리하는 최대 스레드 수
Cashed | - 1개 이상의 Thread가 추가되어 60초 동안 놀고 있으면 스레드가 제거됨 - (처음)대기 >> 손님이 오면 Thread 제작 후 대여 >> 60초 동안 아무도 안 빌려가면 >> 삭제 및 재생성 |
Fixed | - 사용한 Thread를 작업이 끝난 후 반환하면 다음 작업 실행 |
- ThreadPool
package chap01.ex11;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPool {
public static void main(String[] args) {
//thread pool 생성방법 1 - Cached
ExecutorService pool1 = Executors.newCachedThreadPool();
//기본적으로 n개 스레드 유지
int n = Runtime.getRuntime().availableProcessors();
System.out.println("가용 스레드 : "+n);
//thread pool 생성방법 2 - Fixed
ExecutorService pool2 = Executors.newFixedThreadPool(n);
}
}
Runnable & Callable
- Thread Pool의 작업(할일) 생성
- 차이점 : return값의 유무
Runnable 구현 클래스 | Callable 구현 클래스 |
Runnable Task = new Runnable( ){ @Override public void run( ){ //스레드가 처리할 내용 } } |
Callable<T> task = new Callable<T>{ @Override public T call( ) throws Exception{ //스레드가 처리할 내용 return T; } } |
submit() & execute()
- 생성된 작업 실행
execute() | void | - 작업처리 결과를 받지 못함 - 작업처리 도중 예외상황에 Thread를 종료하고 풀에서 Thread 제거 |
submit() | Future | - 작업처리 결과를 Future를 통해 반환됨 - 작업처리 도중 예외상황에도 Thread를 종료하지 않고 재사용 - Callable은 리턴값이 있기 때문에 submit만 사용가능 |
Thread Pool 종료
- MainThread가 종료되더라도 계속 실행상태로 남아 있어 종료메서드를 사용해야함
shutdown( ) | 처리중인 작업내용을 모두 마무리하고 스레드 풀 종료 |
shutdownNow( ) | 처리중인 작업의 마무리 여부 상관없이 interrupt 시켜 바로 종료 |
awaitTermination( ) | shutdown( ) 호출 후 설정시간 만큼 기다려준 후 종료(shutdown과 함께 사용하지 않으면 종료X) 모든 Thread가 작업을 완료했는지의 여부를 boolean 타입으로 반환 |
- RunMain
package chap01.ex12;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.RunnableFuture;
import java.util.concurrent.TimeUnit;
public class RunMain {
public static void main(String[] args) throws InterruptedException {
//1.스레드 풀 생성
int n = Runtime.getRuntime().availableProcessors();
ExecutorService pool = Executors.newFixedThreadPool(n);
//2.어떤 작업에 쓸건지 명시(작업 시 반환이 없으면 Runnable)
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("runnable 처리");
}
};
//3.풀로부터 스레드를 빌려서 작업 실행 >> 대답할 필요없음 >> 스레드가 예외를 발생시키면 죽임
pool.execute(runnable);
//스레드가 다 돌아왔는지를 boolean 값으로 리턴
pool.shutdown(); //
boolean end = pool.awaitTermination(1L, TimeUnit.SECONDS);
System.out.println(end);
}
}
- CalMain
package chap01.ex12;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
public class CallMain {
public static void main(String[] args) throws Exception {
//1.스레드 풀 생성
int n = Runtime.getRuntime().availableProcessors();
ExecutorService pool = Executors.newFixedThreadPool(n);
System.out.println(n);
//2.작업내용 명시(Callable : 작업 후 반환값이 있는 경우)
Callable<String> callable = new Callable<String>() {
@Override
public String call() throws Exception {
System.out.println("callable 처리");
return "완료";
}
};
//3.풀로부터 스레드를 빌려서 작업 실행 >> 대답(Future로) >> 스레드가 예외를 발생시켜도 데려옴
Future<String> result = pool.submit(callable);
String msg = result.get();
System.out.println("실행결과 : "+msg);
//4.영업종료하고 문 닫을 때
pool.shutdown(); //모든 스레드가 반남될 때 까지 기다림
//pool.shutdownNow(); //돌아오지 않는 스레드는 다 죽임(사용지양)
pool.awaitTermination(10L, TimeUnit.SECONDS); //10초 동안 기다렸다가 돌아오지 않으면 죽임
//awaitTermination은 shutdown과 함께 써야함
}
}
Thread Pool Blocking
- runnable | callable 관계없이 작업완료 후 Future 객체를 반환할 수 있음
- Future 객체를 가져오는 get() 메서드는 join(하나의 Thread가 종료될 때 까지 기다림)과 같은 역할을 수행
- get()가 값을 반환할 때 까지 Thread를 종료시키지 않고 기다림(자동으로 blockig됨)
- RunMain
package chap01.ex13;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class RunMain {
public static void main(String[] args) throws InterruptedException, ExecutionException {
ExecutorService pool = Executors.newCachedThreadPool();
Runnable run = new Runnable() {
@Override
public void run() {
for (int i = 0; i <= 10; i++) {
System.out.println("작업처리"+i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
//pool.execute(run);
//blocking하기 위해 반환값이 없지만 submit을 사용
//get까지 사용해주어야 blocking됨
Future<?> future = pool.submit(run);
future.get();
System.out.println("작업완료");
pool.shutdown();
}
}
- CalMain
package chap01.ex13;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
public class CallMain {
public static void main(String[] args) throws InterruptedException, ExecutionException {
//Callable을 이용해 1~100까지 더하는 내용 반환
//1.스레드 풀 생성
ExecutorService pool = Executors.newCachedThreadPool();
//2.작업내용 명시
Callable<Integer> callable = new Callable<Integer>() {
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 0; i <= 100; i++) {
sum += i;
}
return sum;
}
};
//3.스레드 풀 빌려서 작업실행
Future<Integer> result = pool.submit(callable);
//Future 객체를 반환받는 Callable은 응답을 받아야하기 때문에 자동으로 blocking 됨
//버전마다 다르기때문에 실제로는 get()까지 사용해야 blocking이라고 생각해야함
System.out.println("1~100까지의 합 : "+result.get());
//4.종료
pool.shutdown();
}
}
'코딩도전기 > JAVA' 카테고리의 다른 글
CODO Day31_JAVA(Network-Sender&Receiver) (0) | 2023.03.17 |
---|---|
CODO Day30_JAVA(Network) (0) | 2023.03.16 |
CODO Day29_JAVA(Multi Thread) (0) | 2023.03.14 |
CODO Day13_JAVA(Overload/멤버 호출/Static/Final/Import/접근제한자) (0) | 2023.02.17 |
CODO Day12_JAVA(type/method/문자열비교/switch/while/배열/클래스(API/Member(생성자)) (0) | 2023.02.16 |