본문 바로가기
JAVA

[JAVA] 자바 스레드 생성(Thread, Runnable)

by 정공자씨 2024. 4. 29.

 

 

 

메인 스레드(Main Thread)

- 자바 프로그램에는 최소 1개의 스레드가 있는데, 이것이 메인 스레드
- JVM이 자동으로 생성하는 스레드
  • 자바의 프로그램의 시작점은 main() 메서드이고
    • 이 main() 메서드를 수행되도록 하는 것이 스레드인데
    • 이 쓰레드가 메인 스레드
  • 모든 자바 프로그램은 JVM(자바 머신 러닝) 위에서 동작하는데,
    • 이  JVM은 프로그램의 시작점인 main() 메서드가 있는 클래스를 찾고 메인 스레드를 생성한 후에
    • 메인 스레드에서 main() 메서드를 호출하여, 작업이 수행되도록 함
  • 따라서, 메인 스레드는 JVM에 의해 자동으로 생성됨

 

 

JVM이  자동으로 생성하는 메인 스레드(Main Thread) 이외에 추가로 사용자 정의 스레드를 생성하여 구현해보자

 

 

 

 

 

 

쓰레드를 생성하는 2가지 방법

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

 

 

 

 

 

Thread 클래스를 상속 받아서 쓰레드 생성 및 실행

  • 쓰레드 클래스를 만들어 사용하기 위해서는 반드시 Thread 클래스를 상속 받아서 사용해야 함
  • java.lang.Thread 클래스의 run() 메서드를 오버라이딩해서 쓰레드의 실행 코드를 작성
  • 만든 클래스로 쓰레드 객체를 생성한 후에, start() 메서드를 이용하여 쓰레드 실행

 

 

Thread 클래스로 사용자 정의 쓰레드 클래스 만들고 실행하기

1) 쓰레드의 구현

public class ThreadExample extends Thread {

    @Override
    public void run() { 
       // 쓰레드가 수행할 작업내용을 작성
    }
}
  1. java.lang.Thread 클래스를 상속 받음
  2. Thread 클래스의 run() 메서드를 오버라이딩한 후, 구현부에 쓰레드가 수행할 작업 내용을 기재

 

2) 쓰레드의 실행

public class ThreadTest {
    public static void main(String[] args) {
    
        ThreadExample thread = new ThreadExample();
        thread.start();
    }
}
  1. ThreadExample 클래스의 인스턴스를 생성하고
    • ThreadExample는 Thread 클래스를 상속받은 쓰레드 객체
  2. start()메서드를 호출
    • start()를 사용해야 쓰레드가 실행됨
    • 즉, 쓰레드를 실행하기 위해서는 start() 메서드를 호출해야 함
    •  start() 메서드를 호출한다고 바로 메서드가 실행되는 것이 아니라
      • 쓰레드가 실행 대기 상태에 있다가, 자신의 차례가 되면 실행이 됨
      • 실행 대기 중인 쓰레드가 없는 경우, 쓰레드가 바로 실행함
    • 실행 순서는 운영체제(OS) 스케줄러의 스케줄에 따름

 

 

예제 

[ 예제 ] 스레드를 사용하지 않고 '청기 백기' 게임 구현하기
class Blue {	
	 public void run() { 
	     while(true) {
	         System.out.println("청기 올려!!!");
	     }
	 }
}

class White  {	
     public void run() { 
         while(true) {
             System.out.println("백기 올려!!!");
         }
     }
}

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

		White white = new White();
		Blue blue = new Blue();
		
		// CPU는 코드를 순차적으로 실행하므로, 첫번째 호출한 run 메서드만 무한반복함
		white.run();
		blue.run();
	}
}
  • 각각의 깃발을 드는 클래스의 객체를 생성한 후에
  • 메서드를 호출하면
  • "백기 올려"만 출력됨
  • 즉, CPU는 순차적으로 코드를 실행하므로, 첫번째 호출한 white.run()만 무한 반복


[ 실행 결과 ]

 

 

 

[ 예제 ] Thread 클래스를 이용해 청기 백기 클래스 구현하기

// Thread 클래스를 받속 받기
class Blue extends Thread {	
        @Override
        public void run() { // run() 메서드를 오버라이딩 해서 메시지를 무한 출력
                while(true) {
                    System.out.println("청기 올려!!!");
                }
        }
}

