본문 바로가기
교육, 학습/멀티캠퍼스_풀 스택

JAVA 문법 - 자바 멀티스레드 구현(Thread, Runnable)

by 개발하는 경제학도 2022. 1. 17.

강의 소개

현재 수강하고 있는 멀티캠퍼스 k-digital 지능형 웹서비스 풀 스택 과정을 수강하며 적은 내용입니다.

교재로는 자바의 정석을 사용하고 있습니다.


위에는 간단한 개념, 아래에는 Thread 클래스와 Runnable 인터페이스를 사용한 구현이 설명되어 있습니다.

 

프로세스

프로그램

SSD, HHD 내부의 실행 가능한 파일이다. 

프로세스

실행 중인 프로그램이다. 프로세스가 되면 운영체제에 의해 메모리 공간을 할당받는다.

이런 프로세스는 프로그램에 사용되는 데이터, 자원(메모리 등), 스레드로 구성된다.

 

스레드

프로세스 내에서 실제 작업을 수행하는 주체이다.

모든 프로세스는 하나 이상의 스레드를 가지고 있다.

 

프로세스와 스레드

'프로세스 : 스레드 = 공장 : 일꾼'으로 보통 비유된다.

 

- 종류

1) 싱글 스레드 프로세스

'자원 + 스레드'와 같이 스레드가 1개뿐이다. 한 번에 하나의 작업만 처리한다.

ex. 음악 시작 -(종료) -> 뉴스 게시판 시작 -(종료) -> 다운로드 시작 -(종료)

 

2) 멀티 스레드 프로세스

'자원 + 스레드 + 스레드..'와 같이 스레드가 여러 개인 상태이다.

ex. 음악 시작 -(일시 중단)-> 뉴스 게시판 시작 -(일시 중단)-> 다운로드 시작 -(일시 중단)-> 음악 재시작 -> 뉴스 재시작..

1개의 프로세스가 여러 개 스레드를 동시 처리하는 것처럼 보인다. 하지만 사실은 1개 CPU가 여러 개 스레드 작업을 번갈아가며 실행하는 것이다.

즉, 멀티 스레드가 굉장히 빠르게 작업을 번갈아가면서 처리해서 동시로 처리되는 것처럼 보인다. 대부분은 멀티스레드로 동작한다.

 

멀티 프로세스 vs 멀티 스레드

1) 멀티 프로세스

cpu가 여러 개다. ex. 공장이 여러 개

변수, 메모리 영역을 각자 실행시킨다.

 

2) 멀티스레드

더 효율적인 것은 멀티 스레드이다. 자원을 공유하기 때문이다. 메모리를 적게 차지한다.

 

- 멀티스레드 단점

동기화에 주의해야 한다.

교착상태가 발생하지 않도록 주의해야 한다.

각 스레드가 효율적으로 고르게 실행될 수 있게 해야 한다.

 


 

자바에서 멀티스레드 구현 방법

자바에서 스레드를 구현하려면 Thread 클래스를 상속받거나, Runnable 인터페이스를 구현하면 된다.

두 가지 방법은 모두 run메서드를 오버 라이딩하여 작업하고 싶은 스레드의 내용을 적으면 된다.

그럼 왜 Thread클래스와 Runnable인터페이스 두 방법이 있는지 아래에서 확인해본다. (아래서 다루겠지만 보통은 Runnable을 사용한다.)

 

 

1. java.lang.Thread 클래스

 

- 구현 순서

1) Thread 클래스를 상속받은 후 run메서드를 재정의하여 스레드가 실행할 내용을 정의한다. 

class A extends Thread {  // 쓰레드를 상속받는다.
	public void run() 
	// 반드시 run 메서드를 오버 라이딩해야 한다. 
}

 

2) 스레드 인스턴스를 생성한다.

A a1 = new A();  // 쓰레드의 인스턴스를 생성한다.

자동 형 변환을 사용하여 Thread a1 = new A(); 사용 가능하다. 즉, 스레드 타입으로 사용 가능하다.

 

3) 스레드의 실행 메서드인 start를 호출한다.

a1.start();

  Thread의 start메서드이다. start메서드를 실행하면 run메서드를 호출한다.

=> 이 3단계를 사용하면 스레드를 구현할 수 있다.

 

 

- 예시 코드

아래는 사용자가 스레드 1, 2를 만들고 메인 스레드까지 총 3개의 멀티스레드로 동작하는 코드이다.

멀티스레드는 정해진 순서 없이 섞여 출력된다. 따라서 스레드 1의! 와 스레드 2의 #이 번갈아서 출력된다.

스레드는 각자 자동으로 이름이 붙어 있다. 따라서 Thread클래스의 getName()을 호출하면 어떤 스레드에서 동작하고 있는지 터미널 창에서 확인이 가능하다.  만약 사용자가 스레드의 이름을 붙여주고 싶다면 setName()을 사용하면 된다.

 

[스레드 1]

class MyThread1 extends Thread{

	@Override
	public void run() {
		System.out.println(getName());  // 스레드 클래스 내 getName메서드. 자동으로 쓰레드의 번호를 붙여준다. // Thread-0
		for (int i = 0; i < 10; i++) {
			try {
				Thread.sleep(1000); // 1초씩 sleep
			}
			catch(InterruptedException error) {
				error.printStackTrace();  // 에러 발생의 원인을 출력
			}
			System.out.print("!");
		}
	}
}

 

[스레드 2]

class MyThread2 extends Thread{
	
