JAVA

[Java] Thread 쓰레드

soyeonisgood 2023. 2. 13. 07:59

Thread

  • Process
    • 실행 중인 프로그램
    • 프로그램을 실핼하면 OS로부터 실행에 필요한 자원(메모리)을 할당받아 프로세스가 됨
    • 프로그램을 수행하는 데 필요한 자원(데이터,메모리), 쓰레드로 구성됨
  • Thread
    • 프로세스의 자원을 이용해서 실제로 작업을 수행
  • Multi Thread
    • 하나의 프로세스 내에서 여러 쓰레드가 동시에 작업 수행
    • 장점
      • CPU 사용률 향상
      • 자원을 효율적으로 사용
      • 사용자에 대한 응답성 향상
      • 작업이 분리되어 코드 간결
    • 단점
      • 동기화 (Synchronization)
      • 교착상태 (deadlock)
        • 두 쓰레드가 자원을 점유한 상태에서 서로 상대편이 점유한 자원을 사용하려고 기다리느라 진행이 멈춰있는 상태
    • 서버 프로그램
      • 여러 사용자에게 서비스해줌 → 멀티쓰레드로 작성하는 것 필수!
      • 하나의 서버 프로세스가 여러 개의 쓰레드를 생성해서 쓰레드와 사용자의 요청이 일대일로 처리되도록 프로그래밍 해야함.
  • Thread 구현 방법
    • Thread 클래스 상속
      • 다른 클래스 상속 불가 (다중 상속 X)
    • Runnable 인터페이스 구현
      • run() 만 정의되어 있는 간단한 인터페이스
      • 추상 메소드인 run()의 몸통을 만들어줘야함.
    • Thread 를 구현한다는 것은, 쓰레드를 통해 작업하고자 하는 내용으로 run() 를 작성하는 것!
// Thread 클래스 상속
class MyThread extends Thread {
	public void run() { } // run() 오버라이딩
}

// Runnable 인터페이스 구현
class MyThread implements Runnable {
	public void run() { } // run() 구현
}
package pratice.threadEx;

public class ThreadEx1 {
    public static void main(String[] args) {
        ThreadEx1_1 t1 = new ThreadEx1_1();

        Runnable r = new ThreadEx1_2();
        Thread t2 = new Thread(r);

        t1.start();
        t2.start();
    }
}

class ThreadEx1_1 extends Thread {
    public void run() {
        for (int i=0; i<5; i++) {
            System.out.println(getName()); // Thread의 getName() 호출.
        }
    }
}

class ThreadEx1_2 implements Runnable {
    public void run() {
        for (int i=0; i<5; i++){
            // Thread.currentThread() // 현재 실행 중인 Thread 반환.
            System.out.println(Thread.currentThread().getName());
        }
    }
}
  • start() : 쓰레드의 실행
    • 쓰레드는 start() 를 호출하여 실행
    • 일단 실행 대기 상태에서 있다가, 자신의 차례가 되어야 실행됨.
      • 실행 순서는 OS의 스케쥴러가 작성한 스케쥴에 의해 결정됨.
    • 종료된 쓰레드는 다시 실행할 수 없다.
      • start() 는 한 번만 호출됨
      • 종료된 쓰레드의 작업을 다시 수행하려면, 새로운 쓰레드를 생성한 후 start() 를 호출해야함.
        • 두 번 이상 호출하면 IllegalThreadStateException 발생
  • start() 와 run()
    • 쓰레드 실행은 run()이 아닌, start()
    • run() 호출은 생성된 쓰레드를 실행시키는 것이 아닌, 단순히 클래스에 선언된 메소드 호출
    • strat()는 호출스택(start stack)을 생성한 다음 run() 을 호출해서, 생성한 호출스택에 run()이 첫 번째로 올라가게 한다!
      • 호출스택의 가장 위에 있는 메소드가 현재 실행 중인 메소드. 다른 메소드들은 대기상태.
      • run() 수행이 종료된 쓰레드는 호출스택이 비워지고 이 쓰레드가 사용하던 호출스택은 사라진다
  • 싱글코어 싱글쓰레드 vs 싱글코어 멀티쓰레드
    • 싱글코어 싱글쓰레드
      • 단순 CPU만 사용하는 계산 작업 효율적
    • 싱글코어 멀티쓰레드
      • 2개 이상의 쓰레드가 번갈아 가면서 작업 수행 = 동시에 여러 작업 처리되는 것 처럼 느낌
      • 쓰레드간의 작업 전환(context switching)에 시간 소요
      • 다음 실행에 대한 정보를 저장하고 읽어 오는 시간
  • 싱글코어 멀티쓰레드 vs 멀티코어 멀티쓰레드
    • 싱글코어 싱글쓰레드
      • 멀티쓰레드여도 하나의 코어가 번갈아가면서 수행하므로, 두 작업이 절대 겹치지않음 (병렬)
    • 멀티코어 멀티쓰레드
      • 두 작업을 수행하면, 동시에 두 쓰레드 수행(병행)됨 → 겹치는 부분이 발생하므로 자원(console)을 놓고 두 쓰레드가 경쟁
      • 두 쓰레드가 서로 다른 자원을 사용하는 작업에 효율적
        • ex) 사용자로부터 데이터 입력 받는 작업, 네트워크로 파일 주고받는 작업, 프린터 파일 출력 등 외부기기의 입출력 필요로 하는 경우
