inblog logo
|
jjack1
    Java

    [JAVA] 19. 스레드

    최재원's avatar
    최재원
    Feb 21, 2025
    [JAVA] 19. 스레드
    Contents
    1. 스레드 기본 사용법1. 람다함수 안에 메서드 넣어 주기2. 리스너로 스레드 사용3. 스레드 비동기적 사용4. 상태를 보유한 스레드 만들기1. Thread를 상속 받는 나만의 스레드를 만들기5. 데몬 스레드 사용6. 스레드가 리턴해야 할 때1. 타이밍 맞추기2. 리스너 추가3. 콜백(나중에 너가 실행 시켜줘)
    ❗
    스레드를 사용하는 이유
    • 동시에 무언가를 해야 할 때
    • 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

    RSS·Powered by Inblog