쓰레드

김민성·2022년 7월 14일
0

Java

목록 보기
33/47
post-thumbnail

프로세스와 쓰레드

프로세스란 실행중인 프로그램 을 의미한다.

프로세스는 프로그램을 수행하는 데 필요한 데이터와 자원 그리고 쓰레드로 구성되어 있으며, 실제로 작업을 수행하는 것이 바로 쓰레드이다.

모든 프로세스에는 최소한 하나 이상의 쓰레드가 존재하며, 둘 이상의 쓰레드를 가진 프로세스를 멀티쓰레드 프로세스 라고 한다.

쓰레드는 프로세스라는 작업 공간에서 일하는 일꾼이라고 생각하면 좋다.

멀티태스킹과 멀티쓰레딩

  • 멀티태스킹 : 여러 개의 프로세스가 동시에 실행되는 것

  • 멀티쓰레딩 : 하나의 프로세스에서 여러 개의 쓰레드가 동시에 작업하는 것.

    CPU 코어는 한 번에 하나의 작업만 수행할 수 있다. -> 작업의 개수 = 코어의 개수

    쓰레드의 수는 언제나 코어의 개수보다 많다. -> 각각의 코어가 아주 짧은 시간동안 여러 작업을 번갈아가며 수행한다. 이는 매우 짧은 시간 사이에 일어나므로, 작업이 동시에 수행되는것 처럼 보인다.

멀티쓰레딩의 장단점

장점

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

단점

  • 동기화 문제

  • 교착상태 문제

    교착상태 : 두 쓰레드가 자원을 점유한 상태에서 서로 상대방의 자원을 사용하려고 기다리느라 진행이 멈춘 상태.

쓰레드의 구현

쓰레드의 구현은 두 가지 방법이 있다.

  1. Thread 클래스 상속

    public class Main {
        public static void main(String[] args) {
            Thread1 t1 = new Thread1();
    
            t1.start();
        }
    }
    
    class Thread1 extends Thread{
        public void run(){
            for(int i=0; i<5; i++){
                System.out.println(getName());
            }
        }
    }
    
    /*
    출력 결과
    Thread-0
    Thread-0
    Thread-0
    Thread-0
    Thread-0
    */

    Thread 클래스를 상속받은 경우에는 객체를 생성할 때, 자손 클래스의 객체를 생성한다.

    그리고 getName() 메서드를 호출해 이름을 반환할 수 있다.

  2. Runnable 인터페이스를 구현

    Runnable 인터페이스란 run() 메서드만 정의된 간단한 인터페이스이다.

    public class Main {
        public static void main(String[] args) {
            Runnable r = new Thread2();
            Thread t2 = new Thread(r);
    
            t2.start();
        }
    }
    
    class Thread2 implements Runnable{
        public void run(){
            for (int i=0; i<5; i++){
                System.out.println(Thread.currentThread().getName());
            }
        }
    }
    
    /*
    출력 결과
    Thread-0
    Thread-0
    Thread-0
    Thread-0
    Thread-0
    */

    Runnable 인터페이스를 구현한 경우에는 객체를 생성할 때, 구현 클래스의 객체를 생성한 다음, 이 객체를 Thread 클래스의 생성자의 매개변수로 제공한다.

    Runnable 인터페이스의 구현 클래스 멤버는 run() 메서드 밖에 없기 때문에 getName()을 호출하려면 Thread.currentThread().getName()과 같이 써야한다.

아래의 두 가지 이유로 인해 일반적으로 Runnable 인터페이스를 구현하는 방식을 사용한다.

  • Thread 클래스를 상속받는 방식을 사용하면 다른 클래스를 상속받을 수 없다.

  • 객체지향적인 관점에서 Runnable 인터페이스를 구현하는 방법이 재사용성이 높고 코드의 일관성을 유지할 수 있다.

위의 코드에서 쓰레드를 실행시킬 때, run() 을 직접 호출하지 않고, start() 를 호출한다.

run() 을 호출하는 것은 쓰레드의 실행이 아닌, 단순히 선언된 메서드를 호출하는 것일 뿐이라는 점을 기억하자.

start() 메서드는 새로운 쓰레드가 작업을 실행하는데 필요한 공간(호출스택) 을 생성한 후에 run()을 호출해서, 생성된 호출스택에 run()이 첫 번째로 올라가게 한다.

한 번 실행이 종료된 쓰레드는 다시 실행할 수 없다. 즉, 하나의 쓰레드는 start()가 한 번만 호출될 수 있다.

Thread1 t1 = new Thread1();
t1.start();
// t1.start();	한 번 더 호출시 IlleagalThreadStateException 발생
// 따라서 다시 수행해야 한다면 아래와 같이 새로운 쓰레드 생성
t1 = new Thread1();
t1.start();

멀티쓰레드

그렇다면 위의 Thread1 클래스와 Thread2 클래스를 멀티 쓰레드로 처리해보자.

반복문이 너무 짧으면 멀티쓰레드를 확인할 수 없으므로 300번씩 반복해서 "0"과 "1"를 출력하게 만들었다.

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

        Runnable r = new Thread2();
        Thread t2 = new Thread(r);

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

    }
}

class Thread1 extends Thread{
    public void run(){
        for(int i=0; i<300; i++){
            // 줄바꿈을 없애기 위해 printf 사용
            System.out.printf("%s", new String("0"));
        }
    }
}

class Thread2 implements Runnable{
    public void run(){
        for (int i=0; i<300; i++){
            System.out.printf("%s", new String("1"));
        }
    }
}
/*
출력 결과
000000000000000000001111111111111111111111111111111111111111111111111111111111111111101111111111111111111111111111111111111111111111111111111111111100000000000000000000000000000000000000001111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
*/

위의 출력 결과와 같이 순차적으로 실행되는 것이 아닌, 동시에 실행 되는 것을 확인할 수 있다.

싱글쓰레드와 멀티쓰레드의 사용은 상황에 따라 효율이 다르니 잘 고려해서 사용하는 것이 좋다.

0개의 댓글