Thread

동작하고 있는 프로그램을 프로세스(Process)라고 한다.
스레드(thread)는 프로세스(process) 내에서 실제로 작업을 수행하는 주체를 의미한다.
보통 한 개의 프로세스는 한 가지의 일을 하지만, 쓰레드를 이용하면 한 프로세스 내에서 두 가지 또는 그 이상의 일을 동시에 할 수 있다.


public class Sample extends Thread { // Sample 클래스가 Thread 클래스를 상속
public void run() { // Thread 를 상속하면 run 메서드를 구현해야 한다.
System.out.println("thread run.");
}
public static void main(String[] args) {
Sample sample = new Sample();
sample.start(); // start()로 쓰레드를 실행한다.
}
}
// Thread 클래스를 extends 했기때문에 start 메서드 실행시 run 메서드가 수행된다.
// Thread 클래스는 start 실행 시 run 메서드가 수행되도록 내부적으로 동작한다.
// run()을 호출하는 것은 생성된 스레드 객체를 실행하는 것이 아니라, 단순히 스레드 클래스 내부의 run 메서드를 실행시키는 것이다.
// 즉, main 함수의 스레드를 그대로 사용해서 run 메서드를 실행하기 때문에 새로운 스레드가 생기지 않고 병렬처리를 할 수 없다.
start()는 새로운 스레드를 실행하는데 필요한 호출 스택(call stack)을 생성한 다음에 run을 호출해서, 생성된 호출 스택에 run()이 첫 번째로 저장되게 한다.
start()를 호출하면 스레드를 새롭게 생성해서 해당 스레드를 runnable 한 상태로 만든 후 run() 메서드를 실행하게 된다. 따라서 start()를 호출해야만 멀티스레드로 병렬 처리가 가능해진다.
public class Sample extends Thread {
int seq;
public Sample(int seq) {
this.seq = seq;
}
public void run() {
System.out.println(this.seq + " thread start."); // 쓰레드 시작
try {
Thread.sleep(1000); // 1초 대기한다.
} catch (Exception e) {
}
System.out.println(this.seq + " thread end."); // 쓰레드 종료
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) { // 총 10개의 쓰레드를 생성하여 실행한다.
Thread t = new Sample(i);
t.start();
}
System.out.println("main end."); // main 메서드 종료
}
}
// 어떤 쓰레드인지 확인하기 위해서 쓰레드마다 생성자에 순번을 부여
// 시작과 종료 사이에 1초의 간격이 생기도록(Thread.sleep(1000)) 작성

위 결과를 통해 쓰레드는 순서에 상관없이 동시에 실행된다는 사실을 알 수 있다.
(참고로 쓰레드가 종료되기 전에 main 메서드가 종료, main 메서드 종료 시 "main end."라는 문자열이 출력되는데 위 결과를 보면 중간쯤에 출력되어 있다.)
모든 쓰레드가 종료된 후에 main 메서드를 종료시키고 싶은 경우 >> join
import java.util.ArrayList;
public class Sample extends Thread {
int seq;
public Sample(int seq) {
this.seq = seq;
}
public void run() {
System.out.println(this.seq+" thread start.");
try {
Thread.sleep(1000);
}catch(Exception e) {
}
System.out.println(this.seq+" thread end.");
}
public static void main(String[] args) {
ArrayList<Thread> threads = new ArrayList<>();
for(int i=0; i<10; i++) {
Thread t = new Sample(i);
t.start();
threads.add(t);
}
for(int i=0; i<threads.size(); i++) {
Thread t = threads.get(i);
try {
t.join(); // t 쓰레드가 종료할 때까지 기다린다.
}catch(Exception e) {
}
}
System.out.println("main end.");
}
}
생성된 쓰레드를 담기 위해서 ArrayList 객체인 threads를 만든 후 쓰레드 생성시 생성된 객체를 threads에 저장
main 메서드가 종료되기 전에 threads에 담긴 각각의 thread에 join 메서드를 호출하여 쓰레드가 종료될 때까지 대기하도록 설정
쓰레드의 join 메서드는 쓰레드가 종료될 때까지 기다리게 하는 메서드

