본문 바로가기

프로그래밍(TA, AA)/JVM 언어

[병렬프로그래밍] 스레드 이해하기

스레드의 생명주기


스레드의 생명주기는 프로세스의 생명주기와 유사합니다. java에서 프로세스 생명주기를 나타내는 그림입니다.



프로그램이 실행되면 메인부터 시작하여 하나하나씩 개발자가 짠 함수들이 순차적으로 실행됩니다. 이때 a(), b()라는 함수가 있는데, 동시에 실행하고 싶을때 쓰레드를 이용하게 됩니다. 각 함수별로 해당 함수를 실행하는 스레드를 생성합니다. 


자바에서 실행되는 모든 엔티티는 객체입니다. 쓰레드 역시 마찬가지로 객체입니다. 쓰레드를 실행하는 메소드(start()) 실행하면 쓰레드가 동작하면서 CPU가 점유된 상태가 됩니다.


스레드 이후 커리큘럼은 파일 IO에 대한 설명이 이루어지는데, 파일 IO 역시 네트워크 프로그래밍과 연결되어 있습니다. 왜냐하면 네트워크 프로그래밍은 결국 네트워크 IO를 사용하겠다는 의미이기 때문입니다. 


자바 스레드의 생성자

- Thread() : Thread를 상속받은 고유의 클래스 생성 필요. 클래스 내부에 b()같은 함수 정의

- Thread(Runnable target) : Runnable이 들어간 경우는 인터페이스 구현

- Thread(Runnable target, String name) : Runnable이 들어간 경우는 인터페이스 구현

- Thread(String name)

- Thread(ThreadGroup group, Runnable target)

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

- Thread(ThreadGroup group, Runnabel target, String name, long stackSize)

- Thread(ThreadGroup group, String name)


자바에서의 스레드 생성 방법은 두가지가 존재합니다. 1) class를 상속, 2) interface를 구현 하는 방법입니다. Runnable이 들어간 경우가 인터페이스를 구현하는 경우입니다. Runnable 인터페이스 안에는 Run()이라는 함수가 있으며, Run()이라는 함수안에 원하는 스레드 로직을 넣으면 됩니다. ThreadGroup이란 여러개의 쓰레드들을 관리하는 그룹입니다. 부모가될 그룹명을 지정할 수도 있으며, 부모명을 지정안하면 main 함수의 main Thread 그룹이 있으며 그 쓰레드 그룹이 기본이 됩니다.



스레드 생성(2가지 방법)

1) White box (Thread 상속 이용방법)

- 스레드를 상속받는 클래스를 작성합니다.

- run() 메소드를 오바라이딩하여 내용부를 구현합니다.

- main() 메소드 내부에서 스레드를 상속받은 클래스의 객체를 생성합니다.

- 해당 객체의 start() 메소드를 호출합니다.


2) Black box (Runnable 구현 작성방법)

- Runnable을 구현하는 클래스를 작성합니다.

- run() 메소드를 오바라이딩하여 내용부를 구현합니다.

- main() 메소드에서 Runnable을 구현한 클래스의 객체를 생성합니다.

- Thread 객체를 생성하여 매개변수로 대입합니다.

- Thread 객체의 start() 메소드를 호출합니다.


Runnable 인터페이스를 통한 쓰레드 구현 방법

//Thread를 사용하는 두번째 방법: Runnable 인터페이스를 상속받아 사용
class MyRunnable implements Runnable {
public void run() { // run() 메서드 오버라이딩
for(int i=10; i>=0; i--)
System.out.println(i+" ");
}
}
public class MyRunnableTest {
public static void main(String[] args) {
// 객체 생성 : Thread t=new Thread(new 클래스이름());
Thread t = new Thread(new MyRunnable());
t.start(); // start() 메서드 호출 -> 스레드 시작 -> run() 호출
}

}


객체 지향 시스템에서 기능의 재사용(상속 vs 합성)


"Favor Object composition over class inheritance."

  - 객체합성이 클래스 상속보다 더 나은 방법이다.


white box reuse : 디자인 패턴에서 상속을 통한 재사용

- 객체 상속시 private으로 선언되지 않은 모든 변수와 메소드를 생성하는 하위 클래스에 노출.

    : 불필요하게 노출되는 경우가 생길 수 있음(단점)

- 하위클래스에서 수퍼클래스의 내부가 보인다는 의미로 사용.

- 부모, 자식간의 의존 관계가 강하게 이루어져 있어, 부모 수정시 자식도 영향받을 수 있음.

- 상속관계복잡시, 유지보수 비용이 많이 듦.


black box reuse : 디자인 패턴에서 합성을 통한 재사용

- 캡슐화 : 하나의 클래스를 블랙박스화하는 것

- white box reuse의 불필요하게 노출되는 변수가 모두 가려진다는 장점.

- 노출이 필요한 함수에 대한 껍데기만 제공하고 나머지 것은 없음.


합성과 상속에 각각 장단점이 있으나, 디자인을 잘하면 최적의 방식으로 이용할 수 있습니다.




스레드의 종료

run() 메서드의 종료나 stop() 메서드에 의해서 스레드의 실행이 완료된 상태를 말합니다.

그렇지만, 개발자가 직접  stop() 메서드를 호출하는 것은 권장하지 않습니다. 왜냐하면, 여러 쓰레드들 간에 concurrency 문제가 발생할 수 있기 때문입니다. 대신 스레드의 종료 방법으로는 플래그를 이용하는 방법과 interrupt() 메소드를 이용하는 방법이 있습니다.



일반 스레드 vs 데몬 스레드

1) 일반 스레드

 - 애플리케이션 내부의 모든 스레드가 종료되어야 JVM 종료

 - main() 메소드가 종료되어도 중간에 생성된 스레드가 종료되지 않으며 애플리케이션은 종료하지 않는다.

 애플리케이션 내부의 모든 스레드가 종료되지 않으면 JVM은 종료되지 않습니다.


2) 데몬 스레드 (Thread 클래스 내부에 setDaemon이라는 함수를 통해 수동지정 가능)

 - 서비스 스레드라고 불림

 - 대부분 낮은 우선순위를 가짐.

 - 백그라운드 서비스를 위해 쓰임

 - 가비지 콜렉터가 가장 대표적인 데몬스레드임

 - 메인 스레드가 종료되면 강제적으로 모든 데몬스레드도 종료(메인스레드 종료시 JVM 종료)



데몬 스레드와 join 메소드









스레드 이후 커리큘럼은 파일 IO에 대한 설명이 이루어지는데, 파일 IO 역시 네트워크 프로그래밍과 연결되어 있습니다. 왜냐하면 네트워크 프로그래밍은 결국 네트워크 IO를 사용하겠다는 의미이기 때문입니다.