프로세스와 쓰레드

프로세스란 실행중인 프로그램이라고 이해하면 쉽다.

이를 실행하면 OS(운영체제)로 부터 실행에 필요한 자원을 할당받아 프로세스가 된다.

이러한 프로세스쓰레드로 구성이 되어있다.

쓰레드란 프로세서의 자원을 이용해서 실제로 작업을 수행하는 것이다.

으러한 쓰레드가 둘 이상으로 늘어나면 이를 멀티쓰레드 프로세스라고 한다.

멀티태스킹과 멀티쓰레딩

멀티쓰레딩은 하나의 프로세스 내에서 여러 쓰레드가 동시에 작업을 수행하는 것이다.

정말 쉬운 예제가 있다.

image

이런 코어가 사람이라고 생각하고 쓰레드가 손이라고 생각한다면

코어는 단 하나의 작업만 할 수 있다. 왜냐하면 사람 혼자니까

하지만 손을 여러개를 사용하여 금방 문제를 끝낼 수 있다. 라고 생각하면 된다.

그러면 쓰레드가많아지면 그만큼 성능이 좋다? 라고는 꼭 말할 수 는없다.

멀티쓰레딩의 장단점

멀티쓰레딩의 장점

  • CPU의 사용률을 향상시킨다.
  • 자원을 보다 효율적으로 사용할 수 있다.
  • 사용자에 대한 응답성이 향상된다.
  • 작업이 분리되어 코드가 간결해진다.

예를들어 멀티쓰레딩을 못한다면 다음과 같은 상황이 벌어진다.

내가 유튜브를 보면서 친구와 카톡을 하지 못하는 것이다. 오직 한가지만 사용을 해야한다.

그래서 이런 서버 프로그램들은 서버 프로세스를 여러가지 생성하는 것 보다 쓰레드를 생성을 하는 것이 좀더 시간 적인 측면과 메모리적인 측면에서 이득을 볼 수 있다.

이러면 멀티쓰레딩만 쓰면 무조건 좋겠네? 라고 생각하는데 그렇지만은 않다.

멀티쓰레딩을 사용하기 위해서는 synchronization, deadlock 같은 문제점이 발생하기 때문에 신중히 사용해야한다.

쓰레드의 구현과 실행

쓰레드를 사용하기 위해서는 2가지 방법이 있다.

  1. Thread클래스를 상속받는 법
  2. Runnable인터페이스를 구현하는 방법

보통은 자바에서는 단일상속을 하기 때문에 만약 Thread클래스를 상속받아버리면

다른 클래스들을 상속받을 기회가 사라지기 때문에 2번의 방법을 선호한다.

Runnable()인터페이스는 다음과 같이 run()만 정의 되어있는 간단한 인터페이스이다.

class MyThread implements Runnable {
    public void run() { /... }
}

이러한 Thread클래스를 상속받아 사용할 때와 Runnable인터페이스를 구현한 경우의 인스턴스 생성방법은 다르다.

// Thread의 인스턴스 생성방법 (1)
ThreadEx t1 = new TreadEx();

// Runnable 인스턴스 생성방법 (1)
Runnable r = new ThreadEx();
Thread t2 = new Thread(r);

// Runnable 인스턴스 생성방법 (2)
Thread t2 = new Thread(new ThreadEx()); // 2줄이아닌 한번에 생성하는 법

또한, 둘의 차이점은 currentThread()에도 있다. 이는 현재 실행중인 쓰레드의 참조를 반환한느 메서드이다.

만약 Thread클래스에서 상속받았다면 부모인 Thread클래스의 메서드를 직접 호출이 가능하지만

Runnable인터페이스에서 상속받았다면 사용하기 위해서 Thread클래스의 static메서드인 currentThread()를 호출하는 방법으로 사용할 수 있다.

다음은 이에대한 예시이다.

// Thread클래스를 상속받았을 때
class ThreadEx extends Thread{
    @Override
    public void run() {
        System.out.println(getName());
    }
}

//Runnalbe 인터페이스를 사용할 때
class ThreadRunnable implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}

다음 같이 Runnable인터페이스를 사용한 run()메서드는 Thread클래스를 사용해 static함수를 사용하여 불러오는 것을 볼 수 있다.

쓰레드의 실행

쓰레드를 생성해도 start()메서드를 호출하지 않으면 실행이 되지않는다.

그렇다고 start()메서드를 사용했다고 바로 실행되는 것이 아니라

실행 대기 상태에 있다가 자신의 차례일 때 실행이 된다.

