관리 메뉴

나구리의 개발공부기록

자바의 정석 기초편 ch13 - 1 ~ 13 [쓰레드,쓰레드의 구현과 실행, 싱글 쓰레드와 멀티쓰레드, 쓰레드의 I/O블로킹] 본문

유튜브 공부/JAVA의 정석 기초편(유튜브)

자바의 정석 기초편 ch13 - 1 ~ 13 [쓰레드,쓰레드의 구현과 실행, 싱글 쓰레드와 멀티쓰레드, 쓰레드의 I/O블로킹]

소소한나구리 2023. 12. 15. 15:25

1) 쓰레드

(1) 프로세스(process - 공장, 작업환경)

  • 실행중인 프로그램
  • 자원(resource - 메모리, CPU 등등)과 쓰레드로 구성되어 있음

(2) 쓰레드(thread - 일꾼)

  • 프로세스 내의 실제 작업을 수행하며 모든 프로세스는 최소한 하나의 쓰레드를 가지고 있음
  • 싱글 쓰레드 프로세스 = 자원 + 쓰레드
  • 멀티 쓰레드 프로세스(쓰레드가 여러개인 것) = 자원 + 쓰레드 + 쓰레드 + ... + 쓰레드

(3) 싱글쓰레드인 2프로세스 vs 1프로세스 + 2 쓰레드

  • 하나의 새로운 프로세스를 생성하는 것보다 하나의 새로운 쓰레드를 생성하는 것이 비용이 적게 듦
  • 자바가 성장한 계기로 과거 웹서버에서 웹프로그램을 작성할 때 CGI(싱글쓰레드) 와 Java Servlet(멀티쓰레드)를 사용 했으나 1999년말 웹사용량이 폭발적으로 증가함에 따라 멀티쓰레드(효율적)를 지원하는 Java Servlet으로 넘어가게 됨

(4) 멀티쓰레드의 장단점

  • 대부분의 프로그램은 멀티쓰레드로 작성하지만 멀티쓰레드 프로그래밍이 장점만 있는 것은 아님
장점 1. 시스템 자원을 보다 효율적으로 사용할 수 있음
2. 사용자에 대한 응답성(responsiveness)이 향상
3. 작업이 분리되어 코드가 간결해짐

>> 여러방면으로 장점이 있음
단점 1. 동기화(synchronization)에 주의
2. 교착상태(deadlock)가 발생하지 않도록 주의
3. 각 쓰레드가 효율적으로 고르게 실행될 수 있게 해야 함

>> 프로그래밍할 때 고려해야 할 사항들이 많아 짐

2) 쓰레드의 구현

(1) Thread클래스를 상속

  • Thread 클래스를 상속받은 클래스의 인스턴스를 직접 생성
class MyThread extends Thread {
	public void run() { /* 작업내용 */	//Thread클래스의 run()을 오버라이딩
	}
}

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

 

(2) Runnable인터페이스를 구현 - 더 나음

  • Thread객체를 생성할 때 생성자의 인수로 Runnable인터페이스를 구현한 클래스의 객체를 직접 생성하거나 해당 객체를 참조하는 참조변수를 입력
class MyThread2 implements Runnable {	// 다른 클래스를 추가로 상속받을 수 있어서 유연함
	public void run() { /* 작업내용 */	//Runnable인터페이스의 추상메서드 run()을 구현
	}
}

// Runnable인터페이스의 구성
public interface Runnable {
	public abstract void run();
}

// 쓰레드의 실행
Runnable r = new MyThread2();
Thread t2 = new Thread(r);
// 두 줄을 한줄로 Thread t2 = new Thread(new MyThread2());
t2.start();

 

(3) 예제1

  • Thread를 상속받은 클래스와 Runnable을 구현한 클래스로 각각의 쓰레드를 구현하여 멀티쓰레드 프로그램으로 작성
class Ex13_1 {
	public static void main(String args[]) {
		ThreadEx1_1 t1 = new ThreadEx1_1();

		Runnable r = new ThreadEx1_2();
		Thread t2 = new Thread(r);	  // 생성자 Thread(Runnable target)

		t1.start();	// 쓰레드 실행 순서는 OS스케줄러대로
		t2.start();	// 쓰레드 실행
	}
}

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

class ThreadEx1_2 implements Runnable {	// Runnable 인터페이스를 구현해서 쓰레드를 구현
	public void run() {	// 쓰레드가 수행할 작업 작성
		for(int i=0; i < 500; i++) {
			// Thread.currentThread() - 현재 실행중인 Thread를 반환
			System.out.println(Thread.currentThread().getName());
		}
	}
}

 

3) 쓰레드의 실행 - start()

  • 쓰레드를 생성한 후에 start()를 호출해야 쓰레드가 작업을 시작
  • OS스케줄러가 실행순서를 결정하기 때문에 먼저 작성 되어있다고 해서 절대적으로 먼저 실행 되는 것이 아님.

(1) start()와 run() 동작 순서

  1. main메서드 -> start() 실행 
  2. start메서드가 새로운 호출 스택을 생성
  3. start()메서드 종료 -> run()실행, main메서드의 스택이 아닌 start메서드가 생성한 호출스택에서 동작함
  4. 각각의 쓰레드가 위의 순서로 작동되어 서로 독립적인 작업을 수행할 수 있음


