[JAVA]_thread

전희주·2023년 4월 23일
0

JAVA

목록 보기
17/24

https://wikidocs.net/230

thread

thread 개념

  • 동작하고 있는 프로그램을 프로세스(Process)라고 한다. 보통 한 개의 프로세스는 한 가지의 일을 하지만, 쓰레드를 이용하면 한 프로세스 내에서 두 가지 또는 그 이상의 일을 동시에 할 수 있다.
  • Java는 스레드 스케줄러가 스레드의 우선 순위에 따라 스레드에 프로세서를 할당하는 멀티스레딩 환경 내에서 완전히 객체 지향적으로 작동
  • 하나의 프로세스의 작업을 실제로 수행하는 것: 쓰레드
  • 하나의 프로세스 (process) 는 여러 개의 쓰레드를 가질 수 있음
  • 쓰레드 (thread)

thread 특징

  • 공유 가능
    • 하나의 프로세스 내에서 여러 개의 Thread가 생성될 수 있으며, 이들은 같은 프로세스 내에서 실행
    • 이러한 Thread들은 같은 자원(메모리 등)을 공유할 수 있음
    • 이를 통해 데이터 공유, 작업 분할 등이 가능해져 병행 처리에 적합
  • 병행 처리 가능
    • Thread들이 동시에 작업을 수행하면서 병행으로 처리하므로, 실행 시간을 단축
  • 우선 순위 지님
    • 실행 단계에서 쓰레드는 다른 우선순위의 쓰레드에 의해 쫓겨날 수 있다
    • 우선 순위가 가장 높은 스레드가 다른 스레드보다 먼저 실행 기회를 얻음
    • Java에서 스레드를 생성할 때마다 항상 우선 순위가 할당
    • 우선순위는 스레드를 생성하는 동안 JVM에서 제공하거나 프로그래머가 명시적으로 제공

thread 생성 방법 (✨ 출제)

  • extends Thread
    • Thread 클래스를 상속받아 run 메소드를 오버라이딩
    • 완벽한 thread
    • run 메소드
  • implements Runnable
    • Runnable 인터페이스를 implements 하여 run 메소드를 정의
    • 준 thread + 완벽한 thread로 만들기
    • run 메서드 재정의 위한 코드 필요

thread의 상태 (✨ 출제)

  • ✨ (주의) 우리는 run 메서드를 직접 호출하지 않고, start()만 수행

  • void run()

    • 스레드 코드로서 JVM에 의해 호출된다. 반드시 오버라이딩하여 스레드 코드를 작성
    • 이 메소드가 종료하면 스레드도 종료
  • void start()

    • JVM에게 스레드를 실행을 시작하도록 요청(run() 메소드 호출)


  • 쓰레드 실행 순서

    • 시작 준비 단계: 쓰레드 객체를 생성한 후 start() 메서드를 호출합니다. 이 때, JVM은 해당 쓰레드를 실행 가능한 상태인 "Runnable" 상태로 만들고, 해당 쓰레드가 사용할 스레드 스택(Thread Stack)을 생성합니다.
      • t1. start(), t2.start()
      • t1-5, t2-5, t3-10
    • Block 단계: 쓰레드가 실행 가능한 상태가 되면, 스케줄러(Scheduler)에 의해 CPU가 할당되고, 해당 쓰레드가 실행됩니다. 실행 중인 쓰레드가 다른 쓰레드에 의해 중지되는 경우, 해당 쓰레드는 "Block" 상태로 변경됩니다.
      • 명시적 대기 상태 전환
      • sleep(), yield(), join() 메서드는 쓰레드를 일시적으로 block 상태로 만들고, 다른 쓰레드가 실행될 수 있도록 함
    • Wait 단계: 쓰레드가 Block 상태에 있을 때, 일시적으로 실행을 중단하고 다른 쓰레드의 notify() 메서드 호출을 기다리는 경우, 해당 쓰레드는 "Wait" 상태로 변경됩니다. 이 때, 쓰레드는 wait() 메서드를 호출하여 일시적으로 실행을 중단합니다.
    • Notify 단계: Wait 상태에 있는 쓰레드가 다시 실행되기 위해서는, 다른 쓰레드가 notify() 메서드를 호출해야 합니다. 이 때, notify() 메서드를 호출하는 쓰레드와 wait() 메서드를 호출한 쓰레드는 동일한 객체의 모니터를 공유해야 합니다.
    • 제거 단계: 쓰레드가 실행을 마치면, 해당 쓰레드는 종료됩니다. 이 때, JVM은 해당 쓰레드가 사용한 자원을 해제하고, 해당 쓰레드의 스택을 제거합니다.

