[Java] 19. 스레드

김주희's avatar
Feb 26, 2025
[Java] 19. 스레드
 

1. 스레드

 

2. 스레드 기본 사용법

 
  1. 스레드 객체 생성
  1. 스레드 시작 (start())
  1. 타겟 (run()) 만들기
package ex19; // 스레드 기본 (스레드 객체 생성, 스레드 시작, 타켓(run) 만들기) public class Th01 { public static void sub1() { for (int i = 1; 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 = 1; 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 -> 메인 스레드 // 람다식 한 줄이면 중괄호 생략 가능. void 타입이면 return 없어도 중괄호 생략 가능. // run 메서드 = () -> sub1() : Runnable 인터페이스를 통해 run 행위 전달 // 스레드의 제어권 - OS가 가짐 Thread t1 = new Thread(() -> sub1()); t1.start(); new Thread(() -> sub2()).start(); } }
 

3. 스레드 객체의 상태 공유

    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.

     
    package ex19; // 화가 // 스레드 사용시 대기가 없어지는 건 아님, 사용자 UX가 좋아짐 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 화가(); // 비동기 프로그램 -> 좋은 UX 가능 painter.그림그리기(); new Thread(() -> myFile.write()).start(); painter.그림그리기(); } }
     

    5.

      package ex19; import java.util.*; // 1. 상속 받기 // 2. 상태 지정 // 3. run 재정의해서 사용 class MyTread extends Thread { List<Integer> list; @Override public void run() { // run 재정의해서 target 만들기 for (int i = 0; i < 10; i++) { System.out.println("MyTread: " + i); try { Thread.sleep(500); } catch (InterruptedException e) { throw new RuntimeException(e); } } } // ArrayList보다 추상적인 List에 의존 public MyTread(List<Integer> list) { this.list = list; } public List<Integer> getList() { return list; } // 컬렉션은 setter보다 add (한 건씩 추가하니까) public void addList(int num) { list.add(num); } } // 클래스로 스레드 만들기 (스레드별 상태 보관) public class Th04 { public static void main(String[] args) { // MyTread가 List를 has // MyTread가 ArrayList에 의존 MyTread t1 = new MyTread(new ArrayList<>()); t1.start(); } }
       
       

      6. 스레드 제어

       
      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); // 버튼에 액션 리스너 추가 // while 돌면서 특정한 시간을 두고 멈춤 버튼만 지켜보고 있음 // 감시하고 있는데 버튼 클릭하면 내가 뭘 할지 모르겠어 -> 행위({}) 전달 // 추상 메서드 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. 스레드의 데이터를 리턴 받아서 사용하는 경우

      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; } } /* 스레드에서 받은 데이터를 리턴 받아서 응용하고 싶을때!! 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(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println("재고수량 :" + (store.qty + totalQty)); } } // 스레드를 쓰는 이유 // 1. 동시에 쓰고 싶을 때 // 2. I/O가 일어날 때 - hdd 접근해서 꺼내서 가져오고~ 오래 걸린다 // I/O = 오래 걸리는 일과 유사 // 느림 -> context switching, 스레드 객체가 계속 만들어짐
       
       

      2. 리스너

      1. 다른일도 하면 스레드 필요하다. 예제 코드에서는 필요하지 x니까~~
      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; } } /* 스레드에서 받은 데이터를 리턴 받아서 응용하고 싶을때!! 1. 타이밍 맞추기 (임시방편 - 그래도 쓰는 사람 많음) - 통신에서는 불가 / CPU가 그냥 쉰다 2. 리스너 (부하가 너무 큼) - CPU가 쉬지 않고 지켜봄 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) { // store.qty가 int이면 null이 안들어감 -> Integer Wrapper 클래스로 쓰는 것이 낫다 if (store.qty != null) break; // 너무 빠르게 돌리면 안되니까 sleep // 금방 끝나지만 너무 많은 연산이 돌아감 try { Thread.sleep(10); System.out.println("지켜본다"); } catch (InterruptedException e) { throw new RuntimeException(e); } } System.out.println("재고수량 :" + (store.qty + totalQty)); } }
       

      3. 콜백 (중요

      1. ㄹㅇ
        1. package ex19; // 콜백 // (1) 콜백 메서드 만들기 // 인터페이스와 함수 하나 만들어서 리턴 받고 싶은 파라미터를 만들어주면 된다. interface Callback { void 입고(int gty); } class Store implements Runnable { Integer qty; Callback callback; // (2) 리턴이 필요한 곳으로 가서 콜백 메서드 전달받기 public Store(Callback callback) { // 행위 전달 받은 store this.callback = callback; } @Override public void run() { // 통신 -> 다운로드 try { Thread.sleep(100); } catch (InterruptedException e) { throw new RuntimeException(e); } qty = 5; // 끝나면 입고 메서드 호출해주니까 콜백(? callback.입고(qty); // (3) 종료 시 콜백 메서드 호출 } } /* 스레드에서 받은 데이터를 리턴 받아서 응용하고 싶을때!! 1. 타이밍 맞추기 (임시방편 - 그래도 쓰는 사람 많음) - 통신에서는 불가 / CPU가 그냥 쉰다 2. 리스너 (부하가 너무 큼) - CPU가 쉬지 않고 지켜봄 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. 동시에 쓰고 싶을 때 // 2. I/O가 일어날 때 - hdd 접근해서 꺼내서 가져오고~ 오래 걸린다 // I/O = 오래 걸리는 일과 유사 // 느림 -> context switching, 스레드 객체가 계속 만들어짐
       
       
       
       
       
       
       
       
       
       
       
      package ex19; public class Th01 { public static void main(String[] args) { // 모든 스레드는 상태 변수를 공유 -> 동시에 접근하면 교착 상태 //static int num = 5000; // main thread의 target = main method // CPU는 Java 실행될 때 main 스레드를 준다. // 분신 만들기 = 이것만의 새로운 생명 주기를 만드는 것 // main thread가 분신 하나 만들고 start // Runnable의 run 구현 -> 람다식으로 Thread t1 = new Thread(() -> { //t1의 생명 주기 = target // 분신1 for (int i = 0; i < 10; i++) { System.out.println("분신 1: " + i); //System.out.println(num); try { Thread.sleep(500); // 0.5초 } catch (InterruptedException e) { throw new RuntimeException(e); } } }); // 이 스레드만의 메인 메서드가 필요 = target // 메인 스레드가 start를 호출하면 start: os야 남는 스레드 있으면 만들어서 run 좀 호출해줘 (run = call back method t1.start(); // main thread가 분신 하나 만들고 start Thread t2 = new Thread(() -> { // 분신2 for (int i = 0; i < 10; i++) { System.out.println("분신 2: " + i); try { Thread.sleep(500); // 0.5초 } catch (InterruptedException e) { throw new RuntimeException(e); } } }); t2.start(); Thread t3 = new Thread(() -> { // 분신 3 for (int i = 0; i < 10; i++) { System.out.println("분신 3: " + i); try { Thread.sleep(500); // 0.5초 } catch (InterruptedException e) { throw new RuntimeException(e); } } }); t3.start(); System.out.println("메인 스레드 종료"); // 실행 -> context switching으로 t1 target 내용 실행되고 ~ => 메인 스레드가 먼저 종료됨 but java는 종료되지 않음 // 분신이 종료되어야 메인 thread도 종료됨 } } //인터페이스 메서드 전달 역할도 함 -> 람다로
      Share article

      jay0628