이러한 실행순서는 OS에서 작성한 스케쥴에 의해 실행이 된다.

또한, 한 번 실행이 종료된 쓰레드는 다시실행 할 수없다는 점이 특징이다.

그래서 만약 start()메서드를 한 쓰레드에 대해 2번실행 한다면 IllegalThreadStateException이 발생하게 된다.

start()와 run()

이러한 start()run()메서드의 의미는 비슷한것처럼 느껴지지만 다르다.

일단 run()은 생성된 쓰레드를 실행시키는 것이 아닌 단순히 클래스에 선언된 메서드를 호출하는 것일 뿐이다.

그렇지만 start()는 새로운 쓰레드가 작업을 실행하는데 필요한 호출스택을 생성한 다음에 run()을 호출해 생성된 호출에 run()이 첫 번째로 올라가게 한다.

이러한 호출스택은 쓰레드가 종료되면 작업에 사용된 호출스택은 소멸된다.

정리하자면 다음과 같이 실행이 된다.

  1. main메서드에서 쓰레드의 start()를 호출한다
  2. start()는 새로운 쓰레드를 생성하고, 쓰레드가 작업하는데 사용될 호출스택을 생성한다.
  3. 새로 생성된 호출스택에 run()이 호출되어, 쓰레드가 독립된 공간에서 작업을 수행한다.
  4. 호출스택은 이제 2개이므로 스케줄러가 정한 순서에 의해서 번갈아 가면서 실행된다.

우리가 사용하는 main메서드도 작업을 수행하는 쓰레이드이다. 그래서 프로그램을 실행하면 우리도 모르게 하나의 쓰레드를 생성하여 main메서드를 수행한다.

싱글쓰레드와 멀티쓰레드

싱글쓰레드는 2개의 작업을 실행할 때 작업이 한 작업을 끝마치고 나서야 다른 작업을 처리할 수 있다.

하지만 멀티쓰레드는 쓰레드가 번갈아가면서 작업을 한다. 이 작업이 매우 짧은 시간이라 동시에 두 작업이 처리되는 것과 같이 느끼게 한다.

하지만 이런것이 멀티쓰레드가 꼭 빠르다고 볼 수는 없다. 왜냐하면 번갈아가면서 작업을 하기 때문에 쓰레드간 작업 전환에 시간이 더 걸리기 때문에 더 많은 시간이 소요된다.

또는, 한 쓰레드가 작업을 하고 있는 동안 다른 쓰레드는 작업이 끝나기를 기다려야하는데 이때 발생하는 대기시간 때문에 그렇다.

결론적으로는, 하나의 자원을 사용하는 작업에 대해서는 싱글쓰레드가 효율적이다. 라고 말할 수 있다.

이러한, 멀티쓰레드로 진행한 작업은 실행할 때마다 다른 결과를 얻을 수 있다.

이유는 바로 프로세스가 OS의 프로세스 스케줄러의 영향을 받기 때문이다.

하지만 여러 개의 작업을 사용하는 작업일 떄는 멀티 쓰레드가 더 효율 적이다.

예를들어 메세지를 사용한다고 해보자

여기서의 작업은 2가지가 있다.

  1. 사용자의 입력을 기다리는 것
  2. 사람들의 카톡을 받는것

이거는 그냥 예시인것으로 기억하자!! 실제로는 아니다.

싱글쓰레드인 경우는 사용자가 입력을 할때 입력하는 작업에 집중해야 하기 때문에 다른사람의 카톡을 읽어오는 작업을 할 수 가 없다.

왜냐하면 싱글쓰레드는 한가지의 작업이 끝난 뒤 다음 작업을 할 수 있기 때문이다.

하지만, 만약에 멀티쓰레드라면 사용자의 입력을 하는 도중 다른 쓰레드가 입력을 기다릴 때

다른 사람의 메시지도 가져오는 것이다.

결론적으로, 여러개의 자원을 사용하는 작업에 대해서는 멀티쓰레드가 효율적이다.

다음 예시를 보자, 다음 예시는 2가지의 작업이 있다.

  1. 사용자의 입력을 받는 작업
  2. 10~1까지 출력하는 작업이 있다.

씽글쓰레드라면 다음과 같이 작동한다.

싱글쓰레드

//싱글 쓰레드 일때
public class temp {
    public static void main(String[] args) {

        String input = JOptionPane.showInputDialog("값을 입력해 주세요.");
        System.out.println("입력한 값은" + input + "입니다.");

        for(int i = 10 ; i > 0 ; i--){
            System.out.println(i);
            try{
                Thread.sleep(1000);
            }catch (Exception e) {}
        }

    }
}

