JAVA - 스레드 ( Thread, Runnable )

TopOfTheHead·2025년 7월 26일

자바 ( JAVA )

목록 보기
11/28

Java에서의 스레드 운영체제 - 스레드
Java스레드 기반으로 개발한 언어
자바로 구축한 Application은 기본적으로 스레드기반으로 실행하며 메인 스레드에서 main()를 실행하여 시작

。기본적으로 Java 프로그램메인 스레드 ( = main() ) 1개로 실행하지만, 멀티스레딩을 지원
Thread / Runnable 사용 시 여러 작업을 Concurrently하게 실행
main()메인스레드만 실행 시 만약 Thread.sleep(5000)을 설정하는 경우 해당 sleep 기간동안 다른 작업의 수행이 불가능.

。여러종류의 스레드 API를 지원
▶ 각 스레드의 생성 및 관리가 용이

멀티스레드 : 물리적 스택을 늘리는 것
new Thread() : 메모리 상에 스레드가 생성되어 CPU를 할당받고 실행

JAVAUser Thread를 생성 및 관리
OS에 의해 관리되지 않으며 JAVA에 의해 스레드가 관리됨
JVM에 의해 특정 OSkernel thread( pthreads 등 )와 Many to One Model 등의 관계를 맺어서 운용

  • JAVA에서 스레드를 생성하는 방법
    java.lang.runnable , java.lang.thread, ExecutorService, ThreadPool, @Async 활용
    ▶ 모두 Runnable 기반으로 발전
    java.lang

스레드 생명주기

  • NEW :
    스레드 객체가 생성됬지만, start()가 호출되지 않은 상태

  • RUNNABLE :
    CPU 할당을 대기하면서 실행 가능한 상태

  • RUNNING :
    스레드CPU를 점유하여 실제로 작업을 처리중인 상태

  • BLOCKED :
    스레드synchronized 키워드 블록의 진입을 대기하는 상태

  • WAITING :
    스레드join()에 의해 다른 스레드의 작업 완료를 대기하는 상태

  • TIMED_WAITING
    스레드sleep()에 의해 일정 시간 대기중인 상태

  • TERMINATED
    스레드의 실행이 완료된 상태 실행이 완료된 상태

Thread : java.lang.thread
자바 프로그램에서 멀티스레딩을 위해 사용하는 클래스
비동기 작업에서 중요하게 사용

메인스레드(= main())에서 Thread 클래스객체를 활용하여 여러 작업을 동시에 처리 가능

Thread를 객체로서 직접 생성 및 제어하는 기능이 존재
▶ 해당 Class를 상속한 Class 객체스레드로서 조작

。단. JAVA의 Class는 다중상속이 안되는 특징이 존재하므로 보통 Runnable Interface를 구현하는 방식을 사용
Thread를 상속하는 클래스에서 다른 클래스를 상속할 수 있음


Thread 메서드 / Runnable 메서드

  • 스레드객체.start()
    。해당 스레드객체를 실행하여 새로운 스레드를 생성 및 run()를 호출

  • run()
    。해당 스레드가 실행할 작업을 정의하는 메서드
    Thread 또는 Runnable을 상속한 Class에 해당 메소드를 Override하여 작업을 정의

    start()에 의해 생성된 스레드에서 호출
    메인스레드에서 직접 run()을 호출하는 경우 일반 메서드 호출

  • 스레드객체.sleep(ms)
    。지정한 milisec 만큼 현재 스레드를 일시중지

  • 스레드객체.join()
    스레드 실행을 Block하여 다른 스레드가 종료될 때까지 대기
    부모스레드에서 자식스레드객체.join()을 실행 시 자식스레드작업이 모두 끝날 때까지 대기

    프로세스System Call : wait()과 동일한 역할

  • 스레드객체.currentThread()
    。현재 실행중인 스레드 객체를 반환

  • 스레드객체.interrupt()
    。현재 실행중인 스레드에서 인터럽트 ( InterruptedException )를 발생시켜서 스레드Termination
    스레드객체.stop() 의 경우 동기화문제로 deprecated.
