코딩과 결혼합니다

[자바의 정석] chapter 13 : 쓰레드 본문

2세/Java

[자바의 정석] chapter 13 : 쓰레드

코딩러버 2023. 10. 27. 12:29
728x90

📌프로세스와 스레드

프로세스

  • 실행 중인 프로그램, 작업공간
  • 프로그램을 실행하면 OS로부터 실행에 필요한 자원을 할당받아 프로세스가 된다.
  • 데이터, 메모리 등의 자원 + 스레드 

스레드

  • 실제로 작업을 수행하는 것, 일꾼
  • 모든 프로세스에는 하나 이상의 스레드가 존재
  • 둘 이상의 스레드를 가진 프로세스를 '멀티스레드 프로세스'라고 한다.

⭐멀티태스킹과 멀티 스레딩

멀티태스킹(multi-tashking, 다중작업) : 여러 개의 프로세스가 동시에 실행
멀티스레딩 : 하나의 프로세스 내에서 여러 스레드가 동시에 작업을 수행

CPU의 코어는 한 번에 하나의 작업만 수행할 수 있다. (코어의 개수 = 처리되는 작업의 개수)

스레드의 수는 언제가 코어의 개수보다 훨씬 많기 때문에 각 코어가 아주 짧은 시간 동안 여러 작업을 번갈아 가며 수행함으로써 여러 작업들이 모두 동시에 수행되는 것처럼 보인다.

 

⭐멀티스레딩 장단점

멀티스레딩 장점

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

멀티스레딩 단점

  • 여러 스레드가 같은 프로세스 내에서 자원을 공유하면서 작업 ➡️ 동기화, 교착상태 같은 문제가 발생

📌스레드의 구현과 실행

1. Thread클래스를 상속

class MyThread extends Thread {
	public void run() { /* 작업내용 */ }
}
2. Runnable인터페이스를 구현

class MyThread implements Runnable {
	public void run() { /* 작업내용 */ }
}

Thread클래스를 상속받으면 다른 클래스를 상속받을 수 없기 때문에, Runnable인터페이스를 구현하는 방법이 일반적이다. 이 방법은 재사용성이 높고 코드의 일관성을 유지할 수 있기 때문에 보다 객체지향적이다.

 

인스턴스의 생성방법

Thread_1 t1 = new Thread_1(); //쓰레드의 자손 클래스의 인스턴스를 생성

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

Thread t2 = new Thread(new Thread_2()); //위의 두 줄을 한 줄로 간단히

Runnable을 구현하면 Thread클래스의 static메서드인 currentThread()를 호출하여 스레드에 대한 참조를 얻어 와야만 호출이 가능하다.

static Thread currentThread() 현재 실행 중인 스레드의 참조를 반환한다.
String getName()                     스레드의 이름을 반환한다.

 

스레드의 실행 - start()

  • 스레드를 생성 후에 start()를 호출해야만 스레드가 실행된다.
  • start()가 호출되면 일단 실행대기 상태에 있다가 자신의 차례가 되면 실행한다.
  • 한 번 실행이 종료된 스레드를 다시 실행할 수 없다. (하나의 스레드에 한 번의 start() 호출)

📌start()와 run()

main 메서드에서 run()을 호출하는 것은 단순히 클래스에 선언된 메서드를 호출하는 것.

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

  1. main메서드에서 스레드의 start()를 호출한다.
  2. start()는 새로운 스레드를 생성하고, 스레드가 작업하는 데 사용될 호출스택을 생성한다.
  3. 새로 생성된 호출스택에 run()이 호출되어 스레드가 독립된 공간에서 작업을 수행한다.
  4. 이제는 호출스택이 2개이므로 스케줄러가 정한 순서에 의해서 번갈아 가면서 실행된다. 
