스레드를 사용하는 이유
- 동시에 무언가를 해야 할 때
- I/O 때문에 멍 때리면서 느려지는 걸 해결할 때(오래 걸리는 일)
스레드의 단점
스레드를 사용한다고 속도가 빨라지는 게 아니다.
- context swiching때문에(왔다 ← → 갔다)
- 스레드 객체가 많아지면 느려짐(메모리 공간 차지)
1. 스레드 기본 사용법
1. 람다함수 안에 메서드 넣어 주기
package ex19;
// 스레드 기본(스레드 객체 생성 -> 스레드 시작 -> 타겟(run) 만들기)
public class Th01 {
public static void sub1() {
for (int i = 0; i < 5; i++) {
System.out.println("스레드1 : " + i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
public static void sub2() {
for (int i = 0; i < 5; i++) {
System.out.println("스레드2 : " + i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
public static void main(String[] args) { // CPU -> main 스레드 -> targer main() 메서드
Thread t1 = new Thread(() -> sub1()); // target은 run() 메서드
t1.start();
new Thread(() -> sub2()).start();
}
}
2. 리스너로 스레드 사용
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();
}
}
3. 스레드 비동기적 사용
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.그림그리기();
new Thread(() -> myFile.write()).start();
painter.그림그리기();
}
}
4. 상태를 보유한 스레드 만들기
1. Thread를 상속 받는 나만의 스레드를 만들기
package ex19;
import java.util.ArrayList;
import java.util.List;
class MyThread extends Thread {
List<Integer> list;
public MyThread(List<Integer> list) {
this.list = list;
}
public List<Integer> getList() {
return list;
}
public void addList(int num) {
list.add(num);
}
// Thread를 상속 받은 나만의 Thread를 만들고 run()타겟을 재정의 한다
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("MyThread: " + i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
// 클래스로 스레드 만들기(스레드별 상태 보관)
public class Th04 {
public static void main(String[] args) {
MyThread t1 = new MyThread(new ArrayList<>());
t1.start();
}
}
사용 예제
- Thread를 상속 받는 나만의 스레드를 만든다.
- 상속 받는 스레드 안에 run()메서드를 재정의 한다.
- 나만의 스레드를 생성할 때 상태를 추가 한다.
5. 데몬 스레드 사용
package ex19;
import javax.swing.*;
public class Th05 extends JFrame {
private boolean state = true; // 스레드는 boolean으로 제어한다.
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);
// 버튼에 액션 리스너 추가
// 리스너도 스레드다.
// 데몬 스레드는 슬립이 필요하다
// 슬립이 있어야 중간에 다른 일을 시킬 수 있다.(interrupt)
// 1가지 일을 계속 하면 다른 일을 못시킨다. 200ms정도의 슬립을 주면 그 시간 동안 다른 일을 받아 처리할 수 있다.
// 1. 지켜본다 -> 2. 함수를 실행한다. -> 1. 지켜본다 -> 2. 함수를 실행한다.
// 슬립이 없다면...
// 1. 지켜본다 -> 1. 지켜본다. -> 1. 지켜본다.
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();
}
}
이벤트 리스너
- 모든 이벤트 리스너는 데몬 스레드다.
데몬 스레드
- 데몬 스레드는 슬립이 필요하다.
- 슬립 상태가 있어야 다른 일을 할 타이밍이 생긴다.(Interrupt)
- sleep이 있는 경우
- 1. 지켜본다 -> 2. 함수를 실행한다. -> 1. 지켜본다 -> 2. 함수를 실행한다.
- sleep이 없는 경우
- 1. 지켜본다 -> 1. 지켜본다. -> 1. 지켜본다.
- 슬립 시간은 GUI에서는 200~500ms가 적당하다.
- 슬립 시간은 Server에서는 몇 초 정도가 적당하다.
6. 스레드가 리턴해야 할 때
1. 타이밍 맞추기
- 일 처리가 끝날 타이밍에 내가 맞춰서 메서드를 실행한다.
package ex19;
//콜백
class Store implements Runnable {
int qty;
@Override
public void run() {
// 통신 -> 다운로드
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
qty = 5;
}
}
/*
스레드에서 받은 데이터를 리턴 받아서 응용하고 싶을때!! (UI 이벤트 키보드 & 스크롤)
1. 타이밍 맞추기 (임시방편 - 그래도 쓰는 사람 많음)
2. 리스너 (부하가 너무 큼)
3. 콜백 (제일 좋음)
*/
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(200);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("재고수량 :" + (store.qty + totalQty));
}
}
사용 예시
- 스마트폰 사용 중 입력창이 있을 때 클릭을 하면 키보드가 올라온다.
- 키보드가 올라오면서 입력창을 약간 위로 올리고 싶다.
- 키보드가 올라올 때를 맞춰서 입력창을 올려준다.
2. 리스너 추가
- 상태를 지켜보는 리스너를 만들고 상태가 변할 때까지 기다렸다가, 메서드를 실행한다.
package ex19;
//콜백
class Store implements Runnable {
Integer qty;
@Override
public void run() {
// 통신 -> 다운로드
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
qty = 5;
}
}
/*
스레드에서 받은 데이터를 리턴 받아서 응용하고 싶을때!! (UI 이벤트 키보드 & 스크롤)
1. 타이밍 맞추기 (임시방편 - 그래도 쓰는 사람 많음)
2. 리스너 (부하가 너무 큼)
3. 콜백 (제일 좋음)
*/
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));
}
}
주의
- 메인스레드가 while문을 실행하게 되면 매우 빠른 속도로 돌아간다
- 이때 while문 내부의 변수의 상태 변경을 감지하지 못한다.(외부에서 상태를 변경해줌)
- 때문에 sleep을 넣어 상태 변경을 감지 할 수 있도록 만들어야 한다.
3. 콜백(나중에 너가 실행 시켜줘)
- 리턴을 받을 수 없는 상황에서 사용
- 메인 변수를 내가 처리하는 게 아니라 너가 처리해줘
package ex19;
//콜백
// 1) 콜백 메서드 만들기
// 인터페이스 만들고
// 함수 하나 만들어서
// 리턴받고 싶은 파라미터를 만들어준다.
interface Callback {
void 입고(Integer qty);
}
class Store implements Runnable {
Integer qty;
Callback callback;
// 2) 리턴이 필요한 곳으로 가서 콜백 메서드 전달받기
public Store(Callback callback) {
this.callback = callback;
}
@Override
public void run() {
// 통신 -> 다운로드
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
qty = 5;
callback.입고(qty); // 3) 종료시 콜백 메서드 호출
}
}
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();
}
}
절차
- 인터페이스 만들고 메서드 1개 만듦
- 리턴 받고 싶은 파라미터를 넣는다.
- 콜백 받고 싶은 대상의 생성자 함수에 인수로 콜백 인터페이스를 넣는다.
- 사용할 때 전달하고 싶은 람다함수를 작성한다.
Share article