//Thread 클래스를 받속 받기
class White extends Thread {	
        @Override
        public void run() { // run() 메서드를 오버라이딩 해서 메시지를 무한 출력
                while(true) {
                    System.out.println("백기 올려!!!");
                }
        }
}
public class Main {
    public static void main(String[] args) {
    
        // 실행 클래스에서 각각의 스레드 객체 생성한 후, 스레드를 실행
        White white = new White();
        Blue blue = new Blue();

        // 스레드를 실행하기 위해서는 run() 메서드가 아닌
        // start() 메서드를 호출하여 실행해야함
        white.start();
        blue.start(); 
        
        // white.start();  // 종료된 쓰레드는 다시 실행 불가, 예외 발생
    }		
}
  • 실행 클래스에서 각각의 쓰레드 클래스(White, Blue) 인스턴스를 생성한 후, 스레드를 실행
  • 스레드를 실행하기 위해서는 
    • run() 메서드가 아닌, start() 메서드를 호출하여 실행해야함
  • 한번 실행이 종료된 쓰레드는 다시 실행할 수 없음
white.start();
white.start();  // 종료된 쓰레드는 다시 실행 불가, 예외 발생

 

[ 실행 결과 ]

 

 

 

 

 

 

Runnable 인터페이스를 구현하여 스레드 만들기

  • Runnable 인터페이스를 구현하는 클래스는
  • Runnable 인터페이스가 가지고 run() 메서드를 오버라이딩하여, 쓰레드 실행 시에 수행할 작업을 구현

 

 

Runnable 인터페이스는 언제 사용하나?

  • 자바는 다중 상속이 되지 않기 때문에
    • 이미 다른 클래스의 상속을 받고 있다면 Thread 클래스를 상속하여 사용할 수 없음
  • 이렇게, Thread 클래스를 바로 상속받는 것이 어려운 경우에 사용하면 유용함

 

 

Runnable 인터페이스를 구현하여 사용자 정의 스레드 클래스 생성 및 실행

1) 쓰레드 구현

public class ThreadInterface implements Runnable {

    @Override
    public void run() {
        // 스레드 작업 내용
    }	
}
  1. java.lang.Runnable 인터페이스를 구현하는 클래스 만들기
  2. run() 메서드를 오버라이딩하여, 스레드의 기능을 구현

 

2) 쓰레드 실행

public class Main {
    public static void main(String[] args) {
    
        ThreadInterface i = new ThreadInterface();
        Thread t = new Thread(i); 
        
        t.start();
    }
}
  1. Thread 클래스를 상속받고 있지 않기 때문에, start() 메서드를 사용할 수 없음
  2. 따라서, Runnable 인터페이스를 구현한 클래스의 인스턴스를 만들고
  3. Thread 클래스의 인스턴스를 생성할 때, 생성자의 매개변수로 (Runnable 인터페이스를 구현한 클래스의 인스턴스)를 넘겨주고
  4. 그 후에 쓰레드 Thread 클래스의 start() 메서드를 사용할 수 있음

 

 

[ 예제 ] Runnable 인터페이스를 이용하여 '청기백기 올리기 기능' 구현한 스레드

//Runnable 인터페이스 구현하는 클래스 만들기
class Blue implements Runnable {	
    @Override
    public void run() { // run() 메서드를 오버라이딩 해서 메시지를 무한 출력
        while(true) {
            System.out.println("청기 올려!!!");
        }
    }
}

//Runnable 인터페이스 구현하는 클래스 만들기
class White implements Runnable {	
    @Override
    public void run() { // run() 메서드를 오버라이딩 해서 메시지를 무한 출력
        while(true) {
            System.out.println("백기 올려!!!");
        }
    }
}

 

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

        // 인터페이스를 구현한 클래스 객체를 생성
        White white = new White();
        Blue blue = new Blue();

        // Thread 객체 생성 시, 반드시 생성자의 인자로 구현 클래스 객체를 전달
        Thread t = new Thread(white);
        Thread t2 = new Thread(blue);

        // 참조변수로 start() 메서드를 호출하여 메서드 실행
        t.start();
        t2.start();
    }		
}
  •  Runnable 인터페이스를 구현한 클래스는 완전한 쓰레드 클래스가 아님
    • Thread 클래스를 상속받지 않았기 때문에, 쓰레드의 기능을 사용할 수 없고
    • 따라서, start() 메서드도 없음
    • 즉, Runnable 인터페이스의 run() 메서드만 오버라이딩하여 구현한 상태
  • 쓰레드 기능을 사용하기 위해서는 반드시 Thread 클래스의 객체를 이용하여야 함
    • Thread 클래스의 객체 생성시에, 반드시 생성자의 인자로 구현 클래스 객체(white, blue)를 전달하여 쓰레드를 생성
    • Thread 클래스 타입의 참조 변수를 통해서 쓰레드 기능을 사용할 수 있음
  • 쓰레드를 실행하고 싶을 때는
    • 참조 변수로 start() 메서드를 호출하여, 쓰레드 기능을 사용
    • t.start();

[ 실행 결과 ]

 

 

 

 

 

 

start()와 run()의 차이

쓰레드 클래스에는 run() 메서드를 오버라이딩 하여 쓰레드가 실행할 내용을 정의하는데, 쓰레드를 실행할 때는  왜 run()메서드가 아닌 start() 메서드를 써서 실행을 할까? 

 

