728x90

스레드 생성

  • Thread 클래스를 상속받아 run() 메서드를 오버라이딩하여 스레드를 생성
  • Runnable 인터페이스를 구현하여 스레드를 생성

 

Thread 클래스 상속

스레드 클래스 정의

public class HelloThread extends Thread {

    @Override
    public void run() {
        doSomething();
    }
}

 

스레드 생성 및 실행

  • start() 메서드를 호출하여 스레드를 실행
    • main 스레드와 별도의 스레드를 실행
  • run() 메서드를 직접 호출하면 main 스레드에서 실행
    • main 스레드는 HelloThread 인스턴스에 있는 run() 메서드를 호출하는 형태
public class HelloThreadMain {

    public static void main(String[] args) {
        HelloThread helloThread = new HelloThread();
        helloThread.start();
    }
}

 

 

데몬 스레드

스레드는 사용자(user) 스레드와 데몬(daemon) 스레드로 구분

차이는 JVM 종료 시점

  • 모든 user 스레드가 종료되면 JVM도 종료
  • 데몬 스레드는 user 스레드가 모두 종료되면 데몬 스레드도 종료 (why? JVM이 종료돼서)

setDaemon(true) 메서드를 호출하여 데몬 스레드로 설정

  • 데몬 스레드 여부는 start() 실행 전에 결정 (이후에 수정 불가)
public class DaemonThreadMain {

    public static void main(String[] args) {
        System.out.println(Thread.currentThread().getName() + ": main() start");
        DaemonThread daemonThread = new DaemonThread();
        daemonThread.setDaemon(true); // 데몬 스레드 여부
        daemonThread.start();
        System.out.println(Thread.currentThread().getName() + ": main() end");
    }

  static class DaemonThread extends Thread {

    @Override
    public void run() {
      System.out.println(Thread.currentThread().getName() + ": run()");
      try {
        Thread.sleep(10000); // 10초간 실행
      } catch (InterruptedException e) {
        throw new RuntimeException(e);
      }

      System.out.println(Thread.currentThread().getName() + ": run() end");
    }
  }
}

 

 

run() 메서드 안에서 Thread.sleep() 를 호출할 때 checked exception 인 InterruptedException 을 밖으로 던질 수 없고 반드시 잡아야 함

  • why? run() 메서드는 부모 클래스 Thread의 인터페이스인 Runnable 인터페이스에서 throws를 선언하지 않고 있음
    • 자바 오버라이드 규칙에 따라 부모 메서드 또는 인터페이스가 체크 예외를 던지지 않는 경우, 재정의된 자식 메서드도 체크 예외를 던질 수 없음
  • InterruptedException은 checked exception 이기 때문에 try-catch 블록으로 예외를 잡아야 함

 

 

Runnable 인터페이스 구현

Runnable 인터페이스를 구현하는 방식으로 스레드를 생성

public interface Runnable {
     void run();
}
 public class HelloRunnable implements Runnable {
     @Override
     public void run() {
         System.out.println(Thread.currentThread().getName() + ": run()");
     }
}

 

Thread 상속 vs Runnable 구현

스레드 사용할 때는 Thread 를 상속 받는 방법보다 Runnable 인터페이스를 구현하는 방식을 사용하자

 

  • 자바는 다중 상속이 안 된다 -> Thread 클래스를 상속받으면 다른 클래스를 상속받을 수 없음
  • 코드의 분리가 안 된다 -> 굳이 Thread 클래스를 상속받아서 디펜던시 및 추가적인 메모리를 할당 할 필요가 없음
  • 여러 스레드가 동일한 Runnable 객체를 공유할 수 있어 자원 관리를 효율적으로 할 수 있다 -> Runnable 인스턴스를 여러 스레드에서 사용할 수 있음 (개별 스레드에서 run() 참조 값이 같음)
728x90

'Java > 멀티스레드' 카테고리의 다른 글

[JAVA] 스레드 제어와 생명주기  (0) 2024.09.18
728x90

스레드(Thread)

  • 프로그램 코드를 이동하면서 실행하는 하나의 제어

자바의 멀티태스킹

  • 멀티쓰레딩만 가능
    - 자바에 프로세스 개념은 존재하지 않고, 스레드 개념만 존재
    • 스레드는 실행 단위이자 스케쥴링 단위
  • 하나의 응용 프로그램은 여러 개의 스레드로 구성 가능

자바 스레드

  • JVM(Java Virtual Machine)에 의해 스케쥴되는 실행 단위의 코드 블럭
  • 스레드의 생명 주기는 JVM에 의해 관리
  • 하나의 JVM은 하나의 자바 응용 프로그램만 실행
    - 하나의 응용 프로그램은 하나 이상의 스레드로 구성 가능

스레드를 만드는 2가지 방법

  • java.lang.Thread 클래스를 이용하는 경우
  • java.lang.Runnable 인터페이스를 이용하는 경우

1. Thread 클래스를 이용한 스레드 생성

1) Thread 클래스 상속, 새 클래스 작성
2) run() 메소드 오버라이딩

