스레드(Thread)

Joy🌱·2023년 1월 10일
0

☕ Java

목록 보기
35/40
post-thumbnail

💁‍♀️ 스레드(Thread)란,
프로세스 내에서 할당 된 자원을 이용해 실제 작업을 수행하는 작업 단위

  • 모든 프로세스는 하나 이상의 스레드를 가지며 각각 독립적인 작업단위를 지님

💁‍♀️ 프로세스(Process)란,
실행중인 프로그램

  • 싱글 스레드의 경우 메인 스레드가 종료되면 프로세스도 종료되지만
    멀티 스레드의 경우 실행중인 스레드가 하나라도 있다면 프로세스가 종료되지 않음

💁‍♀️ 싱글 스레드(Single Thread) & 멀티 스레드(Multi Thread)란,

  • 싱글 스레드
    : 메인 스레드 하나만 가지고 작업을 처리 → 한 작업씩 차례대로 처리해 나감
  • 멀티 스레드
    : 메인 스레드 외의 추가적인 스레드를 이용하여 병렬적으로 작업을 처리

🙋‍ 잠깐 ! 멀티 스레드의 장점과 단점에는 무엇이 있나요?

  • 장점
    • 자원을 보다 효율적으로 사용 가능
    • 애플리케이션의 응답성 향상
    • 작업이 분리되어 코드가 간결
  • 단점
    • 동기화(Synchronization)에 주의
    • 교착상태(dead-lock)가 발생하지 않도록 주의
    • 프로그래밍 시 고려해야 할 사항이 많음

👀 Thread

📍 Thread를 생성하는 방법

1) Thread 클래스를 상속
2) Runnable 인터페이스를 구현

◼ Car Class

// Thread 클래스 상속
public class Car extends Thread {

	@Override
	public void run() {
		// Car thread로 수행할 작업
		for(int i = 0; i < 1000; i++) {
			System.out.println("Car driving...");
			
			try {
				// 스레드를 지연시키는 메소드 sleep() 사용
				Thread.sleep(1); 
                >>> : 출력할 때마다 1밀리세컨씩 멈췄다가 동작
                >>> sleep()InterruptedException 핸들링 해야함
                
			} catch (InterruptedException e) {
				e.printStackTrace();
			} 
		}
	}
}

◼ Tank Class

// Thread 클래스 상속
public class Tank extends Thread {
	