	@Override
	public void run() {
		System.out.println(getName());  // Thread-1  
		for (int i = 0; i < 10; i++) {
			try {
				Thread.sleep(2000); // 2초씩 sleep
			}
			catch(InterruptedException error) {
				error.printStackTrace();  // 에러 발생의 원인을 출력
			}
			System.out.print("#");
		}
	}
}

 

[메인 스레드]

public class ThreadTest {

	public static void main(String[] args) {
		MyThread1 t1 = new MyThread1();
		MyThread2 t2 = new MyThread2();
		t1.start(); // 스레드의 run 메서드 호출.
		t2.start();
		System.out.print("main 종료"); // t1, t2, main스레드 즉, 3개의 멀티 스레드로 실행된다. 
	}
}

 

메인 스레드에서 실행을 시키면 아래와 같은 순서로 출력된다. 왜 가장 마지막 순서로 보이는 main 종료가 먼저 실행되는지 아래에서 그림과 함께 확인해본다.

main 종료!#!!#!!#!!

 

 

- 메인 메서드에서 run 할 때 실행 순서

 

메인 메서드가 포함된 스레드를 메인 스레드라 한다. 어떤 자바 애플리케이션이건 메인 스레드는 항상 존재한다. 반면 위의 코드로 사용자가 만든 스레드는 오른쪽의 스레드 객체로 표시했다.

 

모든 자바 애플리케이션은 main 메서드를 실행하면서 시작된다. 따라서 run을 하면 실행 순서는 아래와 같다.

(사진과 동일한 순서)

자바 애플리케이션 실행 -> main메서드 실행 -> [1] 스레드 객체 생성 -> [2] 메인 스레드 내 start메서드 호출 -> 스레드 객체의 run 메서드 실행 -> [3] 메인 스레드 실행

 

한 가지 알아두면 좋을 점은, 싱글 스레드 환경에서는 메인 스레드가 종료하면 프로세스도 종료된다.

반면 멀티 스레드 환경에서는 메인 스레드가 작업 스레드보다 먼저 종료되더라도 작업 스레드가 종료될 때까지 프로세스는 종료되지 않는다.

 


 

2. Runnable 인터페이스 

인터페이스 특징 중 하나는 다중 상속이다. 따라서 다른 클래스를 상속받은 뒤 스레드도 구현할 수 있어 보통 Runnable을 자주 사용한다. 
Runnable인터페이스는 내부에 run메서드만 가지고 있다.

 

- 구현 순서

1) Runnable 인터페이스 구현 객체를 만든 뒤, run메서드를 재정의하여 스레드가 실행할 내용을 정의한다.

class A implements Runnable {
	public void run() // 반드시 run 메서드를 오버라이딩해야한다. 다른 쓰레드 동시 처리 내용으로
}

Runnable 인터페이스의 run메서드 자체에는 구현된 내용이 없다. 인터페이스이니 당연하다. 따라서 Thread클래스와 마찬가지로 run메서드를 반드시 오버 라이딩해주어야 한다.

 

2) Runnalbe 타입의 객체를 생성한다.

 A a1 = new A();

  Thread와 마찬가지로 Runnalbe a1 = new A(); 로 Runnable 타입으로 만들 수도 있다.

 

3) Thread 객체로 변환한다. 

Thread ta = new Thread(a1);

Runnable은 작업 내용을 가지고 있는 객체이지, 실제 스레드는 아니다. 따라서 Runnable을 구현한 객체를 생성한 뒤, 이를 매개 값으로 Thread 생성자를 호출해야 작업 스레드가 생성되는 것이다.

Thread클래스만 사용했을 때에 비교해서 Runnable 인터페이스를 사용했을 때, 이 부분만 추가된 점이 다르다.

 

4) 스레드에 있는 start 메서드 호출

ta.start();

 마찬가지로 Thread의 start메서드이다. start메서드를 실행하면 run메서드를 호출한다.

 

- 예시 코드

[스레드 1]

// 다중상속을 해야할 때는 인터페이스 사용위해 Runnable 이용한다.
class MyThread1 implements Runnable{
	
	@Override
	public void run() {
		System.out.println(Thread.currentThread().getName()); // 현재 Run을 수행하고 있는 스레드의 이름 확인. 
		for (int i = 0; i < 100; i++) {
			System.out.print("!");
		}
	}
}

 

[스레드 2]

class MyThread2 implements Runnable {
	@Override
	public void run() {
		System.out.println(Thread.currentThread().getName());

		for (int i = 0; i < 100; i++) {
			System.out.print("#");
		}
	}
}

 

 

[메인 스레드]

public class ThreadTest {

	public static void main(String[] args) {
		System.out.println(Thread.currentThread().getName());  // main도 하나의 쓰레드이다. 
		
		// Runnable 타입 객체 생성 
		MyThread1 t1 = new MyThread1();
		MyThread2 t2 = new MyThread2();
		
		// Thread 타입 객체 변환 (이 부분만 다르다) 
		Thread tt1 = new Thread(t1);
		Thread tt2 = new Thread(t2);
		
		
		tt1.start();
		tt2.start(); // 섞이면서 출력된다. 순서없이 출력된다. (멀티 쓰레드) 
		System.out.print("main 종료"); // t1, t2, main스레드까지 3개의 멀티 스레드로 실행된다. 
	}
}

 

이처럼 Tread 클래스, Runnable 인터페이스 두 가지 방식을 통해 자바에서 멀티 스레드를 구현할 수 있다. 보통은 다중 상속을 가능하게 하는 Runnable 인터페이스를 사용하는 점은 참고한다.

 

 

출처: 자바의 정석(남궁 성 저), 멀티캠퍼스

참고자료: 이것이 자바다(신용권 저)

댓글