2023년 1월 1일
08:00 AM
Buffering ...

최근 글 👑

[자바]Thread(쓰레드)의 이해와 개념

2022. 12. 12. 00:52ㆍJAVA 기초

1.쓰레드란 무엇인가?

스레드(thread)란 프로세스 내에서 실제로 작업을 수행하는 주체를 의미합니다.

모든 프로세스에는 한 개 이상의 스레드가 존재하여 작업을 수행합니다.

또한, 두 개 이상의 스레드를 가지는 프로세스를 멀티스레드 프로세서라고 합니다.

 

이게 무슨 뜻인지 감이 잡히질 않습니다.

 

일단 스레드를 알기전에 프로세스가 무엇인지 알아야 합니다.

프로세스란?

 

 

저희가 어떤 프로그램을 깔때 그 프로그램을 실행한다고 칩시다.

그렇다면 그 프로그램은 하드디스크의 내용을 메모리로 올려야 합니다.

그리고 그 프로그램이 메모리에 올려질동안 기다리는 시간을 우리는 로딩이라고 부릅니다.

그렇게 로딩이 다 된 상태를 프로세스 라고 합니다.

즉 프로세스라는 것은 모든 곳에서 하나의 쓰레드를 반드시 가지고 있습니다.

CPU와 프로세스는 그 쓰레드라는 것과 소통을 하는 것 입니다.

 

 

사진으로 본다면 이렇게 되어있을 것입니다.

즉 쉽게 얘기한다면 모든 프로세스와 소통을 하기위해선 쓰레드가 있어야 합니다.

그 쓰레드는 모든 프로세스안에 무조건 하나씩 들어있습니다.

 

 

쓰레드의 사용이유

그렇다면 우리는 쓰레드를 왜 사용해야 하는지 알아야합니다.

저희는 자바를 사용할때 main클래스에 메서드를 넣어서 실행 했습니다.

하지만 main쓰레드의 경우 단일쓰레드임으로 실행파일밖에 쓰지 못했습니다.

하지만 쓰레드란 클래스를 사용한다면 실행파일들을 독립적으로 사용 할 수 있습니다.

 

예를들어 이것입니다. 저는 영화를 만들고 싶습니다. 하지만 영화를 만들기 위해서는

영상을 띄워주는 비디오프레임과 자막이 동시에 나와야 합니다.

하지만 main쓰레드는 순차적으로 밖에 나오지 않죠.

그러나 쓰레드를 사용하면 각자 독립적으로 실행파일을 실행시켜 동시에 나오게 할 수 있습니다.

 

 

자 이해가 잘 되지 않습니다.

한번 예시로 보시죠.

 

코드는 이렇게 되어있습니다. for문으로 비디오프레임과 자막을 출력하는 소스코드 입니다.

이것을 실행시키면

 

 

이렇게 비디오 프레임이 나오면 자막이 나오고 비디오프레임이 나오면 자막이 나오고

이런식으로 동시에 나오질 않습니다. 만약에 쓰레드를 쓴다면 어떨까요?

 

 

 

쓰레드의 사용방법

쓰레드의 사용방법은 총 두 가지 방법이 있습니다.

하나는 쓰레드의 클래스를 상속 받는 것 이고

하나는 Runnalbe이라는 인터페이스를 상속받아 사용합니다.

 

 

이것이 첫번째 방법인 쓰레드의 클래스를 상속받아 사용하는 방법입니다.

extends로 쓰레드의 클래스로 상속받아 쓰레드 클래스로 만드는 것 입니다.

 

쓰레드를 실행하기전에 쓰레드의 품은 run이라는 메서드를 반드시 오버라이딩 시켜야합니다.

run이라는 메서드에서 자기가 실행시키고 싶은 소스코드를 적어서 사용해야 그 실행파일은

독립적으로 움직일 수 있습니다.

 

이런식으로 쓰레드의 품은 run의 메서드를 오버라이딩해서 작성해줘야 합니다.

(이유는 Thread의 있는 run의 메서드는 아무동작도 하지않은 빈 껍질 메서드이기때문)

또한 쓰레드를 실행시키기 위해서는 쓰레드의 있는 start()라는 메서드를 실행시켜줘야 합니다.

일단 한번 예제로 보시죠

 

자 저는 저기 위에서 처럼 동시에 자막을 실행시키고 싶습니다.

 

package 자바의정석;

public class ex23 {

    static int[] intarray = {1,2,3,4,5};

    public static void main(String[] args){
        Thread thread = new ex24();
        thread.start();

        for(int i=0;i<intarray.length;i++){
            try {
                Thread.sleep(1000);
                System.out.printf("자막 %d\n",intarray[i]);
            }catch (Exception e){

            }

        }


    }


    }

    class ex24 extends Thread{
        String[] str = {"하나","둘","셋","넷","다섯"};
        @Override
        public void run() {
            for(int i=0;i<str.length;i++){
                try {
                    Thread.sleep(800);
                    System.out.printf("비디오 프레임 : %s\n",str[i]);
                }catch (Exception e){

                }


            }
        }
    }

 