class TestThread extends Thread {
    ...
    public void run() {
    	// run() 메소드 오버라이딩
        ...
    }
}

3) 스레드 객체 생성

TestThread thread = new TestThread();

4) 스레드 시작 - start() 메소드 호출

thread.start();
  • 주의 사항
    - run() 메소드가 종료되면 스레드는 종료됨
    - 한 번 종료된 스레드는 다시 시작할 수 없음 = 다시 스레드 객체를 생성하고 등록해야 함
    - 스레드에서 다른 스레드를 강제 종료 가능

2. Runnable 인터페이스로 스레드 생성

1) Runnable 인터페이스로 새 클래스 구현
2) 스레드 코드 작성

class TestRunnable implements Runnable {
	...
    public void run() {
    	// run() 메소드 구현
        ...
    }
}

3) 스레드 객체 생성

Thread thread = new Thread(new TestRunnable());

4) 스레드 시작

thread.start();

Thread 클래스 상속과 Runnable 인터페이스 구현의 차이

  • 자바는 다중 상속을 지원하지 않는다. 그렇기 때문에 Thread 클래스를 상속받는 경우, 다른 클래스를 상속받을 수 없다. 그렇기 때문에 Runnable 인터페이스를 구현하는 것이 일반적이다.

스레드 상태 6가지

  • NEW : 스레드가 생성되었지만 아직 실행할 준비가 되지 않음
  • RUNNABLE : 스레드가 JVM에 의해 실행되고 있거나 실행 준비되어 스케쥴링을 기다리는 상태
  • WAITING : 다른 스레드가 notify(), notifyAll()을 불러주기를 기다리고 있는 상태, 보통 스레드 동기화를 위해 사용
  • TIMED_WAITING : 스레드가 sleep(n)을 호출하여 n ms동안 잠을 자는 상태
  • BLOCK : 스레드가 I/O 작업 요청을 하면 JVM이 자동으로 BLOCK 상태로 만듦
  • TERMINATED : 스레드 종료

스레드 생명 주기

스레드 종료와 다른 스레드 강제 종료

  • 스스로 종료하는 경우 : run() 메소드 리턴
  • 다른 스레드에서 강제 종료하는 경우 : interrupt() 메소드 사용
class TestThread extends Thread {
	int n=0;
    public void run() {
    	while(true) {
            n++;
            try {	
                Thread.sleep(1000);
            } catch (InterruptedException e) {
            	return;	// 예외 처리로 종료
            }
        }
    }
}
public class InterruptEx {
    public static void main(Stirng[] args) {
        TestThread thread = new TestThread();
        thread.start();
        thread.interrupt(); // 스레드 강제 종료
    }
}

flag를 이용한 종료

  • 스레드 안의 flag 변수를 이용하여 종료
class TestThread extends Thread {
	int n=0;
    bool flag = false; // false로 초기화
    public void finish() {
    	flag = true;
    }
    public void run() {
    	while(true) {
            n++;
            try {
                Thread.sleep(1000);
                if (flag == true)
                	return;
            } catch (InterruptException e) {
            	return;
            }
        }
    }
}
public FlagEx {
	public static void main(String[] args) {
    	TestThread thread = new TestThread();
        thread.start();        
        tread.finish();	// TestThread 강제 종료
    }
}

스레드 동기화 (Thread Synchronization)

  • 멀티스레드 프로그램 작성 시 주의점
    - 다수의 스레드가 공유 데이터에 동시에 접근하는 경우
    - 공유 데이터의 값에 예상치 못한 결과 발생 가능
  • 이를 해결하기 위해 스레드 동기화 사용
  • 공유 데이터를 접근하는 모든 스레드를 관리
  • 한 스레드가 공유 데이터에 접근하는 경우 작업이 끝날 때까지 다른 스레드들은 대기

synchronized

  • 한 스레드가 독점적으로 실행해야 하는 부분을 표시하는 키워드
  • synchronized 메소드
synchronized void add() {
	int n = getCurrentSum();
    n+=10;
    setCurrentSum(n);
}
  • synchronized 코드 블럭
void execute() {
	...
    synchronized(this) {
    	int n = getCurrentSum();
        n+=10;
        setCurrentSum(n);
    }
    ...
}

wait(), notify(), notifyAll()

  • 동기화 객체 : 2개 이상의 스레드 사이에 동기화 작업에 사용되는 객체
  • 동기화 메소드
    • synchronized 블록 내에서만 사용되어야 함
    • wait()
      • 다른 스레드가 notify()를 불러줄 때까지 대기
    • notify()
      • wait()을 호출하여 대기 중인 스레드를 깨우고 RUNNABLE 상태로 만듦
      • 2개 이상의 스레드가 대기 중이어도 한 개의 스레드만 깨움
    • notifyAll()
      • wait()를 호출하여 대기 중인 모든 스레드를 깨우고 모두 RUNNABLE 상태로 만듦

이미지 출처 : 명품 JAVA 프로그래밍 (황기태, 김효수 저)

728x90

+ Recent posts