스레드 생성과 실행

황상익·2024년 9월 14일

Inflearn JAVA

목록 보기
42/61

스레드 시작

자바 메모리 구조 복습

자바 메모리 구조

  • 메서드 영역 : 메서드 영역은 프로그램을 실행한는데 공통적으로 필요한 데이터 관리
    클래스 정보 : 클래스의 실행 코드, 필드, 메서드와 생성자 코드등 모든 실행 코드가 존재
    static : static 변수들 보관
    런타임 상수 풀 : 프로그램을 실행하는데 필요한 공통 리터럴 상수 보관

  • stack : 자바 실행시 하나의 실행 스택이 생성, 각 스택 프레임은 지역변수, 중간 연산결과, 메서드 호출
    stack frame : 스택 영역에 쌓이는 네모 박스가 하나의 스택 프레임. 메서드를 호출할 때마다 하나의 스택 프레임 생성, 종료되면 스택 프레임 제거

  • Heap 영역 : 객체 인스턴스와 배열이 생성되는 영역. 가비지 컬렉션이 주로 이뤄지는 영역

스레드 생성

1 . 스레드 생성 - Thread 상속

public class HelloThread extends Thread {
    @Override
    public void run() {
        //main 스레드는 main 메서드 스택 프레임에 스택을 올리면서 시작
        //직접 만드는 스레드는 run 메서드의 스택 프레임을 스택에 올리면서 시작
        //main은 단지 지시만 할 뿐
        System.out.println(Thread.currentThread().getName() + " : run()");
        //currentThread -> 해당 스레드 객체 조회 가ㅡㄴㅇ
        //.getName -> 이름 조회
    }
}
  • Thread 상속, 각 스레드가 실행될 코드를 run 메서드에 재정의
  • Thread.currentThread()를 호출 -> 해당 코드를 실행하는 스레드 객체 조회 가능
  • Thread.currentThread().getName() -> 실행중인 스레드의 이름을 조회
public class HelloThreadMain {
    public static void main(String[] args) {
        //main이 실행 중
        //Thread-0 : run() -> 실행
        System.out.println("Thread.currentThread().getName()" + " : main() start"); // 실행중인 스레드 호출

        HelloThread helloThread = new HelloThread();
        System.out.println("Thread.currentThread().getName()" + " : start() 호출 전");

        helloThread.start(); // run 호출 하면 안됨
        System.out.println("Thread.currentThread().getName()" + " : start() 호출 후");

        System.out.println("Thread.currentThread().getName()" + " : main() end");
    }
}
  • start -> 스레드를 실행하는 특별한 메서드

    실행 결과 -> main 메서드는 main이라는 이름의 스레드가 실행하는 것 확인 가능, 프로세스가 작동하려면 스레드가 최소 하나는 있어야 함. 실행 시점에 main이라는 이름의 스레드를 만들고 프로그램 시작점인 main 메서드 실행

start를 호출 해야 스택 공간에 할당 받고 스레드 작동
스레드에 이름을 주지 않으면 자바는 임의로 스레드 이름 지정
run() 메서드의 스택 프레임을 스택에 올리면서 run() 메서드를 시작

메서드를 실행하면 스택 위에 스택 프레임 생성

main -> main
직접 만든 Thread -> run


start 호출 -> Thread-0 시작, run 메서드 호출
main -> run을 실행하는 것이 아닌, Thread-0이 run 메서드. 호출
main 스레드는 단지 start() 메서드를 통해 Thread-0 스레드에게 실행을 지시

스레드간 실행 순서는 보장 하지 않음

start Vs Run

  • run 메서드를 직접 호출 한 경우
public class BadThreadMain {
    public static void main(String[] args) {
        //main이 실행 중
        //Thread-0 : run() -> 실행
        System.out.println("Thread.currentThread().getName()" + " : main() start"); // 실행중인 스레드 호출

        HelloThread helloThread = new HelloThread();
        System.out.println("Thread.currentThread().getName()" + " : start() 호출 전");

        //별도의 스레드가 run을 호출하는 것이 아닌, main 스레드가 run 메서드를 호출
        helloThread.run(); // run 호출
        System.out.println("Thread.currentThread().getName()" + " : start() 호출 후");

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


main 스레드가 HelloThread 인스턴스에 있는 run 이란 메서드 호출


스레드의 start() 메서드는 스레드에 스택 공간을 할당하면서 스레드를 시작하는 아주 특별한 메서드
main 스레드가 아닌 별도의 스레드에서 재정의한 run() 메서드를 실행하려면, 반드시 start() 메서드를 호출

데몬 스레드

  • 사용자 스레드
    프로그램의 주요 작업을 수행
    작업이 완료될 때 까지 실행
    모든 User 스레드 종료 -> JVM 종료

  • 데몬 스레드
    백그라운드에서 보조적인 작업을 수행한다.
    모든 user 스레드가 종료되면 데몬 스레드는 자동으로 종료된다.

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

        //main이 실행 중
        //Thread-0 : run() -> 실행
        System.out.println("Thread.currentThread().getName()" + " : main() start"); // 실행중인 스레드 호출

        DemonThread demonThread = new DemonThread();
        demonThread.setDaemon(true); //데몬 스레드 여부
        //false로 변경 -> 10 초 기다리고 종료 함
        demonThread.start(); // run 호출 하면 안됨
        System.out.println("Thread.currentThread().getName()" + " : main() end");
        //프로그램 바로 종료 -> main 스레드가 end까지 실행 -> 자바 종료
        //10초 기다리지 않고 main 스레드 종료 해버림
    }

