Java/김영한의 실전자바

스레드의 생성과 실행

슈코 2024. 8. 15. 10:14

스레드의 생성

Runnable 인터페이스 구현을 사용하자
  • Thread 클래스 상속
  • Runnable 인터페이스 구현

 

스레드는 Thread 클래스를 상속해서 만들거나, Runnable 인터페이스를 구현하는 방법으로 생성할 수 있다.

실무에서는 Runnable 인터페이스를 구현하는 방법을 선호한다.

Thread 클래스를 상속하는 경우, 다른 상속이 있는 경우에는 사용할 수 없다( 다중 상속 X )

따라서, 스레드와 실행할 작업을 명확히 나누어서 개발할 수 있는 Runnable 방식을 사용한다.

 

 

스레드 생성 - Thread 클래스 상속

Thread 클래스 상속 후 run() 메소드를 오버라이딩한다.

public class HelloThread extends Thread {

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

 

객체 생성 후 start() 메소드를 사용하여 실행한다.

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

 

 

스레드 생성 - Runnable 인터페이스 구현

Runnable 인터페이스를 구현하는 클래스를 만들고, Thread 객체를 생성할때 생성자로 넣어준다.

public class HelloRunnable implements Runnable {

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

 

public class HelloRunnableMain {
    public static void main(String[] args) {
        HelloRunnable runnable = new HelloRunnable();
        Thread thread = new Thread(runnable);
        thread.start();
    }
}

 

Runnable을 이용하여 다양한 방법으로 스레드를 생성할 수 있다.

가장 기본적으로, Runnable 인터페이스를 구현하고 이를 스레드 생성 시 파라미터로 넣어준다.

 

<기본>

public class InnerRunnableMainV1 {

    public static void main(String[] args) {
        MyRunnable runnable = new MyRunnable();
        Thread thread = new Thread(runnable);
        thread.start();
    }

    static class MyRunnable implements Runnable {
        @Override
        public void run() {
            log("run()");
        }
    }
}

 

다음과 같이 익명클래스를 사용하여 만들 수 있고, 이는 람다로 간단하게 표현할 수 있다.

익명클래스를 사용하는 경우는, 재사용없이 간단히 표현해줄때 사용하면 효과적일 수 있다.

( 재사용이 많은 경우에는, 따로 선언해놓는 방법이 효율적이다. )

 

<익명클래스>

public class InnerRunnableMainV2 {

    public static void main(String[] args) {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                log("run()");
            }
        });
        thread.start();
    }
}

 

<람다 표현식>

public class InnerRunnableMainV2 {

    public static void main(String[] args) {
        Thread thread = new Thread(() -> log("run()"));
        thread.start();
    }
}

 

결국 람다를 사용한 이 방식을 자주 사용한다.

람다의 경우 다음번에 정리하는 시간을 가져야겠다.( 2024년 8월 19일 기준! ) 

 

Runnable의 경우, 한번 만들어둔 것을 반복해서 호출 할 수 있다.

맨 위에 예시 HelloRunnable을 가져와서 반복 생성해보자.

public class ManyThreadMainV1 {

    public static void main(String[] args) {
        HelloRunnable runnable = new HelloRunnable();

        Thread thread1 = new Thread(runnable);
        thread1.start();

        Thread thread2 = new Thread(runnable);
        thread2.start();

        Thread thread3 = new Thread(runnable);
        thread3.start();
    }
}

 

 

스레드의 실행

start() 메소드 호출( run() 메소드 호출 x )

 

만들어둔 스레드 객체를 생성하고, start() 메소드를 실행한다

run() 메소드가 아닌, start() 메소드를 호출해줘야 한다.

( start()를 호출해야, 별도 스레드에서 run()을 호출해줌 )

 

run() 메소드를 호출하는 경우, 별도의 스레드가 생성되면서 그 스레드가 run()을 호출하는 것이 아닌,

현재 스레드가 단순히 run() 메소드를 실행하게 된다.

( 스레드를 생성해서 그 스레드가 run()을 실행하려고 했던 원래 의도에서 벗어남 )

 

 

스레드 메소드

currentThread() - 해당 코드를 실행하는 스레드의 정보 조회

currentThread().getName() - 실행중인 스레드의 이름 조회

( 별도의 이름을 입력해주지 않으면, thread-0, thread-1 등으로 임의 생성된다 )

 

데몬 스레드

백그라운드에서 보조 작업을 수행하는 스레드

스레드는 사용자 스레드와 데몬 스레드로 구분할 수 있다

( Thread 메소드 setDaemon()을 true로 하면 데몬스레드, 디폴트는 false )

모든 유저 스레드가 종료되면, 데몬 스레드는 자동으로 종료된다.

 

 

로거

스레드를 실행할때는 로그를 남기는 것 이 중요하다.

( 어떤 스레드가 실행되었는지를 알아야 하기 때문에 )

 

로그를 남기는 메소드를 따로 구성하자!

import java.time.LocalTime;
import java.time.format.DateTimeFormatter;

public abstract class MyLogger {

    private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HH:mm:ss.SSS");

    public static void log(Object obj) {
        String time = LocalTime.now().format(formatter);
        System.out.printf("%s [%9s] %s\n", time, Thread.currentThread().getName(), obj);
    }
}