4) main쓰레드

  • main메서드의 코드를 수행하는 쓰레드
  • 쓰레드는 '사용자쓰레드(main, run 등..)'와 '데몬 쓰레드(보조 쓰레드)' 두 종류가 있음
  • 실행 중인 사용자 쓰레드가 하나도 없을 때 프로그램이 종료 됨
  • 즉, main 메서드에서 추가적인 쓰레드가 실행되면 main메서드가 종료 되어도 계속 프로그램이 실행되며 모든 사용자 쓰레드가 종료 되어야 프로그램이 종료됨

(1) 싱글쓰레드

// 싱글쓰레드로 작성
class Ex13_2 {
	public static void main(String args[]) {
		long startTime = System.currentTimeMillis();

		// 해당 for문 먼저 작업
		for(int i=0; i < 300; i++)
			System.out.printf("%s", new String("-"));		

		System.out.print("소요시간1:" +(System.currentTimeMillis()- startTime)); 

		// 위 for문이 끝나고 해당 for문 작업
		for(int i=0; i < 300; i++) 
			System.out.printf("%s", new String("|"));		

 		System.out.print("소요시간2:"+(System.currentTimeMillis() - startTime));
	}
}

 

(2) 멀티쓰레드

  • Thread를 상속 받은 클래스 TimerThread를 구현
  • main메서드에서 TimerThread를 생성하여 쓰레드를 시작하고, main메서드에서도 별도의 코드를 수행
  • main메서드의 호출스택과 th1.start()로 생성한 호출스택이 모두 종료되어야 프로그램이 종료됨
// 멀티쓰레드로 작성
class Ex13_3 {
	static long startTime = 0;

	public static void main(String args[]) {
		TimerThread th1 = new TimerThread();
		th1.start();
		startTime = System.currentTimeMillis();

		for(int i=0; i < 300; i++)
			System.out.printf("%s", new String("-"));	

		System.out.print("소요시간1:" + (System.currentTimeMillis() - startTime));
	} 
}

class TimerThread extends Thread {
	public void run() {
		for(int i=0; i < 300; i++)
			System.out.printf("%s", new String("|"));	

		System.out.print("소요시간2:" + (System.currentTimeMillis() - startTime));
	}
}

// run()과 start()의 메서드의 실행순서가 OS의 스케줄러에 따라서 실행순서가 결정됨

 

싱글쓰레드와 멀티쓰레드의 실행 그래프 예시

(3) 동작 시간

  • 싱글쓰레드보다 멀티쓰레드가 시간이 조금더 걸림
  • context switching(작업이 넘어감)발생하여 전환되는 시간이 소요되기 때문
  • 그러나, 동시에 작업이 가능하다는 장점이 있기에 멀티쓰레드로 많이 프로그램이 작성 됨

5) 쓰레드의 I/O블로킹 (blocking) - Input,Output 시 작업 중단

  • 멀티쓰레드로 프로그래밍을 하면 쓰레드가 작업을 대기하여도 다른 쓰레드는 작업을 이어나갈 수 있음.
  • 외부장치의 입출력, 사용자입력 대기, CPU(빠른장치)와 다른 기기(느린장치)와의 작업 등등의 상황에서는 싱글쓰레드보다 멀티 쓰레드로 프로그래밍을 하는 것이 더 효율적일 수 있음

(1) 예제

  • 싱글쓰레드로 프로그래밍하면 입력대기가 발생했을 때 입력값이 주어지지않으면 계속 작업이 멈춰있음
  • 멀티쓰레드로 프로그래밍이 되어있으면 한쪽 쓰레드에서 입력대기가 발생하여도 다른쪽의 쓰레드는 계속 작업이 수행 됨
import javax.swing.JOptionPane;

// 싱글쓰레드로 프로그램 작성
class Ex13_4 {
	public static void main(String[] args) throws Exception {
		String input = JOptionPane.showInputDialog("아무 값이나 입력하세요."); 
		// 입력값을 받기 전까지 다음 작업을 진행 하지 못함(blocking)
		System.out.println("입력하신 값은 " + input + "입니다.");

		for(int i=10; i > 0; i--) {
			System.out.println(i);
			try {
				Thread.sleep(1000);  // 1초간 시간을 지연한다.
			} catch(Exception e ) {}
		}
	}
}

// 멀티쓰레드로 프로그램 작성 -> 실행 시 사용자 입력을 받지 않아도 카운트다운 작업은 실행 됨
import javax.swing.JOptionPane;

class Ex13_5 {
	public static void main(String[] args) throws Exception  {
		CountThread th1 = new CountThread();
		th1.start();
        
		// 사용자 입력을 받는 쓰레드
		String input = JOptionPane.showInputDialog("아무 값이나 입력하세요."); 
		System.out.println("입력하신 값은 " + input + "입니다.");
	}
}

class CountThread extends Thread {
	// 카운트 다운작업을 하는 쓰레드
	public void run() {
		for(int i=10; i > 0; i--) {
			System.out.println(i);
			try {
				sleep(1000);
			} catch(Exception e ) {}
		}
	} // run()
}

 

** 출처 : 남궁성의 정석코딩_자바의정석_기초편 유튜브 강의