1. run() 메서드

  • run() 메서드를 직접 호출하면
    • 현재의 쓰레드인 메인 쓰레드(Main Thread) 내에서 호출이 됨
    • 즉, 메인 쓰레드(Main Thread)의 호출 스택 1개만 이용하는 것
  • 메인 쓰레드(Main Thread) 1개만을 이용한다는 것은
    • 별도의 쓰레드를 만들어서 해당 메서드를 실행하는 것이 아니므로
    • 쓰레드의 활용이 아님
  • 즉, run() 메서드를 호출한다는 것은, 메인 쓰레드에서 Thread 객체의 메소드를 호출하는 것에 불과함뿐
[ 호출 스택 ]
- 이 영역은 실질적인 명령어를 담고 있는 메모리로, 명령어를 하나씩 꺼내서 실행시키는 역할

- 만약, 동시에 2가지 작업을 한다면, 2개 이상의 호출 스택이 필요하게 됨
- 쓰레드를 사용한다는 것은, JVM이 여러 개의 호출 스택을 번갈아가면서 일처리를 하여
- 사용자에게 동시에 작업하는 것처럼 보여주는 것

 

 

2. start() 메서드

메인 쓰레드(Main Thread)와 별도의 쓰레드로 실행을 시키려면 JVM의 도움이 필요하기 때문에 Start() 메서드를 호출함

  1.   자바 프로그램을 실행하면, main 쓰레드가 실행되고, main() 메서드가 호출 됨
  2.   main() 메서드 내에서 start() 메서드를 호출
  3.   start() 메서드를 호출하면
    • JVM이 새로운 쓰레드를 생성하고
    • 새로운 쓰레드가 작업을 실행하는데 사용하는 호출 스택(call stack)이 생성되고
  4. 새로 생성된 호출 스택에서 run() 메서드가 호출이 됨
    • 이로 인하여, 생성된 호출 스택에 run() 메서드가 첫번째로 올라가게 함
    • 쓰레드는 독립된 공간에서 작업을 수행함
  5. 이제는 호출 스택이 2개이므로, 스케줄러가 정한 순서에 따라서 번갈아가면서 실행
쓰레드 객체를 start() 할때마다, 새로운 호출 스택(Call Stack)이 생성되고
작업이 완료된 이후에는 호출 스택이 소멸됨

 

 

 

익명 스레드 객체와 익명 스레드 자식 객체로 스레드 사용하기

 

[ 언제 사용하는가?  ]
- 화면에서 단발성으로 스레드를 사용하는 경우

- 채팅 중에 사용되는 파일 전송 같은 기능 어쩌다 한번씩 사용하는 기능
- 즉, 재사용되지 않는 스레드 작업을 수행하고 싶을 때, 익명 스레드 객체와 익명 스레드 자식 객체를 사용하면 편리함

 

 

1. Thread 클래스를 상속받는 익명 클래스로 '파일 업로드 기능' 구현

  • 익명 스레드 자식 객체(상속 받는 익명 클래스)
class Blue implements Runnable {	
	
    @Override
    public void run() { 
            while(true) {
                System.out.println("청기 올려!!!");
            }
    }
}

class White implements Runnable  {

    @Override
    public void run() { 
            while(true) {
                System.out.println("백기 올려!!!");
            }
    }
}

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

        White white = new White();
        Blue blue = new Blue();

        Thread t1 = new Thread(white);
        Thread t2 = new Thread(blue);

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

        // Thread 클래스를 상속받는 익명 클래스를 이용하여
        // run() 메서드를 오버라이딩하여 스레드 구현
        Thread thread = new Thread() {
            @Override
            public void run() {
                for (int i = 0; i < 1000000; i++) {
                    System.out.println("파일 업로드 중!");
                }
            }
        };
        thread.start();
		
	}
}

 

[ 실행 결과 ] 

 

 

2. Runnable을 구현한 익명 스레드 객체를 이용하여 '파일 업로드 기능' 구현

안드로이드 화면 기능에서 많이 사용
public class Main {
    public static void main(String[] args) {

        White white = new White();
        Blue blue = new Blue();

        Thread t1 = new Thread(white);
        Thread t2 = new Thread(blue);
        t1.start();
        t2.start();

        // Runnable 인터페이스를 구현한 익명 스레드 객체를 이용하여
        // 스레드로 사용
        new Thread( new Runnable() {			
            @Override
            public void run() {
                for (int i = 0; i < 1000000; i++) {
                    System.out.println("파일 업로드 중입니다");
                }
            }
        }).start();

    }
}

 

 

 

 

 

 

 

 

출처

 

https://velog.io/@yukicow/%EC%8A%A4%EB%A0%88%EB%93%9C%EC%99%80-%ED%94%84%EB%A1%9C%EC%84%B8%EC%8A%A4

 

 

https://cano721.tistory.com/161

 

https://velog.io/@wijoonwu/Thread