public class StreamApi {
	public static void main(String[] args) throws InterruptedException {
		ThreadByClass t = new ThreadByClass("영상.mp4 ");
		t.start(); // 스레드 생성 및 run() 실행
		t.join(); // t 스레드가 끝날 때 까지 main 스레드 대기
     	t.interrupt(); // t 스레드에 인터럽트예외를 발생시켜서 종료
	}
}

Thread 클래스를 활용하여 스레드 생성

  • Thread Class상속스레드 클래스를 생성
    。임의의 Java Class를 생성 및 Thread Class를 상속 후 public void run() 메소드를 오버라이딩

    。이후 메인스레드( main() )에서 해당 Java Class의 객체를 생성 후 Thread Classstart()를 호출하여 해당 스레드Context Switching
    run()을 직접 호출하면 안되며, Thread Classstart()를 호출하여 Java ClassOverriderun()을 실행해야한다.
class ThreadByClass extends Thread {
	private final String file;
	public ThreadByClass(String file) {
		this.file = file;
	}
    // Thread Class에서 메소드 오버라이딩
	@Override
	public void run(){
		try {
			while(true){
				System.out.println(file + "스레드 실행중");
				Thread.sleep(500);
			}
		} catch(InterruptedException e){
			e.printStackTrace();
		}
	}
}
public class StreamApi {
	public static void main(String[] args) {
		ThreadByClass t1 = new ThreadByClass("영상.mp4 ");
		ThreadByClass t2 = new ThreadByClass("음악.mp3 ");
		ThreadByClass t3 = new ThreadByClass("문서.hwp ");
		// 메인스레드( main() )에서 다른 스레드( t1 )으로 Context Switch 발생
		t1.start(); // run() 구동
		t2.start();
		t3.start();
		// Context Switch 이 완료되기전이므로 우선 실행
		System.out.println("메인스레드 : 다운로드 중에도 다른 작업 가능");
	}
}
MyThread t = new MyThread();
//
t.run();   // 그냥 메서드 호출 → 새 스레드 생성 안 됨, 메인 스레드에서 실행됨
t.start(); // 새 스레드를 생성하고 그 스레드에서 run()을 실행함

스레드객체.start()를 통해 호출되며, run()을 직접 호출 시 그냥 메서드 호출로서 메인스레드에서 실행

메인스레드에서 main() 메소드를 실행 시 ThreadByClass 객체 생성 및 thread.start()를 통해 해당 스레드Context Switch가 완료되기 전 Non-Blocking으로 System.out.println("Thread started");이 먼저 출력

메인스레드로부터 Context Switch가 완료된 경우 ThreadByClass에서 Method Overridingrun()에 구현된 코드를 실행

Runnable : java.lang.runnable
스레드가 실행할 작업( = run() )을 정의하는 인터페이스
▶ 실무에서 스레드 생성 시 Thread 클래스를 대신해서 주로 활용

Runnable 인터페이스를 구현한 Java Class 객체를 생성자로 전달하여 Thread Class 객체 생성 후 활용
Thread thread = new Thread(new Runnable구현클래스())


Runnable을 활용하는 이유

  • 상속 제한 해결
    Thread 클래스단일 상속의 문제가 존재하므로, 상속이 제한되어 Runnable 인터페이스를 통해 다중 상속
class A extends SomeClass implements Runnable
  • 역할 분리
    Thread 클래스작업 내용을 정의하면서 동시에 실행 주체로서 사용되지만, Runnable 인터페이스는 내부에 작업 내용만 정의하여 활용가능
    작업(= Runnable)과 스레드(= Thread)를 분리하여 재사용성이 높음.

Runnable Interface구현 클래스를 정의하여 스레드 생성
。가장 많이 활용하는 방식

。임의의 Java Class에서 Runnable Interface를 구현 후 부모 Class의 public void run() 메소드를 오버라이딩

。 해당 구현체Thread Class생성자로 전달하여 스레드 생성start()를 호출하여 Context Switching

