[JAVA] Chapter 13 쓰레드 - 1

WOOK JONG KIM·2022년 9월 30일

자바의 정석

목록 보기
18/25
post-thumbnail

ch 13-1~4 프로세스 & 쓰레드

프로그램을 실행하면 OS로 부터 실행에 필요한 자원을 할당받아 프로세스가 됨

프로세스 : 실행 중인 프로그램 ( 수행에 필요한 자원 + 쓰레드)

둘 이상의 쓰레드를 가진 프로세스 -> 멀티쓰레드 프로세스

쓰레드 : 프로세스라는 작업공간에서 작업을 처리하는 일꾼

멀티쓰레드 장점

  • CPU의 사용률을 향상시킨다
  • 자원을 보다 효율적으로 사용할 수 있다
  • 사용자에 대한 응답성이 향상된다
  • 작업이 분리되어 코드가 간결해진다

반면 멀티쓰레드 프로세스는 여러 쓰레드가 같은 프로세스 내에서 자원을 공유하면서 작업을 하기에 동기화, 교착상태 같은 문제를 고려해야함

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

쓰레드 구현과 실행

  1. Thread 클래스 상속받는 방법(다른 클래스 상속 못받음)
class MyThread extends Thread{
	public void run() { /* 작업 내용 */} // Thread클래스의 run() 오버라이딩
}
  1. Runnable 인터페이스 구현( 일반적, 객체지향적 방법)
class MyThread extends Runnable{
	public void run() { /* 작업 내용 */} // Runnable인터페이스의 run() 오버라이딩
}

단순히 쓰레드를 통해 하고자하는 내용으로 run()의 몸통{}을 채우는 것!

package ch13;

public class Ex13_1 {

	public static void main(String[] args) {
		ThreadEx1_1 t1 = new ThreadEx1_1();
		
		Runnable r = new ThreadEx1_2();
		/* Runnable인터페이스를 구현한 클래스의 인스턴스 생성 후 ,
		생성된 인스턴스를 Thread클래스의 생성자에 매개변수로 제공해야 한다.*/
		Thread t2 = new Thread(r); // 생성자 Thread(Runnable Target)
		
		t1.start();
		t2.start();

	}

}

class ThreadEx1_1 extends Thread{
	public void run() {
		for(int i = 0; i < 5; i++) {
			System.out.println(this.getName()); // 조상인 Thread의 getName() 호출.
		}
	}
}
class ThreadEx1_2 implements Runnable{
	// 쓰레드가 수행할 작업
	public void run() {
		for(int i =0; i< 5; i++) {
			// Thread.currentThread() -> 현재 실행중인 Thread의 참조를 반환한다.
			// getName -> 쓰레드의 이름 반환.
			System.out.println(Thread.currentThread().getName());
		}
	}
}

ch 13-5~6 쓰레드 실행

start() 호출을 통해 실행

-> 호출하였다고 바로 실행되는 것이 아니라, 실행 대기 상태에 있다가 자신의 차례가 되어야 실행됨(실행 대기중 쓰레드 없다면 바로 실행)

쓰레드 실행 순서 -> OS의 스케쥴러가 작성한 스케쥴에 의해 실행

쓰레드를 한번더 실행하려면 -> 새로 쓰레드를 생성한 후 호출해야함!!

ThreadEx1_1 t1 = new ThreadEx1_1();
t1.start();

t1 = new ThreadEx1_1();
t1.start();

start() & run()

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

즉 서로 독립적인 공간에서 작업을 수행하는 것을 볼 수 있다


ch 13-7 main쓰레드

main메서드의 코드를 실행하는 쓰레드

프로그램 실행 시 기본적으로 하나의 쓰레드를 생성한 후 , 그 쓰레드가 main메서드를 호출해서 작업이 실행되도록 함

쓰레드 종류 : 사용자 쓰레드 & 데몬 쓰레드

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


ch 13-8~10 싱글쓰레드 & 멀티쓰레드

하나의 쓰레드로 작업 -> 한 작업을 마친 후에 다른 작업 수행

두개의 쓰레드로 작업 -> 짧은 시간동안 두개의 쓰레드 번갈아 가면서 실행

싱글스레드

package ch13;

public class Ex13_2 {

	public static void main(String[] args) {
		long startTime = System.currentTimeMillis();
		
		for(int i = 0; i < 300; i++)
			System.out.printf("%s", new String("-"));
		
		System.out.print("소요시간1: " + (System.currentTimeMillis() - startTime));
		
		for(int i = 0; i < 300; i++)
			System.out.printf("%s", new String("|"));
		
		System.out.print("소요시간2:" + (System.currentTimeMillis() - startTime));
	}

}

멀티쓰레드

싱글쓰레드 와 멀티쓰레드 각각 작업 시간은 거의 비슷