package pratice.threadEx;

import javax.swing.*;

// Multi Thread Ex
public class Thread7 {
    public static void main(String[] args) {
        ThreadEx7_1 th1 = new ThreadEx7_1();
        th1.start();

        String input = JOptionPane.showInputDialog("값을 입력하세요.");
        System.out.println("입력한 값: "+input);
    }
}

class ThreadEx7_1 extends Thread {
    public void run() {
        for(int i=10; i>0; i--){
            System.out.println(i);
            try {
                sleep(1000);
            } catch (Exception e) {

            }
        }
    }
}
  • 쓰레드의 우선순위
    • 작업의 중요도에 따라 쓰레드의 우선순위를 서로 다르게 지정하여 특정 쓰레드가 더 많은 작업 시간을 갖도록 할 수 있음
    • 우선순위 범위: 1~10
    • 우선순위는 생성한 쓰레드로부터 상속 받음
    • main메소드의 우선 순위는 자동으로 5
    • 쓰레드 실행 전(start())에만 우선순위 변경 가능
    • 멀티코어에선, 쓰레드의 우선순위에 따른 차이가 거의 없음.
      • 쓰레드에 높은 우선순위를 주어도 더 많은 실행시간, 기회를 갖게 될 것이라고 기대할 수 없음
      • → 작업에 우선수누이를 두어 PriorityQueue에 저장하고, 우선순위가 높은 작업이 먼저 처리되도록 하는 것이 나을 수 있다!
void setPriority(int newPriority)
int getPriority()
  • 데몬 쓰레드 (Demon Thread)
    • 일반 쓰레드의 작업을 돕는 보조적인 역할을 수행하는 쓰레드
      • ex) 가비지 컬렉터, 워드프로세서의 자동저장, 화면자동갱신 …
    • 무한루프와 조건문을 이용해서 실행 후 대기하고 있다가 특정 조건이 만족되면 작업을 수행하고 다시 대기하도록 작성
    • 데몬쓰레드가 생성한 쓰레드는 자동으로 데몬쓰레드가 됨
    • setDeamon()은 반드시 start() 호출 전에 실행되어야함.
boolean isDeamon() // 데몬쓰레드인지 확인

