Loading...

JAVA / / 2022. 1. 28. 17:41

자바 37강. 스레드(Thread) ★

반응형

스레드(Thread) : 실, 수명

package site.metacoding.ex23;

public class ThreadEx01 {

    // main 스레드 시작
    public static void main(String[] args) {
        System.out.println(1);
        // 컴파일 에러 -> 쓰레드가 잠드는 시점에 예외가 발생할 수 있음
        try {
            Thread.sleep(2000); // 밀리세컨즈 1/1000초, cpu가 sleep중 ...
        } catch (Exception e) { // InterruptedException 니가 잘때가 아니야!(방해)
            e.printStackTrace();
        }
        System.out.println(2);
    }
}

프로그래밍에서의 실의 길이는
메서드 큐의 크기를 말한다.

메인 큐를 실행시키는 게 스레드.
자바에서 메서드를 만들려면 클래스를 무조건 만들어야 한다.

JVM의 생명은 메인 스레드
즉 메인 내부의 코드이다.

컴퓨터의 cpu는 하나이기 때문에 두 개의 실을 왔다 갔다 한다.
왔다갔다 -> 문맥 교환 -> context switching

스레드는 실, 일꾼은 cpu

어떻게 왔다 갔다 하느냐?
타임 슬라이싱으로 시간을 쪼개서 한다.

스레드는 실이고, cpu가 실을 왔다 갔다 한다.
왔다 갔다 = context switching

화장실에 3명이 동시에 들어올 때,
우선순위를 미리 정해주지 않으면
컴퓨터는 모르기 때문에
화장실에 데드락이 걸리게 된다.

이럴 때 알고리즘이 필요하다.

화장실이 제일 급한 사람부터 대기열에 들어가면
대기열 큐에는 A가 먼저 들어가고
B는 15초 기다리고,
C는 22초 기다리게 된다.

반대로 큐에 c가 먼저 들어가면
B는 2초 기다리고,
A는 9초 기다리게 된다.

훨씬 짧아졌다.

이걸 라운드 로빈(Round Robin)이라고 한다.
라운드 로빈은 타임 슬라이싱(time slicing)을 사용하여 cpu를 할당한다.

타임 슬라이싱의 시간을 1초로 정했다.

이때 화장실은 동기화되어 있다.

애초에 화장실이 동기화되어 있지 않다면
이런 알고리즘이 필요가 없다!

대기하는 순서는 중요하지 않고,
만약 순서가 a - b - c라고 한다면
a가 1초 볼일을 보고 14초가 남아있다.
다음 b가 1초 볼일을 보고 6초가 남고,
c는 1초가 남을 것이다.

화장실의 알고리즘으로는 부적합해 보인다.

타임 슬라이싱에서 중요한 것은 문맥 교환이다.
몇 초가 남았는지 기억해야 하기 때문이다.



스레드 사용하는 것은 속도를 빠르게 할까, 느리게 할까?

왔다 갔다 하면 더 느려진다.

하지만 스레드의 장점이 2가지가 있다.

1. 눈을 속일 수 있다.

(동시에 실행되는 것처럼 보인다.)

모니터에 vscode가 켜져 있고, 크롬 창도 켜져 있다.
타임 슬라이싱으로 실행되고 있는 것이다.
하지만 우리 눈에는 너무 빨라서 동시에 실행되어 있는 것처럼 보이게 된다.

즉, UX(user experience)가 좋아지는 것이다.

타임 슬라이싱은 OS가 제공해주는 것이다.

정리해보면 스레드는 실을 타임 슬라이싱을 이용해 돌아가면서 일한다.

2. cpu가 놀지 못하게 한다.

(cpu가 멍 때리지 않는다.)

메모리나 하드디스크가 데이터를 저장할 때는
cpu가 할 일이 없어 놀고 있다.

그림은 cpu(gpu)가 그리고
가운데는 통신을 받아와서 그림을 그릴 건데
통신을 위해 ByteStream 연결은 cpu가 하지만
BR는 메모리(램)로 다운로드한다.
이때는 메모리와 하드디스크가 일을 하고
cpu는 놀게 된다.

그림도 위에서부터 아래로 그린다.
구름을 그리다가 통신을 하는 동안
cpu는 기다려야 한다.

cpu의 입장에서는 이걸 pending(대기)이라고 한다.

보통 cpu가 0.013초 놀면 버벅거린다고 한다.

cpu가 기다리는 동안 나무가 안 그려지고 있으니까
사용자는 버벅거린다고 느끼는 것이다.

이때 스레드를 사용한다.

NewWorker 스레드에게 통신을 맡기고
메인 스레드가 나무를 그리고 있는다.

메인 스레드가 나무 그림을 그리는데 3초가 걸리고
뉴 워커 스레드는 통신을 하는데 5초가 걸린다면

