ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [JAVA] 쓰레드 (Thread)의 구현 / 싱글쓰레드, 멀티쓰레드 / 우선순위 / 쓰레드 그룹 / 데몬 쓰레드
    DEV/JAVA 2024. 4. 5. 00:37

    1) 프로세스(Process)와 쓰레드(Thread)의 차이

    - 프로세스 : ‘실행중인 프로그램’ → 작업관리자에서 실행중인 프로그램의 프로세스 확인 가능

    - 쓰레드 : 프로세스의 자원을 이용해서 실제로 작업을 수행하는 것

    따라서 모든 프로세스에는 최소한 하나 이상의 쓰레드가 존재.

    둘 이상의 쓰레드를 가진 프로세스를 ‘멀티쓰레드 프로세스’라고 함

    2) 쓰레드의 구현방법

    : Thread클래스를 상속받는 방법과 Runnable인터페이스를 구현하는 방법이 있다.

    //1. Thread 클래스 상속
    class MyThread extends Thread {
    	public void run() { .. } //Thread 클래스의 run()을 Overriding
    }
    
    //2. Runnable 인터페이스 구현
    class MyThread implements Runnable {
    	public void run() { .. } //Runnable 인터페이스의 run()을 구현
    }
    

    두 방법의 차이

    ✅ Thread 클래스를 상속 받으면 다른 클래스를 상속 받을 수 없음

    → 따라서 일반적으로 Runnable구현해서 사용

    ✅ 인스턴스 생성 방법이 다름

    class ThreadEx {
    	public static void main(String args[]) {
    		ThreadEx1 t1 = new ThreadEx1();
    		
    		Runnable r = new ThreadEx2();
    		Thread t2 = new Thread(r); // 생성자 Thread에 매개변수로 r을 넘김
    		//위 두줄을 한줄로 표현 가능
    		// Thread t2 = new Thread(new ThreadEx2());
    		
    		t1.start();
    		t2.start();
    	}
    }
    
    class ThreadEx1 extends Thread {
    	public void run() {
    		for(int i=0; i < 5; i++) {
    			System.out.println(getName); // 조상인 Thread의 getName() 호출
    		}
    	}
    }
    
    class ThreadEx2 implements Runnable {
    	public void run() {
    		for(int i=0; i < 5; i++) {
    			 // 현재 실행중인 Thread의 getName() 호출
    			System.out.println(Thread.currentThread().getName());
    		}
    	}
    }
    

    🌟 Runnable인터페이스를 구현하는 방법은 재사용성이 높고, 코드의 일관성을 유지할 수 있기때문에 보다 객체지향적인 방법이다.

    3) start()와 run()의 차이

    - run() : 생성된 쓰레드를 실행시키는 것이 아니라 단순히 클래스에 선언된 메서드를 호출하는 것

    - start() : 새로운 쓰레드가 작업을 실행하는데 필요한 호출스택을 생성한 다음 run()을 호출해서, 생성된 호출스택에 run()이 첫 번째로 올라가게 함.

     

    🌟 쓰레드의 start()를 먼저 호출했다고해서 먼저 실행되는 것이 아님. 순서는 OS가 결정

     

    1.  main메서드에서 쓰레드의 start()를 호출한다.
    2.  start()는 새로운 쓰레드를 생성하고, 쓰레드가 작업하는 사용될 호출스택을 생성한다.
    3.  새로 생성된 호출스택에 run()이 호출되어, 쓰레드가 독립된 공간에서 작업을 수행한다.
    4.  호출스택이 2개이므로 스케줄러가 정한 순서에 의해 번갈아가며 수행한다.

     

    이미지 및 내용 출처 : https://cano721.tistory.com/161#Start()%EC%99%80_run()%EC%9D%98_%EC%B0%A8%EC%9D%B4

     

    4) 싱글쓰레드와 멀티쓰레드

    - 싱글 쓰레드 : 하나의 쓰레드로 두 작업을 처리. 한 작업을 마친 후 다른 작업을 시작

    - 멀티 쓰레드 : 두개의 쓰레드로 두 작업을 번갈아가면서 작업을 수행하여 동시에 두 작업이 처리되는 것 처럼 느껴지게 함

    위 두 작업시간은 거의 차이가 없으며, 오히려 멀티 쓰레드가 싱글 쓰레드보다 작업시간이 더 소요될 수 있다.

    쓰레드간의 작업 전환 (context switching) 시간 때문

    ex) 다음에 실행해야할 위치(PC, 프로그램 카운터)등의 정보를 저장하고 읽어오는 시간 소요

    🌟 쓰레드 스위칭에 비해 프로세스 스위칭이 더 많은 정보를 저장해야해서 더 많은 시간이 소요됨

    🌟 싱글코어에서 단순히 CPU만 사용하는 계산작업은 멀티쓰레드보다 싱글쓰레드로 프로그래밍 하는것이 더 효율적

     

    - 싱글코어 : 멀티쓰레드라도 하나의 코어가 번갈아가면서 작업을 수행 ( 작업이 겹치지 않음 )

    - 멀티코어 : 동시에 두 쓰레드로 작업 수행 가능

     

    🌟 여러 쓰레드가 여러 작업을 동시에 진행하는 것을 ‘병행’이라고 하고,

    하나의 작업을 여러 쓰레드가 나눠서 처리하는 것을 ‘병렬’이라고 한다.

     

    쓰레드는 실행할때마다 다른 결과를 얻을 수 있는데, 그 이유는 실행 중인 프로그램(프로세스)가 OS의 프로세스 스케줄러의 영향을 받기때문이다. 프로세스 스케줄러에 의해서 실행순서와 실행시간이 결정되기 때문에 매 순간 상황에 따라 프로세스에게 할당되는 시간이 일정하지 않고 쓰레드에게 할당되는 시간 역시 일정하지 않게 된다. 그래서 쓰레드가 이러한 불확실성을 가지고 있다는 것을 염두에 두어야한다.

     

    두 쓰레드가 서로 다른 자원을 사용하는 작업의 경우,

    싱글쓰레드 프로세스보다 멀티쓰레드 프로세스가 더 효율적이다.

     

    ex ) 사용자로부터 데이터를 입력 받는 작업, 네트워크로 파일을 주고 받는 작업, 프린터로 파일을 출력하는 작업 등 외부기기와 입출력을 필요로 하는 경우

    예를들어 입력받는 작업 A와 화면에 출력하는 작업 B를 하나의 쓰레드로 처리한다면,

    작업 A에서 사용자가 입력을 마칠때까지 아무 작업도 하지 못하고 기다려야한다.

    그러나 두개의 쓰레드로 처리한다면 사용자의 입력을 기다리는 동안 다른 쓰레드가 작업을 처리할 수 있기 때문에 보다 효율적인 CPU 사용이 가능하다.

    5) 쓰레드의 우선순위

    쓰레드는 우선순위라는 속성(멤버변수)을 가지고 있는데,

    이 우선순위의 값에 따라 쓰레드가 얻는 실행시간이 달라진다.

    쓰레드가 수행하는 작업의 중요도에 따라 쓰레드의 우선순위를 서로 다르게 지정하여 특정 쓰레드가 더 많은 작업시간을 갖도록 할 수 있다.

     

    관련 메서드와 상수

    • void setPriority(int newPriority) : 쓰레드의 우선순위를 지정한 값으로 변경한다.
    • int getPriority() : 쓰레드의 우선순위를 반환한다.
    • public static final int MAX_PRIORITY = 10 : 최대 우선순위
    • public static final int MIN_PRIORITY = 1 : 최소 우선순위
    • public static final int NORM_PRIORITY = 5 : 보통 우선순위

    : 쓰레드가 가질 수 있는 우선순위의 범위는 1~10이며 숫자가 높을수록 우선순위가 높다.

    🌟 쓰레드의 우선순위는 쓰레드를 생성한 쓰레드로부터 상속받는다.

    main메서드를 수행하는 쓰레드는 우선순위가 5이므로,

    main메서드내에서 생성하는 쓰레드의 우선순위는 자동적으로 5가된다.

    class ThreadEx {
    	public static void main(String args[]) {
    		ThreadEx1 th1 = new ThreadEx1();
    		ThreadEx2 th2 = new ThreadEx2();
    		
    		th2.setPriority(7);
    		
    		System.out.println("Priority of th1 :" + th1.getPriority()); //5
    		System.out.println("Priority of th2 :" + th2.getPriority()); //7
    		
    		th1.start();
    		th2.start();
    	}
    }
    
    class ThreadEx1 extends Thread {
    	public void run() {
    	 for(int i=0; i<300; i++) {
    		 System.out.println("-");
    		 //우선순위가 높아지면 한번에 작업이 끝날 수 있어 반복문을 추가하여 작업 지연
    		 for(int j=0; j<1000000; j++) //작업지연용
    	 }
    	}
    }
    
    class ThreadEx2 extends Thread {
    	public void run() {
    	 for(int i=0; i<300; i++) {
    		 System.out.println("|");
    		 //우선순위가 높아지면 한번에 작업이 끝날 수 있어 반복문을 추가하여 작업 지연
    		 for(int j=0; j<1000000; j++) //작업지연용
    	 }
    	}
    }
    

    싱글코어 결과 : 우선순위가 높은 th2에게 th1보다 더 많은 양의 실행시간이 주어져 th2의 작업이 더 빨리 완료됨.

    멀티코어 결과 : 쓰레드의 우선순위에 따른 차이가 전혀 없음.

    → 우선순위에 차등을 두어 쓰레드를 실행시키는것이 별 효과가 없음.

    → 멀티코어라해도 OS마다 다른 방식으로 스케쥴링 하기 때문에, 어떤 OS에서 실행하느냐에 따라 다른 결과를 얻을 수 있다. 특정 OS의 스케쥴링 정책과 JVM의 구현을 직접 확인해야함. 이마저도 어느 정도 예측만 가능한 정도.

    🌟 따라서 쓰레드 우선순위보다는 작업에 우선순위를 두어 PriorityQueue를 활용하여 우선순위가 높은 작업이 먼저 처리되도록 하는 것을 권장

    6) 쓰레드 그룹

    쓰레드 그룹은 서로 관련된 쓰레드를 그룹으로 다루기 위한것.

    쓰레드 그룹에 다른 쓰레드 그룹을 포함 시킬 수도 있다.

    보안상의 이유로 도입된 개념으로,

    자신이 속한 쓰레드 그룹이나 하위 쓰레드 그룹은 변경 가능하나 다른 쓰레드 그룹의 쓰레드는 변경 불가능

     

    주요 생성자와 메서드

    생성자 / 메서드 설명
    ThreadGroup(String name) 지정된 이름의 새로운 쓰레드 그룹을 생성
    ThreadGroup(ThreadGroup parent, String name) 지정된 쓰레드 그룹에 포함되는 새로운 쓰레드 그룹을 생성
    int activeCount() 쓰레드 그룹에 포함된 활성상태에 있는 쓰레드의 수를 반환
    int activeGroupCount() 쓰레드 그룹에 포함된 활성항태에 있는 쓰레드 그룹의 수를 반환
    void checkAccess() 현재 실행중인 쓰레드가 쓰레드 그룹을 변경할 권한이 있는지 체크합니다. 만일 권한이 없다면 SecurityException이 발생시킨다.
    void destory() 쓰레드 그룹과 하위 쓰레드 그룹까지 모두 삭제합니다. 단, 쓰레드 그룹이나 하위 쓰레드 그룹이 비어있어야 합니다.
    int enumerate(Thread[] list)int enumerate(Thread[] list, boolean recurse)int enumerate(ThreadGroup[] list)int enumerate(ThreadGroup[] list, boolean recurse) 쓰레드 그룹에 속한 쓰레드 또는 하위 쓰레드 그룹의 목록을 지정된 배열에 담고 그 개수를 반환.두번째 매개변수인 recurse의 값을 true로 하면 쓰레드 그룹에 속한 하위 쓰레드 그룹에 쓰레드 또는 쓰레드 그룹까지 배열에 담습니다.
    int getMaxPriority() 쓰레드 그룹의 최대 우선순위를 반환
    String getName() 쓰레드 그룹의 이름을 반환
    ThreadGroup getParent() 쓰레드 그룹의 상위 쓰레드 그룹을 반환
    void interrupt() 쓰레드 그룹에 속한 모든 쓰레드를 interrupt
    boolean isDaemon() 쓰레드 그룹이 데몬 쓰레드그룹인지 확인
    boolean isDestroyed() 쓰레드 그룹이 삭제되었는지 확인
    void list() 쓰레드 그룹에 속한 쓰레드와 하위 쓰레드 그룹에 대한 정보를 출력
    boolean parentOf(ThreadGroup g) 지정된 쓰레드 그룹의 상위 쓰레드 그룹인지 확인
    void setDaemon(boolean daemon) 쓰레드 그룹을 데몬 쓰레드 그룹으로 설정/해제
    void setMaxPriority(int pri) 쓰레드 그룹의 최대우선순위 설정

     

    모든 쓰레드는 반드시 쓰레드 그룹에 포함되어야 한다.

    쓰레드 그룹을 지정하는 생성자를 사용하지 않은 쓰레드는 기본적으로 자신을 생성한 쓰레드와 같은 그룹에 속하게 된다.

    ex) 자바 어플리케이션 실행시 JVM은 main과 system이라는 쓰레드 그룹을 만듦.

    • main메서드를 수행하는 main쓰레드 → main쓰레드 그룹
    • 가비지 컬렉션을 수행하는 Finalizer쓰레드 → system쓰레드 그룹

    우리가 생성하는 모든 쓰레드 그룹은 main쓰레드 그룹의 하위 쓰레드 그룹이 되며,

    쓰레드 그룹을 지정하지 않고 생성한 쓰레드는 자동적으로 main쓰레드 그룹에 속하게 됨

     

    쓰레드 그룹과 관련된 메서드

    • ThreadGroup getThreadGroup() : 쓰레드 자신이 속한 쓰레드 그룹을 반환
    • void uncaughtException(Thread t, Throwable e) : 쓰레드 그룹의 쓰레드가 처리되지않은 예외에 의해 실행이 종료 되었을 때, JVM에 의해 이 메서드가 자동적으로 호출 됨

    7) 데몬 쓰레드

    데몬 쓰레드는 다른 일반 쓰레드의 작업을 돕는 보조역할을 하는 쓰레드이다.

    일반 쓰레드가 모두 종료되면 데몬 쓰레드는 강제적으로 자동 종료된다.

    이 점을 제외하고 데몬쓰레드와 일반 쓰레드는 차이가 없다.

    ex ) GC, 워드프로세서의 자동저장, 화면 자동갱신 등

    데몬 쓰레드는 무한루프와 조건문을 이용해서 실행 후 대기하고 있다가 특정 조건이 만족되면 작업을 수행하고 다시 대기하도록 작성한다.

    일반 쓰레드와 작성방법과 실행방법이 같으며

    단, 쓰레드를 생성한 다음 실행하기전 setDaemon(true)를 호출하기만 하면 된다.

    관련메서드

    • boolean isDaemon() : 쓰레드가 데몬쓰레드인지 확인한다.
    • void setDaemon(boolean on) : 쓰레드를 데몬 쓰레드로 또는 사용자 쓰레드로 변경한다.

    자바 프로그램을 실행하면 JVM은 GC, 이벤트 처리, 그래픽 처리와 같이 프로그램이 실행되는 데 필요한 보조작업을 수행하는 데몬 쓰레드들을 자동적으로 생성해서 실행시킨다.

    댓글

Designed by Tistory.