class ThreadByInterface implements Runnable{
    // Thread Class에서 method override
    public void run(){
        try{
            while(true){
                System.out.println("Thread");
                Thread.sleep(500); // 0.5초 간격
            }
        }
        // 스레드 종료 관측 시
        catch(InterruptedException ie){
            System.out.println("interrupted");
        }
    }
}
public class MyThread {
    // 메인스레드 실행
    public static void main(String[] args) throws Exception {
        // ThreadByInterface객체를 활용해 Thread 객체 생성
        Thread thread = new Thread(new ThreadByInterface());
        thread.start(); // 메인스레드에서 다른 스레드로 Context Switch 발생
        System.out.println("Thread started"); // Context Switch 이 완료되기전이므로 우선 실행
    }
}

익명 스레드 : 람다표현식 ( Lambda Expression )을 활용하여 Runnable을 통한 스레드 생성
JAVA 1.8부터 사용가능

람다 표현식을 통해 Runnable 구현체 생성 후 Thread Class 생성자로 전달하여 스레드 객체 생성 및 start()를 호출하여 Context Switching
람다 표현식을 통해 Runnable상속하는 클래스를 정의할 필요가 없다.

。 임의의 Java Class를 선언하는 방식보다 간결하게 표현 가능

public class MyThread {
    // 메인스레드 실행
    public static void main(String[] args) throws Exception {
        Runnable task = () -> {
            try{
                while(true){
                    System.out.println("Thread");
                    Thread.sleep(500); // 0.5초 간격
                }
            }
            // 스레드 종료 관측 시
            catch(InterruptedException ie){
                System.out.println("interrupted");
            }
        };
        Thread thread = new Thread(task);
        thread.start(); // 메인스레드에서 다른 스레드로 Context Switch 발생
        System.out.println("Thread started"); // Context Switch 이 완료되기전이므로 우선 실행
    }
}

Java에서 Non-Blocking을 방지하기위해 부모 스레드의 실행을 대기시키는 방법
스레드객체.join() 을 통해 해당 스레드가 실행이 완료될때까지 부모스레드Block하여 대기.
프로세스에서 fork()를 통해 자식프로세스 생성 시 부모 프로세스System Call : wait()과 같은 개념

부모 스레드Non-Blocking을 통한 동시처리 방지
자식스레드의 모든 실행이 끝나면 메인스레드 실행 재개

public class MyThread {
    // 메인스레드 실행
    public static void main(String[] args) throws Exception {
         // 자식스레드가 실행할 코드 정의 
        System.out.println("메인스레드 실행");
        Runnable task = () -> {
            System.out.println("자식스레드 실행");
        };
        Thread thread = new Thread(task);
        thread.start(); // 메인스레드에서 다른 스레드로 Context Switch 발생
        try{
            System.out.println("메인스레드 실행 Block");
            thread.join();
            System.out.println("자식스레드 실행완료 / 메인스레드 실행 재개");
        }
        // 스레드 종료 관측 시
        catch(InterruptedException ie){
            System.out.println("interrupted");
        }
        // Non-Blocking으로 자식스레드 모두 실행 시 메인스레드 실행
        System.out.println("부모스레드 실행완료");
    }
}


스레드객체.join()을 통해 해당 스레드 작업이 완료될때까지 부모스레드(= 메인스레드 ) 의 실행을 Block

Java에서 실행중인 스레드Termination하는 방법
스레드객체.interrupt()를 통해 인터럽트를 발생시켜서 스레드를 종료

public class MyThread {
    // 메인스레드 실행
    public static void main(String[] args) throws Exception {
        Runnable task = () -> {
            try{
                while(true){
                    System.out.println("Thread");
                    Thread.sleep(500); // 0.5초 간격으로 반복
                }
            }
            // 스레드 종료 관측 시
            catch(InterruptedException ie){
                System.out.println("interrupted");
            }
        };
        Thread thread = new Thread(task);
        thread.start(); // 메인스레드에서 다른 스레드로 Context Switch 발생
        Thread.sleep(2000); 
        thread.interrupt(); // 2초후 스레드 객체에 인터럽트 발생
        System.out.println("메인스레드 실행완료");
    }
}

profile
공부기록 블로그

0개의 댓글