void setDeamon(boolean on) // on=true로 지정하면 데몬쓰레드 또는 사용자 쓰레드로 변경. 
  • sleep(): 일정시간동안 쓰레드 멈춤
    • sleep()에 의해 일시정지 상태가 된 쓰레드는 지정된 시간이 다 되거나 interrupt()가 호출되면(=Interrupt Exception 발생) 잠에서 깨어나 실행대기 상태가 됨
    • sleep() 호출 시에는 꼭 try~catch문으로 예외 처리 필수 !!!
      • try~catch문을 포함하는 새로운 메소드를 만들어서 사용하면 간편
      void delay(long millis) {
      	try {
      		Thread.sleep(millis);
      	} catch (InterruptedException e) { }
      }
      
  • interrupt() 와 interrupted(): 쓰레드의 작업 취소
    • 진행 중인 쓰레드의 작업이 끝나기 전에 취소시킬 때 사용
      • ex) 큰 파일 다운로드 시 너무 오래 걸리면 다운로드 취소
    • 쓰레드에게 작업을 멈추라고 요청
      • 강제 종료X. 단지 요청만.
      • 쓰레드의 interrupted(인스턴스 변수) 상태를 바꾸는 것일 뿐.
    • interrupted(): interrupt() 가 호출되었는지 알려줌. 호출X-false, 호출O: true
    • while 문 조건식 “!” 유의
    • 쓰레드가 sleep(), wait(), join()에 의해 일시정지 상태(WAITING) 에 있을 때, 해당 쓰레드에 대해 interrupt()를 호출하면 해당 함수들에서 Interrupted Exception이 발생하고 쓰레드는 실행대기 상태(RUNNABLE)로 바뀐다.
      • 즉, 멈춰있던 쓰레드를 깨워서 실행가능한 상태로 만드는 것.
    • Thread.sleep(1000) 에서 InterruptException이 발생했으므로, 쓰레드가 sleep()으로 잠시 멈춰있을 때 intterupt()를 호출하면 InterruptException이 발생되고 interrupted상태는 false로 자동으로 초기화됨
    package pratice.threadEx;
    
    import javax.swing.*;
    
    public class Thread14 {
        public static void main(String[] args) {
            ThreadEx14_1 th1 = new ThreadEx14_1();
            th1.start();
    
            String input = JOptionPane.showInputDialog("값 입력");
            System.out.println("입력한 값: " + input);
            th1.interrupt();
            System.out.println("inInterrupted(): " + th1.isInterrupted());
    
        }
    }
    
    class ThreadEx14_1 extends Thread {
        public void run() {
            int i = 10;
    
            while (i != 0 && !isInterrupted()) {
                System.out.println(i--);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    interrupt(); // false로 자동 초기화
                }
            }
    
            System.out.println("카운트 종료!!");
        }
    }
    
    • yield(): 다른 쓰레드에게 주어진 실행시간 양보
package pratice.threadEx;

public class ThreadEx20 {
    public static void main(String[] args) {
        ThreadEx20_1 gc = new ThreadEx20_1();

        gc.setDaemon(true); // 쓰레드gc를 데몬쓰레드로 설정
        gc.start();

        int requiredMemory = 0;
        for (int i = 0; i < 20; i++) {
            requiredMemory = (int) (Math.random() * 10) * 20;

            // 필요 메모리가 사용할 수 있는 양보다 크거나, 전체 메모리의 60% 이상을 사용한 경우
            if (gc.freeMemory() < requiredMemory
                    || gc.freeMemory() < gc.totalMemory() * 0.4) {
                gc.interrupt(); // 잠자고 있는 쓰레드gc 깨우기
                try {
                    gc.join(100);
                } catch (InterruptedException e) { }
            }

            gc.usedMemory += requiredMemory;
            System.out.println("usedMemory: " + gc.usedMemory);
        }
    }
}

class ThreadEx20_1 extends Thread {
    final static int MAX_MEMORY = 1000;
    int usedMemory = 0;

    public void run() {
        while (true) {
            try {
                Thread.sleep(10 * 1000);
            } catch (InterruptedException e) {
                System.out.println("Awaken by interrupt()");
            }
            gc();
            System.out.println("Garbage Collected. Free Memory :" + freeMemory());
        }
    }

    public void gc() {
        usedMemory -= 300;
        if (usedMemory < 0) {
            usedMemory = 0;
        }
    }

    public int totalMemory() {
        return MAX_MEMORY;
    }

    public int freeMemory() {
        return MAX_MEMORY - usedMemory;
    }
}

 

 

 

 

참고: 자바의 정석, 남궁 성