Java

[Java의 정석] chapter13 쓰레드 thread 알아보기

고쩡이 2024. 7. 18. 19:06

본 글은 위 책을 공부하고 정리한 내용입니다:)

답답해서 공부해보는 thread...자바의 정석 책을 다시 펼쳤다..

◼️쓰레드 구현과 실행

🟢 쓰레드 구현 방법 두가지

  1. Thread클래스를 상속  (단점: 다른클래스 상속받을수없음)
  2. Runnable인터페이스를 구현
//1.Thread 클래스를 상속
class MyThread extends Thread{
    public void run() { /*작업내용*/ }
}
//2.Runnable 인터페이스를 구현
class MyRunnable implements Runnable{
	public void run() { /*작업내용*/ }
}

 

🔶예시 코드

public class ThreadEx1 {
    public static void main(String[] args) {
        ThreadEx1_1 t1 = new ThreadEx1_1();

        Runnable r = new ThreadEx1_2(); //Runnable 인터페이스를 구현한 클래스의 인스턴스를 생성
        Thread t2 = new Thread(r); // 생성자 Thread(Runnable target)

        t1.start();
        t2.start();

        //쓰레드의 작업을 한 번 더 수행해야 할 시 새로운 쓰레드를 생성해야한다.
        //t1.start(); //IllegalthreadStateException 발생

    }
}

class ThreadEx1_1 extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            // 조상인 thread getName 호출
            System.out.println(getName());
        }
    }
}
class ThreadEx1_2 implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            // 현재 실행중인 Thread 반환
            System.out.println(Thread.currentThread().getName());
        }
    }
}

◼️start(), run()

근데 한가지 의문이든다. 위 코드에서 왜 run()이 아니라 start()를 호출할까?

🟢run() vs start()

  • run(); 쓰레드실행이아닌 단순 선언 메서드를 호출하는것이다.
  • start(); 새로운 쓰레드가 작업을 실행하는데 필요한 호출스택(call stack)을 생성 후 run()을 호출한다. 따라서 생성호출스택에 run()이 첫번째로 올라가게 된다.

run()
start()

이때 호출스택에선 최상위 메서드라도 실행중이 아닌 '대기상태'에 있을 수 있다.

스케줄러는 실행대기중인 쓰레드들의 우선순위를 고려해 실행순서와 실행시간을 결정한다.

 

참고로 당연히, main 메서드를 수행하는 것도 쓰레드이다.이때 실행 중인 사용자 쓰레드가 하나도 없을 때 프로그램은 종료된다.예를들어 아래처럼 쓰레드를 시작할때 throwException 예외를 발생시켜보자.

public class ThreadEx2 {
    public static void main(String[] args) {
        System.out.println("main 스레드 시작");
        ThreadEx2_1 t1 = new ThreadEx2_1();
        t1.start();
        System.out.println("main 스레드 종료");
    }
}

class ThreadEx2_1 extends Thread {
    @Override
    public void run() {
        throwException();
    }
    public void throwException() {
        try {
            throw new Exception();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

실행 결과

start()로 호출했기에 main 스레드와 t1 쓰레드가 독립적으로 실행된다. 한 쓰레드가 예외발생으로 종료되어도, 다른 쓰레드엔 전혀 영향을 미치지않는다. 여기서 main쓰레드는 이미 종료되었기 때문에 호출스택이 없다.

public class ThreadEx3 {
    public static void main(String[] args) {
        System.out.println("main 스레드 시작");
        ThreadEx3_1 t1 = new ThreadEx3_1();
        t1.run();
        System.out.println("main 스레드 종료");
    }
}

class ThreadEx3_1 extends Thread {
    @Override
    public void run() {
        throwException();
    }
    public void throwException() {
        try {
            throw new Exception();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

이전과 달리 위는 run() 메서드가 main 스레드에서 실행된다. 즉, 새로운 쓰레드가 생성되지 않는다. 호출스택에도 main이 포함되어있다.

 

??모르겟는것

동일한 콜스택에있으면 main스레드종료 출력문이 exception 후에 떠야되는거아닌가?; 잘모르겟음

==>아하! printStackTrace와 일반출력은 서로 아예 다른 파이프이기때문에 출력순위가 보장X!!!!

call stack

◼️싱글 코어, 멀티 코어 비교

싱글 코어 vs 멀티 코어

싱글 코어는 한개의 코어가 두가지 일을 진행하므로 겹칠 일이 없다.

반면 멀티 코어는 두 스레드 작업이 동시에 수행될 수 있으므로 작업이 겹칠 수 있는데, 이때 쓰레드 경쟁이 일어날 수도 있다.

예를들어 console 자원을 놓고 두 쓰레드가 경쟁할 수 있는 것이다.

 

🟢정리

  • 싱글코어에서 단순 계산 작업은 싱글스레드가 더 효율적이다.
    • 이유: 싱글스레드는 문맥 전환(Context Switching) 오버헤드가 없기 때문이다.
  • 두 쓰레드가 서로 다른 자원을 사용할 경우 멀티스레드가 효율적이다.
    •  동일한 자원에 대해 한 스레드가 작업하는 동안 다른 스레드는 대기 상태일 수 있다.
    • 멀티코어일 경우, 한 스레드가 입력을 기다리는 동안 다른 스레드가 작업을 처리할 수 있어 효율적이다.
    • 즉, 서로 다른 자원을 사용하는 경우, 스레드 간 자원 충돌이 없으므로 더욱 효율적.

◼️쓰레드 우선순위

쓰레드 우선순위를 지정할 수 있다. 우선순위 범위는 1~10이며 숫자가 높을수록 우선순위가 높다.

class ThreadEx8 {
    public static void main(String args[]) {
        ThreadEx8_1 th1 = new ThreadEx8_1();
        ThreadEx8_2 th2 = new ThreadEx8_2();

        th2.setPriority(7); //쓰레드 보통우선순위를 7로 변경
        //따로 우선 순위를 지정하지 않을 경우 main의 기본 우선순위인 5을 할당
        //setPriority() : 쓰레드 우선순위 지정
        //getPriority() : 쓰레드 우선순위 반환
        System.out.println("Priority of th1(-) : " + th1.getPriority() ); // 5
        System.out.println("Priority of th2(|) : " + th2.getPriority() ); //7

        //또한, Thread를 실행하기 전에만 우선순위를 정할 수 있다.
        th1.start();
        th2.start();
    }
}

class ThreadEx8_1 extends Thread {
    public void run() {
        for(int i=0; i < 300; i++) {
            System.out.print("-");
            for(int x=0; x < 10000000; x++);
        }
    }
}

class ThreadEx8_2 extends Thread {
    public void run() {
        for(int i=0; i < 300; i++) {
            System.out.print("|");
            for(int x=0; x < 10000000; x++);
        }
    }
}

실행 결과

싱글코어의 경우, 우선순위가 7인 th2가 th1보다 높으므로 th2를 우선적으로 해결한 후 th1 수행한다.|||~을 거의모두수행하고 ---가 실행됨을 예상 가능하다.

멀티코어의 경우, 우선순위가 뭐가 됐건, 우선순위에 따른 차이가 거의 없다. (각 스레드가 별도의 코어에서 동시에 실행되기 때문에, 우선순위가 실행 시간에 큰 영향을 미치지 않는다.)