-> 오히려 멀티쓰레드가 작업전환(Context switching) 때문에 시간이 더 걸림 + 한 쓰레드가 화면에 출력하고 있는 동안 다른 쓰레드는 출력이 끝나길 기다리며 발생하는 대기 시간

싱글 코어에서 단순히 CPU만을 사용하는 계산작업이라면 오히려 싱글 쓰레드가 효율적

package ch13;

public class Ex13_3 {
	
	static long startTime = 0;

	public static void main(String[] args) {
		ThreadEx3_1 th1 = new ThreadEx3_1();
		th1.start();
		startTime = System.currentTimeMillis();
		
		for(int i = 0; i< 300; i++)
			System.out.printf("%s", new String("-"));
		
		System.out.print("소요시간1:" + (System.currentTimeMillis()- Ex13_3.startTime));

	}

}

class ThreadEx3_1 extends Thread{
	public void run() {
		for(int i=0; i< 300; i++)
			System.out.printf("%s", new String("|"));
		
		System.out.print("소요시간2:" + (System.currentTimeMillis() - Ex13_3.startTime));
	}
}

멀티코어에서 멀티 쓰레드로 작업 수행 시 두 작업이 겹치는 부분이 발생

-> 화면(console)이라는 자원을 두고 두 쓰레드가 경쟁하게 된다


ch 13-11~13 쓰레드의 I/O블락킹

I/O블락킹 : 쓰레드가 입출력 처리를 위해 기다리는 것

두 쓰레드가 서로 다른 자원을 사용하는 작업의 경우 멀티쓰레드가 효율적

ex) 사용자로부터 데이터 입력 받는 작업, 네트워크로 파일 주고 받음 등

사용자로 부터 입력을 받는 작업(A)과 화면에 출력하는 작업(B)을 하나의 쓰레드에서 처리한다면 사용자가 입력을 마칠때 까지 아무 일도 하지 못함

package ch13;

import javax.swing.JOptionPane;

public class Ex13_4 {

	public static void main(String[] args) throws Exception{
		String input = JOptionPane.showInputDialog("아무 값이나 입력하세요.");
		System.out.println("입력하신 값은 " + input + "입니다.");
		
		for(int i=10; i>0; i--)
		{
			System.out.println(i);
			try {
				Thread.sleep(1000); // 1초간 시간 지연.
			} catch(Exception e) {}
		}

	}

}

입력하신 값은 hello입니다.
10
9
8
7
6
5
4
3
2
1
package ch13;

import javax.swing.JOptionPane;

public class Ex13_5 {

	public static void main(String[] args) {
		ThreadEx5_1 th1 = new ThreadEx5_1();
		th1.start();
		
		String input = JOptionPane.showInputDialog("아무 값이나 입력하세요.");
		System.out.println("입력하신 값은 " + input + "입니다.");

	}

}

class ThreadEx5_1 extends Thread{
	public void run() {
		for(int i = 10; i >0; i--) {
			System.out.println(i);
			try {
				sleep(1000);
			} catch(Exception e) {}
		}
	}
}
숫자 출력 사이의 텀에 입력을 요청함!!
사용자가 입력을 마치지 않아도 화면에 계속 숫자가 출력되고 있음

10
9
8
7
입력하신 값은 hello입니다.
6
5
4
3
2
1

ch 13-14~15 쓰레드의 우선순위

쓰레드는 우선순위(1~10)라는멤버변수를 가짐

우선 순위가 높을수록 더 많은 작업 시간 갖도록함

