본문 바로가기
멀티캠퍼스 풀스택 과정/Java의 정석

자바의 정석10-1 프로세스와 쓰레드

by 이쟝 2022. 1. 12.

프로세스와 쓰레드(process & thread)

- 프로세스: 실행 중인 프로그램, 자원(resources데이터와 메모리)과 쓰레드로 구성

- 쓰레드: 프로세스 내에서 실제 작업을 수행. 모든 프로세스는 최소한 하나의 쓰레드를 가지고 있다. (쓰레드가 없으면 작업을 수행할 수 없음)

 

프로세스: 쓰레드 = 공장 : 일꾼

 

- 멀티쓰레드: 여러 작업을 동시에 나눠서 수행 가능(작업을 보다 효율적으로 처리 가능)

-> “하나의 새로운 프로세스를 생성하는 것보다 하나의 새로운 쓰레드를 생성하는 것이 더 적은 비용이 든다.”

-> 대부분의 OS는 멀티태스킹(다중작업)을 지원하기 때문에 여러 개의 프로세스가 동시에 실행 가능

-> 메신저로 파일을 다운로드 받거나 음성 대화를 나눌 수 있는 것이 바로 멀티쓰레드로 프로그램이 작성되어 있기 때문

 

멀티쓰레드의 장단점 

 

장점 단점(공유할 때)
시스템 자원을 보다 효율적으로 사용할 수 있다.
(CPU
의 사용률 향상)
동기화(synchronization)에 주의해야 한다.
사용자에 대한 응답성(responseness)이 향상된다. 교착상태(dead-lock)가 발생하지 않도록 주의해야 한다.
작업이 분리되어 코드가 간결해진다. 각 쓰레드가 효율적으로 고르게 실행될 수 있게 해야 한다.
여러 모로 좋다 프로그래밍할 때 고려해야 할 사항들이 많다.”

 

-> 교착상태: 두 쓰레드가 자원을 점유한 상태에서 서로 상대편이 점유한 자원을 사용하려고 기다리느라 진행이 멈춰 있는 상태


쓰레드의 구현과 실행

public interface Runnable {

    public abstract void run ( );

}

 

-> Runnable 인터페이스는 run만 정의되어 있는 간단한 인터페이스

 

1) Thread클래스를 상속

 

MyThread가 Thread를 상속받음

 

- Thread클래스를 상속받은 경우의 인스턴스 생성 방법

 

MyThread t1 = new MyThread();   // 쓰레드의 생성
t1.start();                     // 쓰레드의 실행

 

2) Runnable인터페이스를 구현

 

MyThread2가  Runnable 인터페이스를 구현

 

-> 자바는 단일 상속이기 때문에 Thread클래스를 상속받으면 다른 클래스를 상속받을 수 없기 때문에, Runnable인터페이스를 구현하는 방법이 더 낫다.!

-> run( )안에다가 작업내용을 적으면 된다.

 

- Runnable인터페이스를 구현한 경우의 인스턴스 생성 방법

 

Runnable r = new MyThread2(); // Runnable을 구현한 클래스의 인스턴스를 생성
Thread t2 = new Thread(r);    // 생성자 Thread(Runnable r)

// Thread t2 = new Thread(new MyThread2()); 위의 두 줄을 한 줄로 줄임
t2.start();

예제) 

 

public class Ex13_1 {
	public static void main(String[] args) {
		ThreadEx1_1 t1 = new ThreadEx1_1();  // Thread를 상속받은 ThreadEx_1 객체 생성
		
		Runnable r = new ThreadEx1_2();
		Thread t2 = new Thread(r);     // 생성자 Thread(Runnable target)
     // Thread t2 = new Thread(new ThreadEx1_2()); 위의 두 줄을 한 줄로
        
		t1.start();
		t2.start();
	}
}

class ThreadEx1_1 extends Thread { // 1. Thread클래스를 상속해서 쓰레드를 구현
	public void run() {  // run(): 쓰레드가 수행할 작업을 작성
		for(int i=0;i<5;i++) {
			System.out.println(this.getName()+ "hello");  // 조상인 Thread의 getName()을 호출
		}
	}
}

class ThreadEx1_2 implements Runnable { // 2. Runnable 인터페이스를 구현해서 쓰레드를 구현
	public void run() {   // run(): 쓰레드가 수행할 작업을 작성
		for(int i=0;i<5;i++) {
			//Thread.currentThread() - 현재 실행중인 Thread를 반환한다.
			System.out.println(Thread.currentThread().getName()); 
			// Thread를 상속받지 않아서 Thread.currentThread()로 접근.Thread.currentThread()가 this.
		}
	}
}

 

더보기

<출력문>

Thread-1

Thread-0hello

Thread-0hello

Thread-0hello

Thread-0hello

Thread-0hello

Thread-1

Thread-1

Thread-1

Thread-1

 

-> 쓰레드의 이름을 지정하지 않으면 ‘Thread-번호의 형식으로 이름이 정해진다.

-> 멀티쓰레드는 다중 작업이라서 결과가 섞여서 나온다.

 

- Thread클래스를 상속받으면 자손 클래스에서 조상인 Thread클래스의 메서드를 직접 호출해서 this.로 접근할 수 있다.

- Runnable 인터페이스를 구현하면 Thread클래스의 static메서드인 currentThead( )를 호출해서 쓰레드에대한 참조를 얻어와야 호출이 가능하다.

->Thread를 상속받은 ThreadEx_1 this.getName( )으로 호출하면 되지만, Runnable을 구현ThreadEx_2Thread 클래스의 getName( )을 호출하려면, Thread.currentThread( ).getName( )으로 호출해야 한다.

 

