본문 바로가기
코딩도전기/JAVA

CODO Day29_JAVA(Thread 제어)

by 코도꼬마 2023. 3. 15.

Thread 제어

  • Thread는 Round Robin 방식을 사용하여 빨리 처리한 Thread가 다음 일을 받는 방식
  • 먼저 시작했다고 먼저 끝나지 않기 때문에 Thread를 여러개 실행하다 보면 순서가 제멋대로임
  • Thread는 여러일을 동시에 주기적으로 처리해 줄 수 있으나 순서를 제어하기 힘

 

Synchronized(동기화)

  • Thread는 memory를 공유하기 때문에 객체 간의 데이터 간섭이 일어남
  • 한 Thread가 사용하고 있 데이터를 다른 Thread가 사용하여 값이 바뀔 수도 있음
  • 그래서 한 Thread의 작업이 다 끝나기 전에는 아무도 접근하지 못하게 하는 것이 동기화
    예) Vector, HashTable

 

Synchronized 방법

  1. Synchronized method  :  오직 하나의 스레드만 입장 가능하도록 함(나머지는 메서드 밖에 줄세움)
  2. 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();
	}
}