	@Override
	public void run() {
		for(int i = 0; i < 1000; i++) {
			System.out.println("Tank shooting...");
            
			try {
				Thread.sleep(1);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}

◼ Plane Class

// Runnable 인터페이스 구현
public class Plane implements Runnable { 

	@Override
	public void run() {
		for(int i = 0; i < 1000; i++) {
			System.out.println("Plane flight...");
			try {
				Thread.sleep(1);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}

◼ Application Class

// way1. 작성한 세 개의 클래스를 이용해서 인스턴스 생성
Car car = new Car();
Tank tank = new Tank();
Plane plane = new Plane();
		
// Thread 타입의 인스턴스로 변환
Thread t1 = car;
Thread t2 = tank;
Thread t3 = new Thread(plane); 
>>> Runnable 인터페이스를 구현한 클래스는 Thread 클래스의 생성자에 인자로 인스턴스를 전달해서 
>>> Thread 인스턴스를 생성
		
// way2. 다형성을 적용해서 Thread 타입으로 바로 인스턴스 생성
Thread t4 = new Car();
Thread t5 = new Tank();
Thread t6 = new Thread(new Plane());
>>> 기본적으로 1~10의 우선 순위 중 5의 우선 순위를 가짐
System.out.println("t4 우선순위 : " + t4.getPriority());
System.out.println("t5 우선순위 : " + t5.getPriority());
System.out.println("t6 우선순위 : " + t6.getPriority());
// 모두 기본값 5 출력
		
// 우선 순위 변경 
t4.setPriority(Thread.MAX_PRIORITY); >>> MAX_PRIORITY은 10이 상수로 지정되어있음
t6.setPriority(Thread.MIN_PRIORITY); >>> MIN_PRIORITY은 1이 상수로 지정되어있음
		
System.out.println("t4 우선순위 : " + t4.getPriority()); // 10
System.out.println("t5 우선순위 : " + t5.getPriority()); // 5
System.out.println("t6 우선순위 : " + t6.getPriority()); // 1
		
>>> run() 메소드를 호출하면 별도의 스레드로 동작하지않고 '메소드를 호출하는 방식'으로 동작
//t4.run();
//t5.run();
//t6.run();
>>> 메소드를 호출하는 것과 같이 하나가 끝나야 다음 것 출력
		
>>> 스레드를 동작시키기 위해서 start() 호출
t4.start();
t5.start();
t6.start();
>>> 각각의 스레드가 한번에 동작이 되어서 훨씬 빠르게 모두 출력이 됨 
>>> 스레드가 각자 자기 할일을 함 (마구 섞여서 출력되는 것을 보고 알 수 있음)
>>> 우선순위가 크게 눈에 띄진 않음
		
// 지정한 스레드가 종료될 때까지 현재 메인 스레드의 종료를 대기 시킴
try {
	t4.join(); 
	t5.join();
	t6.join();	 >>> join() : InterruptedException 핸들링 해야함
} catch (InterruptedException e) {
	e.printStackTrace();
}
	
System.out.println("------------------------- Main END !!"); 

📌 Ref.

* try&catch 구문없이 실행(종료 대기X) 
  : 메인 스레드도 자기 할일을 하기 때문에 다른 스레드를 기다리거나 하지않고 바로 Main END 출력문을 출력
* try&catch 구문 생성 후 실행(종료 대기O) 
  : 세 가지 스레드가 모두 실행된 뒤, 메인 스레드가 Main END 출력문을 출력 

💻 Mini Console

// try&catch 구문 생성 후 실행
Plane flight...
Car driving...
Car driving...
Plane flight...
Tank shooting...
Plane flight...
Car driving...
Plane flight...
Car driving...
------------------------- Main END !!
// 불규칙적으로 출력되다가 마지막에 메인 스레드가 Main END 출력 (메인 스레드가 기다려줌)

👀 데몬 스레드(Daemonthread)

💁‍♀️ 데몬 스레드(Daemonthread)란,
데몬 스레드(Daemon Thread) 다른 스레드의 작업을 돕는 보조적인 역할을 수행하는 스레드. 데몬 스레드 이외의 스레드들이 모두 종료되면 데몬 스레드는 강제적으로 종료 (메인 스레드가 종료될 때 함께 종료)

  • 데몬 스레드가 될 스레드의 레퍼런스 변수에 setDaemon(true)를 호출하여 생성
    • 단, start() 메소드 호출 전에 setDaemon(true)를 호출
      (그렇지 않으면IllegalThreadStateException이 발생)

◼ CountDown Class

// 0.5초씩 멈추면서 실행되는 50부터 1씩 적어지는 카운트다운
public class CountDown extends Thread {

	@Override
	public void run() {
		for(int i = 50; i > 0; i--) {
			try {
				Thread.sleep(500);	>>> 0.5초씩 멈췄다가 동작
				System.out.println(i);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}

◼ Application Class

Thread t = new CountDown();

Scanner sc = new Scanner(System.in);
		
// start()를 호출하기 전 데몬 스레드로 설정
t.setDaemon(true); 
t.start();
		
System.out.print("아무거나 입력하세요 : ");
String str = sc.nextLine();
System.out.println("입력한 값 : " + str);
System.out.print("-------------------------- Main END !!");

📌 Ref.

* 데몬 스레드로 설정하면 스캐너로 값을 입력했을 때 메인 스레드와 함께 카운트다운이 종료되지만, 
  데몬 스레드를 설정하지 않았을 경우 입력과는 관계없이 계속 카운트다운이 마지막까지 진행됨

💻 Mini Console

// 데몬스레드로 설정한 경우의 출력문
아무거나 입력하세요 : 50
49
48
47
그만하십쇼!46
45
44
43

입력한 값 : 그만하십쇼!
-------------------------- Main END !!	

👀 동기화(Synchronization)

💁‍♀️ 동기화(Synchronization)란,
한번에 한 개의 스레드만 프로세스 공유 자원(인스턴스)에 접근할 수 있도록
락(Lock)을 걸어 다른 스레드가 진행중인 작업에 간섭하지 못하도록 하는 것


1) 생산자가 생산을 해야만 소비자가 소비를 할 수 있도록 스레드를 스케쥴링

◼ Producer Class

// 공유 데이터 값을 넣는 일을 하는 공급자(생산자) 클래스
public class Producer extends Thread {
	
    // 필드
	private Buffer criticalData;
	
    // 생성자
	public Producer(Buffer buffer){
		this.criticalData = buffer;
	}
	
	@Override
	public void run(){
		
		for (int i = 1; i <= 10; i++) {
			criticalData.setData(i); >>> buffer 클래스의 setData() 메소드 호출
			
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}

◼ Consumer Class

// 공유자원을 꺼내서 사용하는 소비자 클래스
public class Consumer extends Thread {
	
    // 필드
	private Buffer criticalData;
	
    // 생성자
	public Consumer(Buffer buffer){
		this.criticalData = buffer;
	}
	
	@Override
	public void run(){
		
        for(int i = 1; i <= 10; i++){
			criticalData.getData(); >>> buffer 클래스의 getData() 메소드 호출
			
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}

◼ Buffer Class

public class Buffer {
	
	private int data;
	private boolean empty = true;
	
	public synchronized void getData(){ 
    >>> synchronized : 동기화 메소드
		
		// 현재 값이 없으면 대기 상태, 값이 있으면 소비 
		if(empty){
			try {				
				System.out.println("getData wait");
                >>> 생산보다 소비가 먼저 나오면 (스레드는 무작위로 출력되므로) 이 출력문을 출력하고, 
                >>> wait()으로 기다리게 하여 생산이 먼저 나오도록 스레드를 컨트롤
				
                >>> wait() : 스레드를 일시 정지. notify() 호출 되면 블럭 상태를 빠져 나오게 됨
				wait();	
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		
		// 값이 있으면 소비하고, 다시 empty 는 true로 바꿈
		System.out.println("소비자 : " + data + " 번 상품 소비하였습니다.");
		empty = true;
		
		// 스레드를 다시 실행 대기 상태로 만듬
		notify();
	}
	public synchronized void setData(int data){
		
		// 현재 값이 있으면 대기상태로, 값이 없으면 생산
		if(!empty){
			try {
				System.out.println("setData wait");
				wait(); 
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		
		// 값이 없으니 생산하고, empty는 false로 바꿈 
		this.data = data;
		System.out.println("생산자 : " + data + " 번 상품을 생산하였습니다.");
		empty = false;
		
		// 스레드를 다시 실행 대기 상태로 만듬
		notify();
	}
}

◼ Application Class

>>> 여러 개의 스레드가 하나의 공유 자원을 사용할 때 동기화 처리를 할 수 있음
		
// 공유 자원 인스턴스 생성
Buffer buffer = new Buffer();
		
// 동일한 버퍼 인스턴스를 공유하는 생산자와 소비자 스레드 생성
Thread t1 = new Producer(buffer); // 생산자
Thread t2 = new Consumer(buffer); // 소비자
		
// Producer와 Consumer 클래스 만들고 난 뒤 스레드 생성
t1.start();
t2.start();

💻 Mini Console

생산자 : 1 번 상품을 생산하였습니다.
소비자 : 1 번 상품 소비하였습니다.
생산자 : 2 번 상품을 생산하였습니다.
소비자 : 2 번 상품 소비하였습니다.
getData wait
생산자 : 3 번 상품을 생산하였습니다.
소비자 : 3 번 상품 소비하였습니다.
생산자 : 4 번 상품을 생산하였습니다.
소비자 : 4 번 상품 소비하였습니다.
// 2번 상품을 소비한 후, 생산이 되지 않은 상태에서 소비가 또 이루어지려고 하여 
// getData wait 출력 후 생산을 먼저 출력하도록 함

profile
Tiny little habits make me

0개의 댓글

관련 채용 정보