    //Thread의 보조적 역할
    static class DemonThread extends Thread {
        @Override
        public void run() {
            System.out.println("Thread.currentThread().getName()" + " : run() start");

            try {
                Thread.sleep(10000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println("Thread.currentThread().getName()" + " : run() end");
        }
    }
}

demonThread.setDaemon(true); : 데몬 스레드 여부, start 이전에 결정해야 함, main 스레드 종료시 자바 종료
demonThread.setDaemon(false); : main 스레드 종료 -> user Thread-0이 종료 되기 전까지 자바 종료 안됨

스레드 생성 - Runnable

Runnable Inteface

 package java.lang;
 public interface Runnable {
     void run();
}
//실무에서는 이 방법 주로 사용
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) {
        System.out.println(Thread.currentThread().getName() + " : main() start");

        HelloRunnable helloRunnable = new HelloRunnable();
        Thread thread = new Thread(helloRunnable); // 작업을 넣어줄 수 있다.
        thread.start();

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

Thread 상속 vs Runnable 구현

Runnable 인터페이스 구현 방식 사용 추천

  • Thread 상속
    장점 : 간단한 구현, run 메서드만 재정의 하면 그만
    단점 : 상속의 제한, 유연성 부족

  • Runnable 인터페이스 구현
    장점 : 다른 클래스 상속 받아도 구현 문제 없음, 코드의 분리, 여러 스레드가 동일한 Runnable 객체 공유 가능
    단점 : 코드 복잡성 약간 증가

여러 스레드 만들기

public class ManyThreadMainV1 {
    public static void main(String[] args) {
        log("main() start");

        HelloRunnable helloRunnable = new HelloRunnable();
        Thread thread1 = new Thread(helloRunnable);
        thread1.start();
        Thread thread2 = new Thread(helloRunnable);
        thread2.start();
        Thread thread3 = new Thread(helloRunnable);
        thread3.start();

        log("main() end");
        //실행 순서를 보장 할 수 없음
        //스레드 생성할 때 모두 같은 HelloRunnable 인스턴스의 스레드 작업 실행
        //HelloRunnable 인스턴스에 있는 run 메서드 실행
    }
}


인스턴스 스레드의 실행 작업으로 모든 스레드 전달

public class ManyThreadMainV2 {
    public static void main(String[] args) {
        log("main() start");

        HelloRunnable runnable = new HelloRunnable();
        for (int i = 0; i < 100; i++) {
            Thread thread = new Thread(runnable);
            thread.start();
        }

        log("main() end");
    }
}

Runnable 만드는 다양한 방법

public class InnerRunnableMainV1 {
    public static void main(String[] args) {
        log("main() start");
        Runnable runnable = new MyRunnable();
        Thread thread = new Thread(runnable);
        thread.start();

        log("main() end");
    }

    //특정 클래스 안에서만 사용하는 경우 중첩 클래스 사용
    static class MyRunnable implements Runnable {

        @Override
        public void run() {
            log("run()");
        }
    }
}

public class InnerRunnableMainV2 {
    public static void main(String[] args) {
        log("main() start");

        //익명 클래스 사용
        Runnable runnable = new Runnable() {
            public void run() {
                log("run() start");
            }
        };

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

        log("main() end");
    }
}


public class InnerRunnableMainV3 {
    public static void main(String[] args) {
        log("main() start");

        //익명 클래스 변수 없이 전달
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                log("run()");
            }
        });

        thread.start();
        log("main() end");
    }
}

public class InnerRunnableMainV4 {
    public static void main(String[] args) {
        log("main() start");

        //람다
        Thread thread = new Thread(() ->
                log("run()"));

        thread.start();
        log("main() end");
    }
}
profile
개발자를 향해 가는 중입니다~! 항상 겸손

0개의 댓글