소스코드입니다. 한번 예제로 보죠

 

저는 쓰레드클래스를 상속받았으며

run메서드안에 제가 실행하고싶은 코드를 작성했습니다.

비디오프레임을 하나씩 출력합니다. 그런데 저기 Thread.sleep라는 메서드가 보입니다.

저것은 무엇이나면 sleep안에 숫자를 넣었을때 그 시간동안 멈춰라 입니다.

자바가 sleep코드를 만난다면 0.8초동안 기다리고 그 밑에 블럭을 실행시킵니다.

즉 800millis니까 0.8초동안 멈추고 실행시켜라 입니다. 그렇게 된다면

바로 값이 출력되는 것이 아닌 0.8초마다 비디오 프레임이 나오게 됩니다.

 

 

여기서는 main메서드에서 쓰레드를 실행시켰습니다.

쓰레드의 객체를 만드는 방법은 기존의 상속받은 것 처럼 사용하시면 됩니다.

제가 앞서 말한 것 처럼 쓰레드는 start()메서드를 반드시 호출해줘야 run안에 있는 실행파일들이 동작합니다

그렇기에 start()를 실행한 후 sleep를 이용해서 1초마다 자막을 출력하게 했습니다.

 

 

값은 이렇게 나옵니다. 아까와 별 차이가 없어보이지만 직접 실행시켜본다면 프레임과 자막이 각각 독립적으로 나오는걸 확인 할 수 있으실껍니다.

 

 

 

 

 

START(),RUN()

위에서 계속 말했다 싶이 실행파일들을 독립적으로 실행시키고 싶다면!

Thread안에 품고있는 Run메서드안에 

자기가 쓰레드를 사용하고싶은 원하는 코드를 집어넣고

 Run메서드를 호출해서 실행시키는 것이 아닌

Start()를 사용해서 쓰레드의 파일들을 실행 시켜줘야합니다!!

즉= 코드는 RUN의 넣고 실행은 START()로 해야한다

 

 

상속

저희는 여기서 한 개 이상한 점을 눈치채실 수 있으셔야 합니다.

상속이라는 것은 자바에서는 "단 한 개"의 클래스밖에 상속받지 못합니다.

그렇기에 저렇게 쓰레드를 상속받는다면 더 이상 다른 클래스들을 상속받지 못합니다.

그렇기에 저희는 이 방법을 교묘하게 우회해서 사용할 수 있습니다.

바로 인터페이스를 사용 할 수 있습니다.

 

 

Runnable을 사용하자.

 

 

 

사용방법은 implements를 사용해서 Runnable을 구현 하는 것 입니다.

이 Runnalbe클래스는 Run이라는 메서드를 가지고 있습니다. 

인터페이스이기 때문에 Runnalbe을 상속받는 순간 반드시 run메서드를 구현시켜줘야 합니다.

 

 

이런식으로 구현시키라고 오류가 뜹니다.

 

 

 

 

예제를 한번 보죠

 

package 자바의정석;

public class ex23 {

    static int[] intarray = {1,2,3,4,5};

    public static void main(String[] args){

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

        for(int i=0;i<intarray.length;i++){
            try {
                Thread.sleep(1000);
                System.out.printf("자막 %d\n",intarray[i]);
            }catch (Exception e){

            }

        }


    }


    }

    class ex24 implements Runnable{
        String[] str = {"하나","둘","셋","넷","다섯"};
        @Override
        public void run() {
            for(int i=0;i<str.length;i++){
                try {
                    Thread.sleep(800);
                    System.out.printf("비디오 프레임 : %s\n",str[i]);
                }catch (Exception e){

                }


            }
        }
    }

 

소스코드입니다.

 

 

 

밑에 부분은 아까와 같이 똑같은 코드로 동작하지만

Runnalbe을 구현한다면 아까와 다르게 객체를 생성 할 수 없습니다

인터페이스이기 때문입니다.

 

 

 

Runnalbe을 구현한다면 아까와 다르게 쓰레드를 만들어줘야 합니다.

당연히 Runnalbe은 인터페이스이기때문에 객체를 생성할 수 없습니다. 그렇기에

다형성을 이용해서 Runnalbe을 생성해줬습니다.

하지만 이 Runnalbe은 인터페이스지 쓰레드 클래스가 아니죠

그렇기에 이 Runnalbe만 생성한다해서 쓰레드는 실행되지 않습니다.

그렇기에 쓰레드를 생성한 후에 저희가 ex24안에 오버라이딩  run메서드가 있죠?

이 값을 쓰레드의 집어넣습니다.

그렇게된다면 쓰레드는 빈 껍데기인 Run을 가지고 있었지만