(참고) 동기화(synchronization)

  • 자바에서 동기화(synchronization)는 공유 자원에 대한 접근을 제한하고, 여러 쓰레드가 공유 자원에 동시에 접근하는 것을 방지하는 기술

  • 자바에서는 동기화를 위해 synchronized 키워드와 wait(), notify(), notifyAll() 메서드를 제공

  • 이 중 wait(), notify(), notifyAll() 메서드는 Object 클래스에서 제공되며, 다음과 같은 실행 순서를 지님 (개념만 알기)

    • 쓰레드 A가 공유 자원에 대한 동기화 블록에 진입하려고 할 때, 다른 쓰레드들은 해당 블록에 접근할 수 없음
    • 동기화 블록에서 wait() 메서드를 호출한 쓰레드는 공유 자원을 놓고 대기(waiting) 상태로 진입
    • 동기화 블록을 점유하고 있는 다른 쓰레드가 작업을 마치고 notify() 또는 notifyAll() 메서드를 호출하면, 대기 상태에 있는 쓰레드 중 한 쓰레드(A)가 깨어나서 실행 가능한 상태
    • 하지만 notify() 또는 notifyAll() 메서드를 호출한 쓰레드가 더 높은 우선순위를 가지는 경우, 깨어난 쓰레드(A)가 바로 실행될 수도 있지만, 그렇지 않은 경우 깨어난 쓰레드(A)는 대기(waiting) 상태로 유지되며, 다시 자신의 차례가 올 때까지 대기

thread 실습 코드 분석

  • go class와 come class 간의 병행성이 없는 상태 => 스레드 생성을 통해 병행 처리 가능
  • 즉 개별 클래스가 서로 영향을 미치지 못하고, 무한 루프를 수행하는 go 클래스의 내용만 출력되고 있음

ThreadTest (p01)

package p01;



class Go{
	public void go() {
		while(true) {
			System.out.println("go");
		}
	}
}

// go class와 come class 간의 병행성이 없는 상태 => 스레드 생성을 통해 병행 처리 가능 

class Come{
	public void come() {
		while(true) {
			System.out.println("come");
		}
	}
}


public class ThreadTest {
	public static void main(String[] args) {

		Go g = new Go(); 
		Come c = new Come(); 
		
		g.go();
		c.come();
	}

}
  • 우리는 run()는 호출하지않고 start()만 호출함 (주의할 것)
    • 스레드 생성을 통해 병행 처리 가능
    • 단, 몇 번 수행할지는 제어하기 까다로운 측면이 있음

ThreadTest (p02)

package p02;



class Go extends Thread{
	public void run() {
		while(true) {
			System.out.println("go" + Thread.currentThread().getName());
		}
	}
}

// go class와 come class 간의 병행성이 없는 상태 => 스레드 생성을 통해 병행 처리 가능 

class Come extends Thread{
	public void run() {
		while(true) {
			System.out.println("come" + Thread.currentThread().getName());
		}
	}
}


public class ThreadTest {
	public static void main(String[] args) {

		Go g = new Go(); 
		Come c = new Come(); 
		
		g.start();
		c.start();
	}

}

  • 현재 실행 중인 스레드 명칭 확인
    • Thread.currentThread().getName()
  • 실행 결과

ThreadTest (p03)

  • implements Runnable 주의 사항: Go는 아직 스레드가 아님 (준 스레드 상태)
    • g.start(); => start는 스레드에만 있는 메서드라 준 스레드에서 실행 불가능
    • start 메소드 호출 위해서는 진짜 스레드 생성 필
package p03;


// implements Runnable 주의 사항: Go는 아직 스레드가 아님 (`준 스레드` 상태) 
class Go implements Runnable{
	// run 메서드 재정의 필 
	public void run() {
		while(true) {
			System.out.println("go");
		}
	}
}


