[JAVA] 19. 스레드

최재원's avatar
Feb 21, 2025
[JAVA] 19. 스레드
스레드를 사용하는 이유
  • 동시에 무언가를 해야 할 때
  • 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(); } }

사용 예제

  1. Thread를 상속 받는 나만의 스레드를 만든다.
  1. 상속 받는 스레드 안에 run()메서드를 재정의 한다.
  1. 나만의 스레드를 생성할 때 상태를 추가 한다.

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(); } }

이벤트 리스너

  1. 모든 이벤트 리스너는 데몬 스레드다.

데몬 스레드

  1. 데몬 스레드는 슬립이 필요하다.
  1. 슬립 상태가 있어야 다른 일을 할 타이밍이 생긴다.(Interrupt)
      • sleep이 있는 경우
        • 1. 지켜본다 -> 2. 함수를 실행한다. -> 1. 지켜본다 -> 2. 함수를 실행한다.
      • sleep이 없는 경우
        • 1. 지켜본다 -> 1. 지켜본다. -> 1. 지켜본다.
  1. 슬립 시간은 GUI에서는 200~500ms가 적당하다.
  1. 슬립 시간은 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)); } }

주의

  1. 메인스레드가 while문을 실행하게 되면 매우 빠른 속도로 돌아간다
  1. 이때 while문 내부의 변수의 상태 변경을 감지하지 못한다.(외부에서 상태를 변경해줌)
  1. 때문에 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. 인터페이스 만들고 메서드 1개 만듦
  1. 리턴 받고 싶은 파라미터를 넣는다.
  1. 콜백 받고 싶은 대상의 생성자 함수에 인수로 콜백 인터페이스를 넣는다.
  1. 사용할 때 전달하고 싶은 람다함수를 작성한다.
Share article

jjack1