멀티쓰레드 일때는 다음과 같다.

//멀티 쓰레드 일 때
public class temp {
    public static void main(String[] args) {

       ThreadEx th1 = new ThreadEx();
       th1.start();

       String input = JOptionPane.showInputDialog("값을 입력해 주세요");
        System.out.println("입력한 값은" + input + "입니다.");

    }
}

class ThreadEx extends Thread{
    @Override
    public void run() {
        for(int i = 10 ; i > 0 ; i--){
            System.out.println(i);
            try{
                sleep(1000);
            } catch (Exception e) {}
        }
    }
}

멀티쓰레드

쓰레드의 우선순위

쓰레드는 우선순위(priority)라는 속성을 가지고 있다.

이러한 우선순위의 순서에 따라서 특정 쓰레드가 더 많은 작업시간을 갖도록 할 수도 있고

그렇기 때문에 사용자에게 빠르게 반응해야하는 특정작업들의 우선순위를 높여야 한다.

쓰레드의 우선순위 지정하기

쓰레드의 우선순위를 변경하는 메서드들은 다음과 같다.

void setPriority(int newPriority) // 쓰레드의 우선순위를 변경하는 메서드
int getPriority() // 쓰레드의 우선순위를 반환하는 메서드

// 전역변수
public static final int MAX_PRIORITY = 10 // 최대 우선순위
public static final int MIN_PRIORITY = 1 // 최소 우선순위
public static final int NORM_PRIORITY = 5 // 보통 우선순위

쓰레드의 우선순위는 특징이 있다.

  • 우선순위는 1~10이고 이는 숫자가 높을수록 우선순위가 높다
  • 쓰레드의 우선순위는 쓰레드를 실행하기 전에만 변경이 가능하다.
  • 멀티코어에서는 쓰레드의 우선순위에 따른 차이가 거의 없다.
    • 멀티코어에서는 쓰레드에 높은 우선순위를 주면 더 많은 실행시간과 실행기회를 갖게 될 것이라고 기대할 수 없다.
    • 빨리 끝나도 나머지 작업을 끝내야하기 때문에 그렇다. 시간이 비슷하다.
  • 어떤 OS에서 실행하느냐에 따라 스케줄러가 다르기 때문에 다른 결과를 얻을 수 있다.

쓰레드 그룹

쓰레드 그룹이란 서로 관련된 쓰레드를 그룹으로 다루기 위한 것이다.

이러한 쓰레드 그룹은 보안상의 이유로 도입된 개념이다.

쓰레드 그룹에 포합시키기 위해서는 Thread의 생성자를 이용해야한다.

Thread(ThreadGroup group, String name)
Thread(ThreadGroup group, Runnable target)
Thread(ThreadGroup group, Runnable target, String name)
Thread(ThreadGroup group, Runnable target, String name, long stackSize)

모든 쓰레드는 반드시 쓰레드 그룹에 포함되어 있어야 한다.

그렇기 때문에 만약 쓰레드 그룹을 지정하는 생성자를 사용하지 않은 쓰레드는

기본적으로 자신을 생성한 쓰레드와 같은 그룹에 속하게 된다.

자바어플리케이션이 실행될때 JVM은 쓰레드 그룹을 생성한다.

JVMmainsystem이라는 쓰레드 그룹을 생성한다

이후 필요한 쓰레들을 생성해서 mainsystem쓰레드 그룹에 분류한다.

만약, 쓰레드그룹을 지정하지 않는다면 보통은 main쓰레드 그룹에 속하게 된다.

이러한 그룹에 관한 메서드는 다음과 같다.

ThreadGroup getThreadGroup() // 자신이 속한 쓰레드 그룹을 반환한다.
void uncaughtException(Thread t, Throwble e) // 쓰레드 그룹의 쓰레드가 처리되지 않은
//예외에 의해 실행이종료될 때, JVM에 의해 자동적으로 이 메서드가 호출된다.

Reference

  • 자바의 정석

'Java > Java' 카테고리의 다른 글

[Java/Study] 지네릭 타입  (0) 2021.09.19
[Java/Study] java.time 패키지  (0) 2021.09.18
[Java/Study] 예외처리  (0) 2021.09.14
[Java/Study] 내부 클래스  (0) 2021.09.11
[Java/Study] 인터페이스  (0) 2021.09.11

+ Recent posts