// implements Runnable 주의 사항: Go는 아직 스레드가 아님 (`준 스레드` 상태) 
class Come implements Runnable{
	public void run() {
		while(true) {
			System.out.println("come");
		}
	}
}


public class ThreadTest {
	public static void main(String[] args) {

		// 아직 스레드가 아니다 
		Go g = new Go(); 
		Come c = new Come(); 
		
		// g.start(); => start는 스레드에만 있는 메서드라 준 스레드에서 실행 불가능 
	
		// 진짜 스레드로 생성 
        Thread t1 = new Thread(g, "go thread");
	    Thread t2 = new Thread(c, "come thread");
	    
        // 스레드 우선권 확인 
		System.out.println("t1 우선권" + t1.getPriority());
		System.out.println("t2 우선권" + t2.getPriority());
		
		// 우선권 변경 
		t1.setPriority(10);
        
        
	    // 실행시 스레드를 이용한 병행 처리 결과와 동일
	    t1.start();
		t2.start();
	}

}

  • 사용자 지정 스레드 명칭 부여

  • 실행 결과

  • 스레드 우선권 확인

     // 스레드 우선권 확인 
		System.out.println("t1 우선권" + t1.getPriority());
		System.out.println("t2 우선권" + t2.getPriority());
		
		// 우선권 변경 
		t1.setPriority(10);
    • MIN_PRIORITY: 1, MAX_PRIORITY: 10

ThreadTest (p04)

  • Java에서 모든 쓰레드는 기본적으로 우선순위(priority) 5를 갖습니다
  • main 스레드에서 생성된 쓰레드들도 마찬가지입니다. 따라서, main 스레드 내에서 생성된 쓰레드들의 우선순위도 5로 초기화됩니다.
  • 1에서 10까지의 범위를 가지며, 숫자가 높을수록 우선순위가 높아집니다.
      • MIN_PRIORITY: 1, MAX_PRIORITY: 10
  • 하지만 우선순위가 높은 쓰레드가 계속해서 CPU 자원을 점유하는 것은 아닙니다. (100퍼센트가 아님)
    • 즉, 스케쥴러가 준비단계에서 실행단계로 가져오는 확률을 높인 것이다.
package p04;



public class ThreadTest {
	// main 함수도 스레드에 의해 실행되고 있는 것임
	// 즉, main 스레드가 main 메소드를 실행시키고 있음 
	// 우리가 직접 스레드를 만들지 않아도 자동으로(background) 생성되는 스레드가 main 스레드임 
	public static void main(String[] args) {

		// main 스레드가 main 메서드 실행 
		System.out.println(Thread.currentThread().getName()); // main
		System.out.println(Thread.currentThread().getPriority()); // 5
		
		
	}

}

ThreadTest (p05)

  • 메인 쓰레드는 무한히 반복하면서 현재 시간을 출력하고, 1초씩 대기
    • 1) 메인 스레드가 시작되면서 main() 메서드가 실행된다.
    • 2) while(true) 루프가 시작되면서 반복문 안에서 Date 객체를 생성하여 현재 시간을 출력한다.
    • 3) Thread.sleep(1000) 메서드를 사용하여 현재 실행 중인 스레드(메인 스레드)를 1초 동안 block 시킨다.
    • 4) 1초가 지난 후, 다시 while 루프를 실행하여 현재 시간을 출력한다.
    • 5) 3,4 단계를 무한 반복하면서 시간이 출력된다. 만약 InterruptedException이 발생하면, 해당 예외 처리를 하고 다음 반복을 진행한다.
    • 6) 프로그램을 강제로 종료시키지 않는 한 무한 반복하면서 현재 시간을 출력한다.
package p05;

import java.util.Date;

public class ThreadTest {
	public static void main(String[] args) {

		// 시간 출력 
		// 아래의 반복문은 메인 스레드에 의해 실행되고 있음
		// 메인 스레드가 실행 단계로 가 while문 날짜를 구하고, try -catch 문을 만나 block 상태로 넘어가 1초 대기 하게 됨
		// 출력 위와 같은 방식으로 반복 진행 
		// 시간 출력
		while(true) {
			Date d = new Date();
			try {
				//현재 실행중인 스레드를 block시킬때 사용됨.
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				
				e.printStackTrace();
			}
			System.out.println(d);
		}
		
	}

}

0개의 댓글