ruunalbe의 객체를 집어넣엇기 때문에

runnalbe안에 있는 run메서드를 쓰레드 메서드와 바꿔서 사용할 수 있습니다.

그렇게 되면 쓰레드의 run메서드안에 저희가 재정의한 run의 메서드가 들어가게 되겠죠

 

 

두번째 방법은 이렇게 사용하는 것 입니다.

 

첫번째와 두번째 방법 중 어느 것을 사용하셔도 상관없지만

보통은 두번째 방법을 많이들 사용합니다. 하지만 자기가 어떤 방법을 사용하던

편한 방법을 선택해서 사용하면 됩니다.

 

 

 

Main 쓰레드와 멀티쓰레드

메인 쓰레드라는 것은 어떤 것일까요?

 

 

 

그림으로 본다면 이런느낌입니다. 저희는 이때까지 메인메서드안에 메서드나 실행 값들을 넣어서 실행 시켰습니다.

즉 말해서 main이라는 메서드"도" 쓰레드를 가지고 있는 겁니다. 메인 메서드도 한개의 쓰레드를 가지고 있기 때문에

저희가 실행시킬 수 있었던 것 이죠. 저희가 클래스파일을 실행한다면 JVM에서 Main 쓰레드를 생성 시켜줍니다.

그 위에 저희가 실행시킨 메서드나 값들이 동작하고 있었던 것 이죠.

하지만 저희는 Thread로 인해서 main 쓰레드 "한개"와 Thread를 상속받은 클래스의 쓰레드 총 " 두 개"의

쓰레드를 사용했기 때문에 멀티 쓰레드를 사용했습니다.

 

 

 

 

쉽게 얘기해서 run메서드도 main메서드와 같다고 생각하시면 편합니다.

즉 저희는 기존에 있던 main메서드도 안에 정의되어있는  코드를 실행시키고 

run의 메서드도 저희가 정의한 코드를 실행 시킨다고 생각 하시면 쉽습니다.

그렇게 된다면 두 개의 메인메서드가 실행된다고 하면 각각 독립적으로 실행 되겠죠?

즉 run=main main 메서드 두개가 "동시"에 돌아갑니다.

이것이 바로 멀티 쓰레드입니다.

 

 

Main쓰레드가 끝난다면?

그러면 이렇게 생각 할 수 있겠죠 main메서드에 있는 코드가 끝나고

저희가 정의한 쓰레드에 있는 실행파일들이 끝나지 않았다면 어떻게될까요?

다들 예상하셨다싶이 메인메서드가 끝나도 쓰레드에 실행파일이 실행 중 이라면

프로그램은 종료되지 않습니다.

이유는 두 개의 실행파일들이 각 각 독립적으로 실행되기때문에 main메서드가 끝난다고

하더라도 쓰레드가 끝날 수 가 없습니다. 저기 위에처럼 main메서드가 "두 개"가 있다고

생각하시면 쉽습니다.

 

한번 예제로 볼까요

 

package 자바의정석;

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

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

        for(int i=0;i<4;i++){
            try {
                Thread.sleep(1000);
                System.out.printf("메인 메서드 포문 :"+i);
            }catch (Exception e){

            }

        }


    }
    
    }

    class ex24 implements Runnable{
   
        @Override
        public void run() {
            for(int i=0;i<10;i++){
                try {
                    Thread.sleep(1000);
                    System.out.println("쓰레드 포문 :"+i);
                }catch (Exception e){

                }


            }
        }
    }

 

 

소스코드이며 run의 메서드는 포문을 10번 메인 메서드는 포문을 4번 돌렸습니다.

그렇다면 값은 어떻게 나오게될까요?

 

 

 

 

결과는 이렇게 나옵니다. 실행하니 메인 메서드와 쓰레드 포문이 처음에는 동시에 나오다가

메인 메서드가 5번을 다 도니 메인 메서드는 그대로 죽었습니다.

하지만 쓰레드 포문은 계속 돌아갑니다. 이렇듯 쓰레드를 만든 순간 실행이 독립적으로 실행됩니다.

메인쓰레드가 끝나고 쓰레드도 끝날 수 있게 하는 것은 데몬쓰레드라고 하는데 그것은 다음 장에 배워보도록 하겠습니다.

 

 

 

 

 

결론

1.쓰레드는  모든 프로세스안에 반드시 한개가 들어가있다.

2.쓰레드를 생성하는 방법은 총 두 가지이며

Runnable과 Thread를 상속받는 방법이 있다

3.쓰레드는 run()메서드안에 실행코드를 정의해야 하며

실행시에는 start()로 실행시켜줘야 함.

4.우리가 사용하는 main메서드도 쓰레드를 가지고 있음.

JVM이 생성한 쓰레드안에서 우리는 메서드와 값들을 실행시킨 것

 

 

 

 

 

쓰레드의 메서드는 따로 정리해서 올리겠습니다.