[JAVA] 70. 스레드

문정준's avatar
Feb 20, 2025
[JAVA] 70. 스레드
스레드 (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) 사용
 
notion image
notion image
notion image
 
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 스레드 종료"); } }
notion image
 

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

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.그림그리기(); } }
notion image

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

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

7. Thread 활용 5. Thread가 Return해야 할 것

  • Thread에서 Main Thread의 상태를 이용하기 위해 변수를 return 해야 할 때
      1. 타이밍 맞추기 (임시방편)
          • 제일 간단하지만 최적의 타이밍을 맞추는 데에 시간이 많이 소요
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 해야 할 때
      1. 리스너 (부하가 너무 큼)
          • 계속 스레드의 동작을 지켜봐야 하기 때문에 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 해야 할 때
      1. 콜백 메서드 (제일 좋음)
          • 메서드가 끝나고 함수를 한 번 더 호출
          • 리스너도 필요없고, 함수를 부르는 것만으로도 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(); } }
  • 결과는 모두 동일
notion image
 
Share article

sxias