쓰레드 프로그래밍시 가장 많이 실수하는 부분이 바로 쓰레드가 종료되지 않았는데 쓰레드가 종료된 줄 알고 그 다음 로직을 수행하게 만드는 일이다.
쓰레드가 종료된 후 그 다음 로직을 수행해야 할 때 꼭 필요한 join 메서드를 꼭 기억하기!!
sleep
1- 현재 쓰레드 멈추기
2- 자원을 놓아주지는 않고, 제어권을 넘겨주므로 데드락이 발생할 수 있음
interrupt
1- 다른 쓰레드를 깨워서 interruptedException 을 발생
2- Interrupt가 발생한 쓰레드는 예외를 catch하여 다른 작업을 할 수 있음
join
1- 다른 쓰레드의 작업이 끝날 때 까지 기다리게 함
2- 쓰레드의 순서를 제어할 때 사용할 수 있음

보통 쓰레드 객체를 만들 때 위의 예처럼 Thread 클래스를 상속하여 만들기도 하지만 보통은 Runnable 인터페이스를 구현하도록 하는 방법을 주로 사용
>> Thread 클래스를 상속하면 다른 클래스를 상속할 수 없기 때문 (자바는 다중상속 x)
추상 메서드 run을 반드시 구현해야 한다.
// Runnable 이름부터 인터페이스의 느낌이 물씬 풍김, 인터페이스는 주로 형용사로 작성하기 때문
import java.util.ArrayList;
public class Sample implements Runnable {
//쓰레드를 구현하기 위한 템플릿에 해당
//인터페이스의 구현체를 만들고 Thread 객체 생성 시에 넘김
int seq;
public Sample(int seq) {
this.seq = seq;
}
public void run() {
System.out.println(this.seq+" thread start.");
try {
Thread.sleep(1000);
}catch(Exception e) {
}
System.out.println(this.seq+" thread end.");
}
public static void main(String[] args) {
ArrayList<Thread> threads = new ArrayList<>();
for(int i=0; i<10; i++) {
Thread t = new Thread(new Sample(i));
t.start();
threads.add(t);
}
for(int i=0; i<threads.size(); i++) {
Thread t = threads.get(i);
try {
t.join();
}catch(Exception e) {
}
}
System.out.println("main end.");
}
}
// Thread를 extends 하던 것에서 Runnable을 implements 하도록 변경
Runnable 인터페이스는 run 메서드를 구현하도록 강제한다.
Thread 를 생성하는 부분이 다른데 Thread의 생성자로 Runnable 인터페이스를 구현한 객체를 넘길 수 있는 방법을 사용했다.
인터페이스를 이용했으니 상속 및 기타 등등에서 좀 더 유연한 프로그램으로 발전했다
Runnable 인터페이스를 구현하게되면 재사용성이 높고, 코드의 일관성을 유지할 수 있다!
자바에서 각 스레드는 우선순위(priority)에 관한 자신만의 필드를 가지고 있다.
우선순위에 따라 특정 스레드가 더 많은 시간 동안 작업을 할 수 있도록 설정할 수 있다.
getPriority()와 setPriority() 메소드를 통해 스레드의 우선순위를 반환하거나 변경할 수 있다.
스레드의 우선순위가 가질 수 있는 범위는 1부터 10까지이며, 숫자가 높을수록 우선순위 또한 높아진다.
하지만, 스레드의 우선순위는 비례적인 절댓값이 아닌 어디까지나 상대적인 값이며 우선순위가 10인 스레드가 우선순위가 1인 스레드보다 10배 더 빨리 수행되는 것이 아니다.
(우선순위가 10인 스레드는 우선순위가 1인 스레드보다 좀 더 많이 실행 큐에 포함되어, 좀 더 많은 작업 시간을 할당받을 뿐)
그리고 스레드의 우선순위는 해당 스레드를 생성한 스레드의 우선순위를 상속받게 된다.
일반적으로 하나의 프로세스는 하나의 스레드를 가지고 작업을 수행하게 된다.
하지만 멀티 스레드(multi thread)란 하나의 프로세스 내에서 둘 이상의 스레드가 동시에 작업을 수행하는 것을 의미한다.
멀티 프로세스(multi process)는 여러 개의 CPU를 사용하여 여러 프로세스를 동시에 수행하는 것을 의미한다.
멀티 프로세스는 각 프로세스가 독립적인 메모리를 가지고 별도로 실행되지만, 멀티 스레드는 각 스레드가 자신이 속한 프로세스의 메모리를 공유한다는 점이 다르다.
멀티 스레드는 각 스레드가 자신이 속한 프로세스의 메모리를 공유하므로, 시스템 자원의 낭비가 적고, 하나의 스레드가 작업을 할 때 다른 스레드가 별도의 작업을 할 수 있어 사용자와의 응답성도 좋아진다.
context switching
컴퓨터에서 동시에 처리할 수 있는 최대 작업 수는 CPU의 코어(core) 수와 같다.
CPU의 코어 수보다 더 많은 스레드가 실행되면, 각 코어가 정해진 시간 동안 여러 작업을 번갈아가며 수행하게 된다.
이때 각 스레드가 서로 교체될 때 스레드 간의 문맥 교환(context switching)이라는 것이 발생한다.
(문맥 교환이란 현재까지의 작업 상태나 다음 작업에 필요한 각종 데이터를 저장하고 읽어오는 작업)
문맥 교환에 걸리는 시간이 커지면 커질수록, 멀티 스레딩의 효율은 저하된다.
(많은 양의 단순한 계산은 싱글 스레드로 동작하는 것이 더 효율적일 수 있다.)
많은 수의 스레드를 실행하는 것이 언제나 좋은 성능을 보이는 것은 아니라는 점을 유의!!
스레드 그룹(thread group)
스레드 그룹(thread group)이란 서로 관련이 있는 스레드를 하나의 그룹으로 묶어 다루기 위한 장치
(자바에서는 스레드 그룹을 다루기 위해 ThreadGroup이라는 클래스를 제공)
스레드 그룹은 다른 스레드 그룹을 포함할 수도 있으며, 이렇게 포함된 스레드 그룹은 트리 형태로 연결된다.
이때 스레드는 자신이 포함된 스레드 그룹이나 그 하위 그룹에는 접근할 수 있지만, 다른 그룹에는 접근할 수 없다.
(스레드가 접근할 수 있는 범위를 제한하는 보안상으로도 중요한 역할)
class ThreadWithRunnable implements Runnable {
public void run() {
try {
Thread.sleep(10); // 0.01초간 스레드를 멈춤.
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class Thread03 {
public static void main(String[] args){
Thread thread0 = new Thread(new ThreadWithRunnable());
thread0.start(); // Thread-0 실행
System.out.println(thread0.getThreadGroup());
ThreadGroup group = new ThreadGroup("myThread"); // myThread라는 스레드 그룹 생성함.
group.setMaxPriority(7); // 해당 스레드 그룹의 최대 우선순위를 7로 설정함.
// 스레드를 생성할 때 포함될 스레드 그룹을 전달할 수 있음.
Thread thread1 = new Thread(group, new ThreadWithRunnable());
thread1.start(); // Thread-1 실행
System.out.println(thread1.getThreadGroup());
}
}
// main() 메소드에서 생성된 스레드의 기본 스레드 그룹의 이름은 "main"이 되며, 최대 우선순위는 10으로 자동 설정
데몬 스레드(deamon thread)
데몬 스레드(deamon thread)란 다른 일반 스레드의 작업을 돕는 보조적인 역할을 하는 스레드를 가리킴
데몬 스레드는 일반 스레드가 모두 종료되면 더는 할 일이 없으므로, 데몬 스레드 역시 자동으로 종료된다.
생성 방법과 실행 방법은 모두 일반 스레드와 같으며, 실행하기 전에 setDaemon() 메소드를 호출하여 데몬 스레드로 설정하기만 하면 된다.
데몬 스레드는 일정 시간마다 자동으로 수행되는 저장 및 화면 갱신 등에 이용되고 있다.
가비지 컬렉터(gabage collector)
데몬 스레드를 이용하는 가장 대표적인 예
프로그래머가 동적으로 할당한 메모리 중 더 이상 사용하지 않는 영역을 자동으로 찾아내어 해제해 주는 데몬 스레드를 뜻한다.
(자바에서는 프로그래머가 메모리에 직접 접근하지 못하게 하는 대신에 가비지 컬렉터가 자동으로 메모리를 관리)
가비지 컬렉터를 이용하면 프로그래밍을 하기가 훨씬 쉬워지며, 메모리에 관련된 버그가 발생할 확률도 낮아진다.
가비지 컬렉터가 동작하는 동안에는 프로세서가 일시적으로 중지되므로, 필연적으로 성능의 저하가 발생 but 요즘에는 가비지 컬렉터의 성능이 많이 향상되어, 새롭게 만들어지는 대부분의 프로그래밍 언어에서 가비지 컬렉터를 제공
.
.
.
+
<메인 스레드>
Main메서드도 하나의 스레드이다. 이를 메인 스레드 라고 한다.
메인 쓰레드는 프로그램이 시작하면 가장 먼저 실행되는 쓰레드이며, 모든 쓰레드는 메인 쓰레드로부터 생성된다.
다른 쓰레드를 생성해서 실행하지 않으면, 메인 메서드, 즉 메인 쓰레드가 종료되는 순간 프로그램도 종료된다.
하지만 여러 쓰레드를 실행하면, 메인 쓰레드가 종료되어도 다른 쓰레드가 작업을 마칠 때까지 프로그램이 종료되지 않는다.
쓰레드는 '사용자 쓰레드(user thread)'와 '데몬 쓰레드(daemon thread)'로 구분되는데,실행 중인 사용자 쓰레드가 하나도 없을 때 프로그램이 종료된다.
public class ThreadDemo {
public static void main(String[] args) {
Thread t1 = Thread.currentThread();
System.out.println("currentThread = " + t1);
Thread t2 = new Thread(new ThreadEx_1());
System.out.println("newThread = " + t2);
}
}
class ThreadEx_1 implements Runnable {
@Override
public void run() {}
}
// currentThread 메서드는 현재 실행 중인 쓰레드의 참조를 반환하는 스태틱 메서드
// 실행결과
currentThread = Thread[main,5,main]
newThread = Thread[Thread-0,5,main]
메인 메서드에서 현재 쓰레드를 참조하면 main 쓰레드가 반환되는 것을 확인할 수 있다.
그리고 쓰레드 클래스의 toString 메서드는 아래와 같이 구현된다.
public String toString() {
ThreadGroup group = getThreadGroup();
if (group != null) {
return "Thread[" + getName() + "," + getPriority() + "," +
group.getName() + "]";
} else {
return "Thread[" + getName() + "," + getPriority() + "," +
"" + "]";
}
}
// 대괄호 안에 첫 번째가 쓰레드 이름이고 두 번째는 해당 쓰레드의 우선순위, 세 번째가 쓰레드가 속한 쓰레드 그룹의 이름
<스레드의 상태>
멀티쓰레드 프로그래밍을 잘하기 위해서는 정교한 스케줄링을 통해 자원과 시간을 여러 쓰레드가 낭비 없이 잘 사용하도록 해야 한다.
이를 위해서는 쓰레드의 상태와 관련 메서드를 잘 알아야 하는데, 먼저 쓰레드에는 5 가지 상태가 있다.

JDK 1.5부터 getState() 메서드를 통해 쓰레드의 상태를 확인가능
쓰레드의 상태는 메서드를 통해 제어할 수 있다. 일반적으로 start()를 통해 쓰레드를 실행 가능한 상태로 만들면 run 메서드에 의해 코드가 실행되고 모든 작업을 마치면 TERMINATED 상태가 되지만, 메서드를 이용해 쓰레드를 정지시키거나 다시 실행시킬 수 있다.
<I/O블로킹>
사용자 입력을 받을 때는 사용자 입력이 들어오기 전까지 해당 쓰레드가 일시정지 상태가 된다. 이를 I/O 블로킹이라고 한다.
한 쓰레드 내에서 사용자 입력을 받는 작업과 이와 관련 없는 작업 두 가지 코드를 작성하면, 사용자 입력을 기다리는 동안 다른 작업 또한 중지되기 때문에 CPU의 사용 효율이 떨어진다.
이 경우 사용자 입력을 받는 쓰레드와, 이와 관련 없는 다른 작업을 하는 쓰레드를 분리해주면 더욱 효율적으로 CPU를 사용할 수 있다.
(싱글 스레드 경우)
import javax.swing.JOptionPane;
public class ThreadDemo {
public static void main(String[] args) {
// 사용자입력
String input = JOptionPane.showInputDialog("아무 값이나 입력하세요");
System.out.println("입력 값은 " + input + " 입니다.");
// 카운트다운
for (int i = 10; i > 0; i--) {
System.out.println(i);
try {
Thread.sleep(1000);
} catch (Exception e) { }
}
}
}
// 아래는 출력 결과
입력 값은 XXXXX 입니다.
10
9
8
7
6
5
4
3
2
1
(멀티 스레드 경우)
import javax.swing.JOptionPane;
public class ThreadDemo {
public static void main(String[] args) {
Thread t = new Thread(new MyThread());
t.start();
String input = JOptionPane.showInputDialog("아무 값이나 입력하세요");
System.out.println("입력 값은 " + input + " 입니다.");
}
}
class MyThread implements Runnable {
@Override
public void run() {
for (int i = 10; i > 0; i--) {
System.out.println(i);
try {
Thread.sleep(1000);
} catch (Exception e) { }
}
}
}
// 아래는 출력 결과
10
9
8
7
6
5
4
3
입력 값은 XXXXX 입니다.
2
1
<참고>
07-05 쓰레드(Thread)
동작하고 있는 프로그램을 프로세스(Process)라고 한다. 보통 한 개의 프로세스는 한 가지의 일을 하지만, 쓰레드를 이용하면 한 프로세스 내에서 두 가지 또는 그 이상의 일을…
wikidocs.net
http://www.tcpschool.com/java/java_thread_concept
코딩교육 티씨피스쿨
4차산업혁명, 코딩교육, 소프트웨어교육, 코딩기초, SW코딩, 기초코딩부터 자바 파이썬 등
tcpschool.com
https://hyeo-noo.tistory.com/293
[Java] Runnable과 Thread의 차이
자바에서 쓰레드를 구현할 때 2가지 방법이 있다고 한다. 1. Runnable 2. Thread 1. Runnable Runnable은 이름부터 인터페이스의 느낌이 강하다. implements Runnable 을 통해서 Runnable 인터페이스를 구현할 수 있
hyeo-noo.tistory.com
https://wisdom-and-record.tistory.com/48
[Java Study 10주차] 멀티쓰레드 프로그래밍
본 포스팅은 백기선님이 진행하시는 자바 스터디 를 진행하며 혼자 공부하고 이해한 내용을 바탕으로 정리한 글입니다. 오류나 지적 사항이 있다면 댓글로 알려주시면 감사하겠습니다. Thread 클
wisdom-and-record.tistory.com