스레드 (Thread)
일을 비동기적으로 처리하기 위해 사용하는 CPU의 작업 공간의 복제 (분신)
1. 순차 처리의 단점
- 작업을 순차적으로 처리할 경우 : 처리 속도 자체는 제일 빠름
- 서버가 느려지는 대부분의 원인이 파일 입출력 (File I/O)
- 파일 입출력이 걸리면 CPU는 그 시간동안 다른 작업을 진행할 수 없음 (busy waiting)
- 그 동안 들어오는 다른 요청들은 자동으로 버려짐
2. 스레드 (Thread)
- busy waiting으로 인한 자원 낭비를 줄이기 위해 작업을 비동기적으로 처리
- File I/O 접근 시 일반 처리보다 유리 ( Client 입장에서는 waiting이 없음 : UX )
- I/O가 길어질 경우 thread에서도 waiting 발생
- 이를 줄이기 위해 NIO (Non-blocking IO) 사용



package ex19;
public class Th01 {
public static void main(String[] args) {
// CPU > 메인 thread
// Java : 스레드가 전부 종료되어야 종료됨 ( main thread 안에 내부 thread는 동작 중 )
// Thread() { } << target 설정
// main thread의 target은 main 메서드 내부
// new Runnable >> Interface : run() 메서드 1개이므로 람다 표현식 사용 가능 (() -> {})
Thread t1 = new Thread(() -> {
for (int i = 0; i < 10; i++) {
System.out.println("분신1 : " + i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
// OS에게 thread를 생성하여 run() 호출 요청 (callback)
t1.start();
Thread t2 = new Thread(() -> {
for (int i = 0; i < 10; i++) {
System.out.println("분신2 : " + i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
t2.start();
Thread t3 = new Thread(() -> {
for (int i = 0; i < 10; i++) {
System.out.println("분신3 : " + i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
t3.start();
System.out.println("main 스레드 종료");
}
}

3. Thread 활용 1. Listener
- 상품 (product)이 입고되는 것을 thread에서 지켜본 후 확인되면 while 탈출
package ex19;
public class Th02 {
static String product = null;
public static void main(String[] args) {
Thread supp = new Thread(() -> {
try {
Thread.sleep(10000);
product = "바나나깡";
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
});
supp.start();
Thread lis = new Thread(() -> {
while (true) {
try {
Thread.sleep(500);
if (product != null) {
System.out.println("상품이 입고되었습니다. : " + product);
break;
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
lis.start();
}
}

4. Thread 활용 2. Main Thread & Threads
- 메인 스레드의 작동은 다른 스레드와 별개로 발생
- 다른 스레드가 대기중이어도 메인 스레드는 계속 진행
package ex19;
class MyFile {
public void write() {
try {
Thread.sleep(5000);
System.out.println("파일 쓰기 완료");
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
class 화가 {
public void 그림그리기() {
System.out.println("그림 그리기 완료");
}
}
// 화가
public class Th03 {
public static void main(String[] args) {
MyFile myFile = new MyFile();
화가 painter = new 화가();
painter.그림그리기();
// main이 실행 시 5초 기다리고 파일 쓰기 완료 > 그림 그리기 완료
// thread에게 맡기면 그림 그리기 완료 2회 & 따로 5초 기다리고 파일 쓰기 완료
new Thread(() -> myFile.write()).start();
painter.그림그리기();
}
}

5. Thread 활용 3. Thread 안에 상태 저장
- 클래스에 Thread를 상속하여 상태 저장 가능
- run() (타겟)을 재정의해야함 (Override)
package ex19;
import java.util.ArrayList;
import java.util.List;
class MyThread extends Thread {
List<Integer> list;
// 클래스형 thread에 타겟 지정 1. run() Override
// Thread는 기본적으로 상태 저장을 할 수 없음
// Target을 넣을 공간에 상태가 들어갔으므로 run() Override
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("MyThread: " + i);
addList(i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println(getList());
}
// 2. 타겟을 매개변수에 추가
/*
public MyThread(List<Integer> list, Runnable r) {
this.list = list;
super(r);
}
*/
public MyThread(List<Integer> list) {
this.list = list;
}
public void addList(int num) {
list.add(num);
}
public List<Integer> getList() {
return list;
}
}
// 클래스로 스레드 만들기 ( 스레드 별 상태 보관 )
public class Th04 {
public static void main(String[] args) {
MyThread t1 = new MyThread(new ArrayList<>());
t1.start();
}
}

6. Thread 활용 4. ActionListener
- Thread 내부에서 버튼 클릭으로 인한 이벤트 발생 → Thread 정지
package ex19;
import javax.swing.*;
public class Th05 extends JFrame {
private boolean state = true;
private int count = 0;
private int count2 = 0;
private JLabel countLabel;
private JLabel count2Label;
public Th05() {
setTitle("숫자 카운터 프로그램");
setVisible(true);
setSize(300, 200);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// 레이아웃 매니저 설정
setLayout(new BoxLayout(getContentPane(), BoxLayout.Y_AXIS));
// 숫자를 표시할 레이블 생성
countLabel = new JLabel("숫자1: " + count);
count2Label = new JLabel("숫자2: " + count2);
countLabel.setAlignmentX(CENTER_ALIGNMENT);
count2Label.setAlignmentX(CENTER_ALIGNMENT);
add(countLabel);
add(count2Label);
// 멈춤 버튼 생성
JButton increaseButton = new JButton("멈춤");
increaseButton.setAlignmentX(CENTER_ALIGNMENT);
add(increaseButton);
// 버튼에 액션 리스너 추가
increaseButton.addActionListener(e -> {
state = false;
});
new Thread(() -> {
while (state) {
try {
Thread.sleep(1000);
count++;
countLabel.setText("숫자1 : " + count);
} catch (InterruptedException ex) {
throw new RuntimeException(ex);
}
}
}).start();
new Thread(() -> {
while (state) {
try {
Thread.sleep(1000);
count2++;
count2Label.setText("숫자2 : " + count2);
} catch (InterruptedException ex) {
throw new RuntimeException(ex);
}
}
}).start();
}
public static void main(String[] args) {
new Th05();
}
}

7. Thread 활용 5. Thread가 Return해야 할 것
- Thread에서 Main Thread의 상태를 이용하기 위해 변수를 return 해야 할 때
- 타이밍 맞추기 (임시방편)
- 제일 간단하지만 최적의 타이밍을 맞추는 데에 시간이 많이 소요
package ex19;
//콜백 1. 타이밍 맞추기
class Store implements Runnable {
int qty;
@Override
public void run() {
// 통신 -> 다운로드
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
qty = 5;
}
}
/*
스레드에서 받은 데이터를 리턴 받아서 응용하고 싶을때!!
1. 타이밍 맞추기 (임시방편 - 그래도 쓰는 사람 많음)
2. 리스너 (부하가 너무 큼)
3. 콜백 (제일 좋음)
*/
/*
* 스레드를 사용할 때
* 1. 어떤 작업을 동시에 진행해야 할 때
* 2. I/O가 일어날 때 ( 오래 걸리는 (무거운) 연산 )
*/
public class Th06 {
public static void main(String[] args) {
int totalQty = 10;
Store store = new Store();
Thread t1 = new Thread(store);
t1.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("재고수량 :" + (store.qty + totalQty));
}
}
- Thread에서 Main Thread의 상태를 이용하기 위해 변수를 return 해야 할 때
- 리스너 (부하가 너무 큼)
- 계속 스레드의 동작을 지켜봐야 하기 때문에 CPU의 부하 가능성
package ex19;
//콜백 2. 리스너
class Store implements Runnable {
Integer qty;
@Override
public void run() {
// 통신 -> 다운로드
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
qty = 5;
}
}
/*
스레드에서 받은 데이터를 리턴 받아서 응용하고 싶을때!!
1. 타이밍 맞추기 (임시방편 - 그래도 쓰는 사람 많음)
2. 리스너 (부하가 너무 큼)
3. 콜백 (제일 좋음)
*/
/*
* 스레드를 사용할 때
* 1. 어떤 작업을 동시에 진행해야 할 때
* 2. I/O가 일어날 때 ( 오래 걸리는 (무거운) 연산 )
*/
public class Th06 {
public static void main(String[] args) {
int totalQty = 10;
Store store = new Store();
Thread t1 = new Thread(store);
t1.start();
while (true) {
if (store.qty != null) break;
try {
Thread.sleep(10);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println("재고수량 :" + (store.qty + totalQty));
}
}
- Thread에서 Main Thread의 상태를 이용하기 위해 변수를 return 해야 할 때
- 콜백 메서드 (제일 좋음)
- 메서드가 끝나고 함수를 한 번 더 호출
- 리스너도 필요없고, 함수를 부르는 것만으로도 return 가능
package ex19;
//콜백 3. 콜백 메서드
// 1. 콜백 메서드 만들기
interface CallBack {
void 입고(int qty); // 리턴 받고싶은 인수를 만들기
}
// 2. 콜백 메서드 전달
class Store implements Runnable {
CallBack callback;
public Store(CallBack callback) {
this.callback = callback;
}
Integer qty;
@Override
public void run() {
// 통신 -> 다운로드
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
qty = 5;
// 종료 시 콜백 메서드 호출
callback.입고(qty);
}
}
/*
스레드에서 받은 데이터를 리턴 받아서 응용하고 싶을때!!
1. 타이밍 맞추기 (임시방편 - 그래도 쓰는 사람 많음)
2. 리스너 (부하가 너무 큼)
3. 콜백 (제일 좋음)
*/
/*
* 스레드를 사용할 때
* 1. 어떤 작업을 동시에 진행해야 할 때
* 2. I/O가 일어날 때 ( 오래 걸리는 (무거운) 연산 )
*/
public class Th06 {
public static void main(String[] args) {
int totalQty = 10;
Store store = new Store(qty -> {
System.out.println("재고수량 :" + (qty + totalQty));
});
Thread t1 = new Thread(store);
t1.start();
}
}
- 결과는 모두 동일

Share article