-> System.out.println(Thread.currentThread().getName()); 아래의 코드를 한 줄로 줄여 쓴 것

Thread t = Thread.currentThread( );

String name = t.getName( );

System.out.println(name);

 

-> 싱글쓰레드로 바꾸려면 main 메서드 안에 ThreadEx_1과 ThreadEx_2의 for문을 넣으면 된다. (첫 번째 for문이 끝나고 나서 두 번째 for문 실행!)

 


쓰레드의 실행 - Start()

- 쓰레드를 생성한 후에 start( )를 호출해야 쓰레드가 작업을 시작한다.

 

ThreadEx1_1 t1 = new ThreadEx1_1();  // 쓰레드 t1을 생성한다. 
ThreadEx1_1 t2 = new ThreadEx1_1();  // 쓰레드 t2를 생성한다.
		
t1.start();  // 쓰레드 t1을 실행시킨다. 
t2.start();  // 쓰레드 t2를 실행시킨다.

 

-> 어느 것이 먼저 실행될지는 모름

-> 쓰레드를 start( )하면 실행 가능한 상태가 되는 것이고, 실행 대기 상태에 있다가 자신의 차례가 되어야 실행된다.

(쓰레드의 실행 순서는 OS 스케줄러가 작성한 스케줄에 의해 작성된다.)

-> 쓰레드는 OS에 의존적이다.

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

 

  ThreadEx1_1 t1 = new ThreadEx1_1( );
  t1.start( );
  t1.start( );   // 예외 발생
  ThreadEx1_1 t1 = new ThreadEx1_1( );
  t1.start( ); 
  t1 = new ThreadEx1_1( ); // 다시생성
  t1.start( );  // OK

 


start( ) run( )

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

- start( )는 새로운 쓰레드가 작업을 실행하는데 필요한 호출스택(call stack, U자 박스)을 생성한 다음에 run( )을 호출해서, 생성된 호출스택에 run( )이 첫 번째로 올라가게 한다. 

 

-> 모든 쓰레드는 독립적인 작업을 수행하기 위해서 새로운 쓰레드를 생성하고 실행시킬 때마다 새로운 호출스택이 생성되고 쓰레드가 종료되면 작업에서 사용된 호출스택은 소멸된다.(일회용)

 

main쓰레드에서 새로운 쓰레드를 생성하고 start( )를 호출한 후 호출스택의 변화

 

1. main메서드에서 쓰레드의 start( )를 호출한다.

2. start( )는 새로운 쓰레드를 생성하고, 쓰레드가 작업하는데 사용될 호출스택을 생성한다.

3. 새로 생성된 호출스택에 run( )이 호출되어, 쓰레드가 독립된 공간에서 작업을 수행한다.

4. 이제는 호출스택이 2개이므로 스케줄러가 정한 순서에 의해서 번갈아 가면서 실행된다.

 

- 원래 main 메서드가 먼저 수행을 마치면 프로그램이 종료되었지만 main메서드가 수행작업이 끝나도 다른 쓰레드가 작업을 마치지 않은 상태라면 프로그램이 종료되지 않음!!!!!

-> 실행 중인 사용자 쓰레드가 하나도 없을 때 프로그램은 종료된다.


main쓰레드

- main메서드의 작업을 수행하는 것도 쓰레드이고, 이를 main쓰레드라고 한다. 

- 쓰레드는 사용자 스레드’와 데몬 쓰레드’ 두 종류가 있다.

 

사용자 쓰레드(user thread): main 쓰레드(main 메서드의 작업을 수행함) 데몬 쓰레드(daemon thread): 보조쓰레드

 

예제1) 새로 생성한 쓰레드에서 고의로 예외를 발생시키고 printStackTrace( )를 이용해서 예외가 발생한 당시의 호출스택을 출력하는 예제

 

public class ThreadEx2 {
	public static void main(String[] args) {
		ThreadEx2_1 t1 = new ThreadEx2_1();  //Thread를 상속받은 ThreadEx2_1 객체 생성
		t1.start();                          // start() 호출(call stack생성)
	}
}
class ThreadEx2_1 extends Thread { 
	public void run() {
		throwException();
	}
	public void throwException() {
		try {
			throw new Exception();  // 고의로 예외를 발생시킴
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

 

더보기

-> main에서 start( )호출하면서 main쓰레드 종료 -> start쓰레드 호출스택에서 run( )호출 -> run( )throwException( )호출

<출력>

java.lang.Exception

       at ThreadEx2_1.throwException(ThreadEx2.java:17)  

       at ThreadEx2_1.run(ThreadEx2.java:12)                

 

예제2) 예제1에서 main 메서드에 t1.start( )대신 t1.run( )을 출력

 

public class ThreadEx3 {
	public static void main(String[] args) {
		ThreadEx3_1 t1 = new ThreadEx3_1();
		t1.run();  // 따로 호출스택에서 작업하지 않고, main 쓰레드에서 작업 실행
	}
}
class ThreadEx3_1 extends Thread {
	public void run() {
		throwException();
	}
	public void throwException() {
		try {
			throw new Exception();  // 고의로 예외 발생
		} catch(Exception e) {
			e.printStackTrace();
		}
	}
}

 

더보기

-> main쓰레드의 호출스택에서 run( )호출 -> run( )throwException( )호출

 

<출력>

java.lang.Exception

       at ThreadEx3_1.throwException(ThreadEx3.java:14)

       at ThreadEx3_1.run(ThreadEx3.java:10)

       at ThreadEx3.main(ThreadEx3.java:5)