✔️ 스레드가 둘 이상일 때는 호출스택의 최상위에 있는 메서드일지라도 대기상태에 있을 수 있다.
✔️ 스레드들은 작성된 스케줄에 따라 자신의 순서가 되면 지정된 시간 동안 작업을 수행한다.
✔️ 주어진 시간 동안 작업을 마치치 못하면 자신의 차례가 돌아올 때까지 다시 대기상태로 있게 된다.
✔️ run()의 수행이 종료된 스레드는 호출스택이 모두 비워지면서 이 스레드가 사용하던 호출스택은 사라진다.

 

⭐main스레드

 

main메서드의 작업을 수행하는 것도 스레드이며, 이를 main스레드라고 한다. main메서드가 수행을 마쳤다 하더라도 다른 스레드가 아직 작업을 마치지 않은 상태라면 프로그램이 종료되지 않는다.

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

📌싱글스레드와 멀티스레드

싱글스레드 프로세스와 멀티스레드 프로세스의 비교(싱글 코어)

멀티스레드가 작업 수행에 시간이 더 소요되는 이유?

  1. 스레드 간의 작업 전환 - 현재 진행 중인 작업의 상태 등의 정보를 저장하고 읽어 오는 시간이 소요됨
  2. 대기시간 - 한 스레드가 화면에 출력하고 있는 동안 다른 스레드는 출력이 끝나기를 기다려야 한다.

➡️싱글 코어에서 단순히 CPU만을 사용하는 계산작업이라면 오히려 싱글스레드로 프로그래밍하는 것이 더 효율적 

 

 

싱글코어와 멀티코어의 비교

싱글 코어인 경우에는 멀티스레드라도 하나의 코어가 번갈아가면서 작업을 수행 ➡️ 두 작업이 겹치지 않는다.

멀티 코어에서는 멀티스레드로 두 작업을 수행하면 동시에 두 스레드가 수행 ➡️ 두 스레드의 경쟁

 

병행 : 여러 스레드가 여러 작업을 동시에 진행
병렬 : 하나의 작업을 여러 스레드가 나눠서 처리

 

 

싱글스레드 프로세스와 멀티스레드 프로세스의 비교

하나의 스레드로 작업을 처리한다면 첫 번째 그림처럼 사용자가 입력을 마칠 때까지 아무 일도 하지 못하고 기다기만 해야 한다. 그러나 두 개의 스레드로 처리한다면 사용자의 입력을 기다리는 동안 다른 스레드가 작업을 처리할 수 있기 때문에 보다 효율적인 CPU의 사용이 가능하다.


📌스레드의 우선순위

스레드는 우선순위라는 속성(멤버변수)을 가지고 있는데, 이 우선순위의 값에 따라 스레드가 얻는 실행 시간이 달라진다. 시각적인 부분이나 사용자에게 빠르게 반응해야하는 작업을 하는 스레드의 우선순위는 다른 작업을 수행하는 스레드에 비해 높아야 한다.

 

⭐스레드의 우선순위 지정하기

void setPriority(int newPriority) 스레드의 우선순위를 지정한 값으로 변경한다.
int getPriority                               스레드의 우선순위를 반환한다.
  • 스레드가 가질 수 있는 우선순위 범위는 1~10이며 숫자가 높을수록 우선순위가 높다.
  • 스레드의 우선순위는 스레드를 생성한 스레드로부터 상속받는 것이다.

 

스레드의 우선순위에 따른 할당되는 시간


📌스레드 그룹

서로 관련된 스레드 그룹을 생성해서 관리할 수 있다. 

하위 폴더 처럼 스레드 그룹에 다른 스레드 그룹을 포함 시킬 수 있는데, 자신이 속한 스레드 그룹이나 하위 스레드 그룹은 변경할 수 있지만 다른 스레드 그룹의 스레드를 변경할 수는 없다.

 

ThreadGroup을 사용해서 생성할 수 있으며, 주요 생성자와 메서드는 다음과 같다.

스레드를 스레드 그룹에 포함시키려면 Thread의 생성자를 이용해야 한다.

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

모든 스레드는 반드시 스레드 그룹에 포함되어 있어야 하며, 스레드 그룹을 지정하는 생성자를 사용하지 않은 스레드는 기본적으로 자신을 생성한 스레드와 같은 스레드 그룹에 속하게 된다.