자바 스레드

바그다드·2023년 7월 7일
0

이번 포스팅에서는 자바에서 스레드를 이용해 멀티쓰레딩을 구현하는 방법을 알아보려고 한다.

1. 스레드 생성

자바에서 스레드를 생성하는 방법은 두 가지가 있다.
1. Thread 상속

  • run메서드를 오버라이드 해줘야 한다.
class Task1 extends Thread {
	@Override
    public void run() { // 이부분은 이대로 사용해줘야함
    	System.out.println("task1 started \n");

        for (int i = 101; i <= 199; i++) {
            System.out.print(i +" ");
        }
        // 스레드를 1초동안 대기
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        // 스케쥴러에 대한 힌트로
        // thread가 cpu를 사용가능한 이 상태를 양보하겠다는 뜻
        // 이것도 강제되는 것이 아니고 스케줄러가 이를 무시할 수 있음
        Thread.yield();

        // synchronized(동기화)
        // 특정한 시점에 하나의 thread만 동기화된 메서드들을 사용할 수 있음
        // 동기화된 모든 메서드들을 특정 시점에는 하나의 스레드만이 사용 가능하고
        // 다른 스레드들은 해당 스레드의 작업이 끝날때까지 대기해야하므로
        // 오버헤드가 발생

        System.out.println("\n task1 done");
    }
}
  1. Runnable 구현
  • 마찬가지로 run메서드를 오버라이드 해줘야 한다.
class Task2 implements Runnable {

    @Override
    public void run() {
        System.out.println("task2 started \n");

        for (int i = 201; i <= 299; i++) {
            System.out.print(i +" ");
        }
        System.out.println("\n task2 done");
    }
}

Thread.sleep(1000);

스레드를 1초동안 대기시킴
예외가 발생할 수 있으므로 try-catch로 감싸주자

Thread.yield();

스케줄러에게 보내는 힌트로 스레드가 cpu를 활용할 수 있는 권한을 양도한다는 뜻이다.
다만, 강제되는 것이 아니기 때문에 스케줄러가 이를 무시할 수 있다.

synchronized(동기화)

먼저 자료구조 중에 HashTable을 확인해보자

HashTable 클래스를 확인해보면 synchronized라는 키워드가 여러 메서드에 붙어있는 것을 알 수 있는데, 이는 동기화를 뜻한다.
멀티스레드 환경에서 어떤 하나의 메서드가 해당 객체에서 synchronized키워드가 붙은 메서드를 사용하고 있다면, 다른 스레드는 선점하고 있는 스레드의 작업이 완료될 때까지 synchronized키워드가 붙은 어떤 메서드도 사용하지 못한다.
해당 작업이 완료될 때까지 대기해야 하기 때문에 오버헤드가 발생한다.

2. 스레드 생명주기

NEW

객체 생성 등을 통해 스레드를 초기화한 상태

RUNNABLE

start()가 호출 시
실행 예정이지만 다른 스레드 실행 등의 이유로 실행되지는 준비하고 있는 상태

RUNNING

현재 실행중인 상태

BLOCKED/WAITING

  • BLOCKED
    특정한 이유로 RUNNING상태에서 BLOKED상태로 이동할 수 있음
  • WAITING
    외부 인터페이스나 DB등에서 어떤 입력을 위해 대기하고 있거나,
    실행이 완료되지 않은 다른 threod로부터 데이터를 입력받아야 하는 상태

TERMINATED/DEAD

스레드의 실행이 완료되거나, stop() 메서드가 호출되어 스레드가 종료되었을 때의 상태

3. 스레드 호출

class Task1 extends Thread {
    @Override
    public void run() { // 이부분은 이대로 사용해줘야함
        System.out.println("task1 started \n");

        for (int i = 101; i <= 199; i++) {
            System.out.print(i +" ");
        }
        // 스레드를 1초동안 멈춤
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        Thread.yield();

        System.out.println("\n task1 done");
    }
}

class Task2 implements Runnable {

    @Override
    public void run() {
        System.out.println("task2 started \n");

        for (int i = 201; i <= 299; i++) {
            System.out.print(i +" ");
        }
        System.out.println("\n task2 done");
    }
}

public class ThreadBasicsRunner {
    public static void main(String[] args) throws InterruptedException {
        // task1
        System.out.print("task1 kicked off \n");
        Task1 task1 = new Task1();
        task1.run(); // 이렇게 사용하면 이 메서드가 끝난 다음에 다음 코드가 실행이 됨
        task1.setPriority(1);
        task1.start(); // 이렇게 하면 병렬처리가 됨

        // task2
        // Runnable을 이용한 멀티쓰레딩
        System.out.print("task2 kicked off \n");
        Task2 task2 = new Task2();
        Thread task2Thread = new Thread(task2);
        task2Thread.setPriority(10);
        task2Thread.start();

        //task1이 완료될 때까지 기다리기
        task1.join();
        task2Thread.join();
        // task1,2가 완료가 되어야만 다음 로직이 실행이 됨

        // task3
        System.out.print("task3 kicked off \n");
        for (int i = 301; i <= 399; i++) {
            System.out.print(i +" ");
        }
        System.out.println("\n task3 done");
        System.out.println("main done");
    }
}

setPriority() - 우선순위 부여

  • task1.setPriority(1);
    스레드의 우선순위 부여하는 메서드이다.
    최소 우선순위 1
    보통 우선순위 5
    최고 우선순위 10

  • 다만, 무조건적으로 우선순위가 정해지는 것이 아니라 권장? 추천?정도의 뜻으로 반드시 이 우선순위가 지켜지는 것은 아니다.

스레드 실행

  • task1.run();
    선언한 객체의 run메서드 호출
  • task1.start();
    스레드 객체의 메서드 실행

run과 start의 차이?

start()

  • 새로운 스레드가 생성되며 스레드가 실행되면 run메서드를 실행시킨다.

  • 동일한 객체에서 두번 이상 호출 시 IllegalThreadStateException이 발생한다.

    • 아래 캡처를 보면 스레드가 NEW 상태일 때가 0이란 것을 알 수 있다.
  • 멀티스레드로 동작한다.

run()

  • 스레드를 생성하지 않으며 run()메서드만 실행한다.
    • 단순히 객체를 생성하고 객체 내의 메서드를 호출하는 것과 같다
  • 호출수에 제한이 없다.
  • 싱글스레드로 동작한다.

join() - 스레드 제어

task1.join();
: task1 스레드의 실행이 완료될 때까지 기다린다.
위에서 작성했던 코드의 일부를 보자

        //task1이 완료될 때까지 기다리기
        task1.join();
        task2Thread.join();
        // task1,2가 완료가 되어야만 다음 로직이 실행이 됨

        // task3
        System.out.print("task3 kicked off \n");
        for (int i = 301; i <= 399; i++) {
            System.out.print(i +" ");
        }
        System.out.println("\n task3 done");
        System.out.println("main done");

  • task1, task2 스레드의 작업이 완료된 이후에 다음 로직이 실행되는 것을 확인할 수 있다.
    • 참고로 여기서 task3는 스레드가 아닌 단순히 main메서드에서 실행되는 로직이다.
profile
꾸준히 하자!

0개의 댓글