메인 스레드가 먼저 끝나고 종료가 되어도
프로그램은 종료되지 않는다.

모든 프로그램은 하나의 스레드라도 살아있으면
종료되지 않는다.

뉴워커 스레드가 펜딩이 끝나고
돌아와서 그림을 그리려고 할 때

스레드마다 그림 그리는 것을 허용해준다면
통신이 끝나는 순서에 따라
그림이 다른 그림에 가려질 수 있다.

따라서 그림 그릴 수 있는 스레드는 하나뿐이다.

근데 뉴 워커 스레드가 펜딩이 끝나고 돌아와도
메인 스레드가 종료되어 그림을 그릴 수가 없다.

따라서 메인 스레드는 통신받아 오는 스레드가 돌아올 때까지
while을 돌며 기다리고 있어야 한다.

즉, 다른 스레드가 종료될 때까지 살아있어야 한다.

이렇게 되면 ux도 좋아지고,
빨라진 것처럼 느껴지게 된다.

멍 때리는 시간을 없앴기 때문이다.

하지만 그림을 그리는 프로그램이 아니라면
메인 스레드는 다른 스레드의 종료를 기다릴 필요가 없다.


 

package site.metacoding.ex23; 

class NewWorker implements Runnable { 
	// 타겟
	@Override 
	public void run() {
		for (int i = 1; i < 6; i++) { 
			try { 
				System.out.println("뉴워커 스레드 : " + i); 
				Thread.sleep(1000); 
			} catch (Exception e) { 
				e.printStackTrace(); 
			} 
		} 
	} 
} 

public class ThreadEx02 {  
	// MainThread의 타겟은 main 메서드 -> 메인 쓰레드의 'task' 작업, 임무
    // 타겟을 실행시키고 종료시키는 임무, 
    public static void main(String[] args) {
    	// NewWorker 생성 
        Thread newWorker = new Thread(new NewWorker()); // 타겟이 없음 -> 타겟은 생성자로 지정 
        newWorker.start(); // run() 메서드 호출 
        
        // 메인쓰레드는 스타트만 때려놓고 자기 일 하러 감 
        // 그리고 뉴워커 쓰레드가 런 메서드 내부 실행 
        // 비동기 프로그램 ! 
        for (int i = 1; i < 6; i++) { 
        	try {
            	System.out.println("메인 스레드 : " + i); 
                Thread.sleep(1000); 
			} catch (Exception e) { 
            	e.printStackTrace(); 
			} 
		} 
	} 
}


Thread.start( )



Thread.start( )는 메서드가 아니라서 OS에게 요청한다.
스레드의 제어권은 OS에게 있기 때문이다.

자바가 OS에게 스레드의 타겟을 알려줘야 한다.
그럼 그 타겟을 OS가 때려준다.

그럼 자바에서 기본적으로 실행되고 있는 메인 스레드와
타임 슬라이싱 하면서 헬로 메서드를 실행시킨다.

하지만 이렇게 하면 헬로 메서드는 실행이 안된다.
메서드는 1급 객체가 아니라 주소를 알 수 없기 때문에
OS에게 헬로 메서드가 어디 있는지 알려줄 수가 없다.

그래서 메서드가 클래스나 인터페이스 내부에 있어야 한다.

1급 객체라는 말은 바로 찾아갈 수 있는 주소가 있다는 말이다.
객체지향 언어라서 모든 건 클래스 위주이다.

어쩔 수 없이 hello( )가 실이야 라고 말할 수 없으므로
클래스의 이름을 알려줘야 한다.
Call이 실이야 라고 알려주는 것이다.

윈도우 입장에서는 Call 안에 어떤 메서드인지 알 수가 없으니
또 문제가 생겼다.

그래서 애초에 강제시켜서
run( ) 메서드를 때리라고 OS에게 말해준다.

프로그램을 만드는 사람이 실수로
run메서드를 만들지 않을 수도 있으니까
인터페이스로 묶어서
무조건 run 메서드를 만들도록 강제시킨 것이다.

그걸 위해 생긴 게 Runnable 인터페이스다.

 

그래서 이제 Call 클래스를 저렇게 만들지 않고
class Call implements Runnable {
run( ) {
}
}

이렇게 만들어야 강제로 run메서드를 가지게 된다.










[출처]

https://cafe.naver.com/metacoding

 

메타코딩 : 네이버 카페

코린이들의 궁금증

cafe.naver.com


메타 코딩 유튜브
https://www.youtube.com/c/%EB%A9%94%ED%83%80%EC%BD%94%EB%94%A9

 

메타코딩

문의사항 : getinthere@naver.com 인스타그램 : https://www.instagram.com/meta4pm 깃헙 : https://github.com/codingspecialist 유료강좌 : https://www.easyupclass.com

www.youtube.com

 

반응형