ex) 파일전송이 있는 메신저의 경우, 파일 다운로드 처리보다 `채팅 내용을 전송하는 쓰레드의 우선순위가 높아야 채팅에 불편함이 없지만 다운로드 오래걸림

-> 보통 시각적인 부분이나 사용자에게 빠르게 반응해야 하는 작업에 우선순위를 높게 부여함

main 쓰레드는 우선순위가 5

void setPrioirty(int newPriority)
int getPriority()

public static final int MAX_PRIORITY = 10 // 최대 우선 순위
public static final int MIN_PRIORITY = 1 // 최대 우선 순위
public static final int NORM_PRIORITY = 5 // 최대 우선 순위
package ch13;

public class Ex13_6 {

	public static void main(String[] args) {
		ThreadEx6_1 th1 = new ThreadEx6_1();
		ThreadEx6_2 th2 = new ThreadEx6_2();
		
		//설정 전에는 main메서드에서 생성하였기에 우선순위 5를 상속받음!!
		th2.setPriority(7);
		
		System.out.println("Priority of th1(-) : " + th1.getPriority());
		System.out.println("Priority of th2(|) : " + th2.getPriority());
		th1.start();
		th2.start();

	}

}

class ThreadEx6_1 extends Thread{
	public void run() {
		for(int i = 0; i < 300; i++) {
			System.out.print("-");
			// 작업 지연 for 문(우선순위가 높으면 한번에 작업을 끝낼수도 있기에)
			for(int x = 0; x < 10000000; x++);  
		}
	}
}

class ThreadEx6_2 extends Thread{
	public void run() {
		for(int i = 0; i < 300; i++) {
			System.out.print("|");
			for(int x=0; x< 10000000l; x++);
		}
	}
}
Priority of th1(-) : 5
Priority of th2(|) : 7
-|-||||||||||||||||||||||||||||||||||||||||
|||||||||||||||||||||||||||||||||||||||||||
||||||||||||||||||||||||||||||
||||-|------
----------------------------------|||||||||||||||||||||||||---------|||||||||||||||||||||||||||||||||
||||||
||||||||||||||||---------
----------------------|||||||||||||----------------||----|-|-
||||||||||||||||||||||||||||||||||||
||||||||||||||||||||||||||||||||||||||--|||||||||------
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

멀티코어에서는 쓰레드의 우선순위에 따른 차이가 거의 없다!
-> 우선 순위를 높게 준다고 더 많은 실행시간과 기회를 갖게 될 것이라 기대하기 어려움

굳이 우선순위에 차등을 주어 쓰레드 실행하려면 특정 OS 스케쥴링 정책JVM의 구현을 확인해 보아야함 -> 해도 어느정도 예측만 가능


ch 13-16,17 쓰레드 그룹

쓰레드 그룹은 서로 관련된 쓰레드그룹으로 다루기 위한 것으로, 폴더를 생성해서 관련된 파일들을 함께 넣어서 관리하는 것처럼 쓰레드 그룹을 생성해서 쓰레드를 그룹으로 묶어서 관리할 수 있다.

Thread의 생성자 이용

Thread(ThreadGroup group, String name)
Thread(ThreadGroup group, Runnable target)
Thread(ThreadGroup group, Runnable target, String name)
Thread(ThreadGroup group, String name)

모든 쓰레드는 반드시 쓰레드 그룹에 포함되어야 함
-> 일반적으로 자신을 생성한 쓰레드에 포함됨

Thread의 쓰레드 그룹과 관련된 메서드

// 쓰레드가 자신이 속한 쓰레드 그룹 반환
ThreadGroup getThreadGroup()

// 처리 되지 않은 예외에 의해 쓰레드 그룹의 쓰레드가 실행이 종료 되면
// JVM에 의해 이 메서드가 자동으로 호출
void uncaughtException(Thread t, Throwalbe e)


ch 13-18,19 데몬 쓰레드

다른 일반 쓰레드의 작업을 돕는 보조적인 역할

일반 쓰레드가 모두 종료되면 데몬 쓰레드는 자동 종료됨

ex) 가비지 컬렉터, 워드프로세서 자동 저장

무한루프와 조건물을 이용 해 실행 후 대기하고 있다가 특정 조건이 만족되면 작업을 수행하고 다시 대기하도록 작성

public void run(){
	while(true){
    	try{
        	Thread.slee(3 * 1000) // 3초마다
        } catch(InterruptedException e){}
        
        // autoSave의 값이 true이면 autoSave()를 호출
        if(autoSave) autoSave();
    }
}

일반 쓰레드의 작성방법과 실행 방법이 같으나 실행 전 setDaemon(true)를 호출하면 됨

package ch13;

class Ex13_7 implements Runnable{
	static boolean autoSave = false;

	public static void main(String[] args) {
		// run메서드를 스레드의 생성자에 전달하라는 뜻으로 볼수있다 
		Thread t = new Thread(new Ex13_7()); // Thread(Runnable r);
		
		t.setDaemon(true); // 이 부분이 없으면 종료 되지 않는다! 
		t.start();
		
		for(int i = 1; i <= 10; i++)
		{
			try {
				Thread.sleep(1000);
			} catch(InterruptedException e) {}
			System.out.println(i);
			
			//5초전엔 false 
			if(i==5) autoSave = true;
		}
		System.out.println("프로그램을 종료합니다. ");

	}
	
	public void run() {
		while(true) {
			try {
				Thread.sleep(3* 1000); // 3초마다   
			} catch(InterruptedException e) {}
			
			// autoSave의 값이 true이면 autoSave()를 호출한다.
			if(autoSave) autoSave();
		}
	}
	
	public void autoSave() {
		System.out.println("작업파일이 자동저장되었습니다.");
	}

}
1
2
3
4
5
작업파일이 자동저장되었습니다.
6
7
8
작업파일이 자동저장되었습니다.
9
10
프로그램을 종료합니다. 

메인쓰레드와 데몬 쓰레드 존재하는 것을 볼 수 있다

profile
Journey for Backend Developer

0개의 댓글