14. 멀티스레드

14.1 멀티 스레드 개념

멀티? 여러개의 스레드 하나의 자극시 하나의 스레드 사용함.
스레드 = 실행흐름 스레드 = 코드를 실행해나가는거
멀티스레드 = 여러개 코드를 실행하는것 멀티는 동시에 여러개를 처리할수잇다. = 멀티태스킹
윈도우도 여러개를 처리할수있음.
동시에 작성하느 경우 많음. 하나의 프로세스안에서 여러개의 작업

운영체재는 실행주인 프로그램을 프로세스로 관리한다. 여러개의 프로세스를 띄워놓고 작업을함.
하나의 프로세스에서 여러개의 작업을 하는 멀티태스킹도 가능하다.
여러개의 프로세스 = 멀티프로세스, 하나의 프로세스 안헤서 여러개 실행 = 멀티스레드
일반적으로 오류 한쪽에서 생기면 하나만 고치면됨
그러나 멀티스레드에서 하나에서 예외발생하면 전체 프로세스가 문제가됨.
그래서 스레드 처리를 잘 작성해야함. 안하는거보다 못한결과가 나올 수있다.

그럼 언제 적용하나? 데이터를 분할해서 병렬로 처리하는 곳에서 사용한다.
게임에서 화면갱신, 데이터갱신, 커뮤니케이션 등 동시 작업해야해서 많이씀
서버에서 필수 클라이언트가 다수면 다중클라를 지원하기 위해 서버를 개발할때 필요
웹에서는 딱히 많이 사용하지는 않다. 웹서버 자체 개발에서는 필요 잘 사용은안해도 알아둘 필요가 잇다.
이해잘하면 쉬움

14.2 메인스레드

실행할때 메인스레드가 실행한다. main()메소드를 실행하면서 시작된다.
메인스레드 jvm에서 생성되고 메인스레드 종료시 프로세스 종료됨. (싱글
멀티에선 마지막 하나의 스레드가 종료되어야 프로세스가 종료됨. 한곳에서 에외발생시 프로세스 바로 종료
주체는 메인쓰레드인데 메인스레드가 필요할때 새로운 작업 스레드를 만들어서 실행흐름을 만들어야함.


자바는 스레드도 하나의 객체로 관리 즉 이름이있다.
하나가 무한루프면 프로세스가 끝나지않음.
스레드 종료못시키는 상황도잇음 예를들어 렉걸렷을경우 스레드가 종료할수없는 상태가 되버림 -> 강제종료필요

    System.out.println("시작");
    //실행~흐름 
    //자바는 스레드도 하나의 객체로 관리
    Thread currTread = Thread.currentThread(); // 현재 스레드의 객체를 리턴
    System.out.println(currTread .getName());

    System.out.println("종료");}

14.3 작업 스레드 생성과 실행

메인스레드가 필요할때 작성한다는데 대체 어떻게 생성을할까?
스레드를 생성했다는 것과 실행과는 다르다. 생성후 실행해야 실행하는 것임.
처음부터 작성을 하는 것이 아님. 메인스레드 작업중 동시에 스레드가 작업을 해야한다고 판단할때 개발자가 만들기.
시점에 스레드를 몇개를 만들것인지 생각하기
작업스레드 만드는방법은 직접 스레드로부터 객체만들기 or 스레드의 자식클래스를 만들기

14.3.1 스레드 클래스로 직접생성

java.lang 패키지의 Tread클래스로 부터 작업 스레드의 객체를 직접 생성하면 된다.
thread도 객체임!
Thread thread = new Thread(Runnable target);
thread.start();하면 실행 생성후 실행 필요
생성자안에 들어가는게 뭐냐? -> Runnable이라하는 인터페이스 타입의 객체이다.
Runnable은 run()메소드를 가지고 있음. 실제로 생성자의 객체를 줄때 runnable구현객체를 넣을 수 있다.
매개변수에 인터페이스가 오면? 실제 생성자에 Runnable을 구현한 객체를 매개값으로 제공가능함.
그 객체를 만들기 위해서 명시적으로 클래스를 선언하고 구현클래스를 만들필요가잇다.
class Task implements Runnable(){@Override public void run(){스레드가 실행할 코드} }
Runnable task = new Task();
Thread thread = new Thread(task);
Runnable인터페이스는 추상메소드 단하나 run()메소드만 잇음.

명시적으로 Runnable 구현 클래스로 작성하지 않고 Thread생성자를 호풀할때 익명구현 객체를 매개값으로 사용할 수 잇다.
이방법이 더 많이 사용됨

Thread thread = new Thread(new Runnable(){
@Override
public void run(){스레드가 실행할 코드}
}
} );

Runnable을 일반적으로 작업의 내용을 가지고 있는 객체라고 한다. 스레드가 실행할 수 잇다라 Runnable이라고함.
메인스레드가 start()메소드 호출후 같이 돌아감.

예제 소리를 내면서 출력하는 프로그램 싱글스레드는 소리내고 출력하고 이런식인데 멀티에서는 동시에 가능함.

public class BeepPrintExample {
public static void main(String[] args) {
    /*
    //원래의 작업 메인에서 작업1 실행후 작업2 실행됨
    //작업1 
    Toolkit toolkit = Toolkit.getDefaultToolkit();
    for(int i = 0 ; i <5 ; i++) {
        toolkit.beep();
        try {Thread.sleep(500); //.sleep(밀리세컨드) 시간 만큼 일시정지
        } catch(Exception e) {} //예외처리해야함
    }

    //작업2
    for(int i = 0 ; i <5 ; i++) {
        System.out.println("띵");
        try {Thread.sleep(500); //.sleep(밀리세컨드) 시간 만큼 일시정지
        } catch(Exception e) {}
    }
    */

    // 멀티스레드 설계 작업1 익명구현객체 사용
    //Thread thread = new Thread(new Runnable(){});
    Thread thread = new Thread(new Runnable() {
        @Override
        public void run() {
            Toolkit toolkit = Toolkit.getDefaultToolkit();
            for(int i = 0 ; i <5 ; i++) {
                toolkit.beep();
                try {Thread.sleep(500); //.sleep(밀리세컨드) 시간 만큼 일시정지
                } catch(Exception e) {}
            }
        }});

    thread.start();

    //작업2
    for(int i = 0 ; i <5 ; i++) {
        System.out.println("띵");
        try {Thread.sleep(500); //.sleep(밀리세컨드) 시간 만큼 일시정지
        } catch(Exception e) {}
    }

main이 실행하는 동안 작업스레드를 실행함
api도큐먼트 확인시 java.lang.Thread에 들어있음.

익명자식객체이서도 사용가능하다.
따로 인터페이스와 자식객체 구현한 예제 참조 task

public class Task implements Runnable {
@Override
public void run() {
    for(int i = 0 ; i <5 ; i++) {
        System.out.println("띵");
        try {Thread.sleep(500);
        } catch(Exception e) {}
        }}}

  Runnable task = new Task(); Task가 Runnable을 구현한것이니. 자동타입변환으로 넣기 가능해져서 넣어줌.
Thread thread2 = new Thread(task);
Thread thread2 = new Thread(new Task()); //new Thread(new Runnable()); 인데 구현객체를 넣어준것.
thread2.start();

14.3.2 thread 자식 클래스로 생성

스레드를 상속을 받아서 만들기
자식클래스를 만들고 자식스레드에 스레드 내부의 run메소드를 재정의 한다.
이전에 한것은 Runnable의 추상메소드 run()을 재정의 한거고 이것은
Thread의 run()을 재정의 하는 것이기때문에 구분필요함!

public class PrintThread extends Thread{@Override..}
스레드 객체생성
Tread thread = new PrintThread(); PrintThread thread = new PrintThread();
thread.strat();

항상익명자식객체가 좋은 것은 아님
명시적으로 재정의 해두고 사용하는게 더 재사용성은 좋다.

이 코드 자체가 여기서만 사용되고 다른데서 사용안되면 그냥 익명객체로
아니면 명시적으로 클래스를 만들어 두는게 필요함.
재사용이 좋아짐.

public class PrintThread extends Thread{
//얘는 Thread를 상속해서 만드는거라 스레드 그자체임.
@Override
public void run(){
    for(int i = 0 ; i <5 ; i++) {
        System.out.println("띵");
        try {Thread.sleep(500); //.sleep(밀리세컨드) 시간 만큼 일시정지
        } catch(Exception e) {}
    }}}

public class BeepPrintExample {
public static void main(String[] args) {
    // 멀티스레드 설계 작업1 익명 자식클래스 Thread()부모를 상속하는데 이름이없고 {}선언부가잇음.
    //Thread thread = new Thread() {};
    Thread thread = new Thread() {
        @Override
        public void run(){
        Toolkit toolkit = Toolkit.getDefaultToolkit();
        for(int i = 0 ; i <5 ; i++) {
            toolkit.beep();
            try {Thread.sleep(500); //.sleep(밀리세컨드) 시간 만큼 일시정지
            } catch(Exception e) {}//예외처리해야함
        }
    }
    };

    thread.start();

    /*
    //작업2
    for(int i = 0 ; i <5 ; i++) {
        System.out.println("띵");
        try {Thread.sleep(500); //.sleep(밀리세컨드) 시간 만큼 일시정지
        } catch(Exception e) {}
    }
    */

    //직접 자식객체 만들어서 해보기

    //Thread Thread2 = new PrintThread();
    PrintThread Thread2 = new PrintThread();
    Thread2.start();
    }}

작업내용을 가지고 있는 객체와
스레드 자체를 구분할필요가잇다.

2회독 하면서 생긴 의문점 결국 둘이 뭐가 다른가?
스레드를 생성시키는 다양한 방법은 개발자의 성향과 관련이 있다?
한가지 방법만 알면 다른사람이 작성한 스레드 코드가 다른 방법으로 되어있다면 해석이 어려울수잇다.
runnalbe로 생성해서 스레드 생성자에 대입해 만드는거나 스레드 클래스를 상속해서 만드는 것이나 동일하다.
어떤 것이 더 효율적이다라고 할 수 없다. 라고 카페에 질문답변으로 올라와있음.

14.4 스레드이름

스레드는 이름을 가지고있다.
스레드마다 이름을 가질수있는데 기본적 이름과 필요에따라 이름을 줄수있다.
현재코드가 어떤 스레드를 실행중인지 알고싶을때 사용함.

main / Thread-n의 이름을 가진다.
thread.setName("스레드이름")으로 이름을 지어줄수잇다.
디버깅할때 사용함. 테스트할때 사용함.
정적메소드 currentTread()를 사용해서 현재 스레드의 번지를 리턴하고 그 것의 getName()으로 이름을 얻음.
Thread thread = thread.currentThread();
System.out.println(thread.getName);
스레드이름을 설정할때는 스레드시작전에 설정해야한다.

public class ThreadNameExample {
public static void main(String[] args) {
    Thread mainThread = Thread.currentThread();
    System.out.println(mainThread.getName()+ " 실행");

    for(int i =0; i<3 ;i++ ) {
        Thread threadA = new Thread() {
        @Override
        public void run() {
            System.out.println(getName()+ " 실행");
        }
    };
    threadA.start();
    }

    //스레드 2번째방법으로 만듦
    Thread chatThread = new Thread() {
        @Override
        public void run() {
            System.out.println(getName() + " 실행");
        }
    };
    //스레드이름변경후 실행
    //시작하기전에 이름을 바꾸려면 먼저 바꾸기
    chatThread.setName("chat-thread"); 
    chatThread.start();
    }}

14.5 스레드상태

스레드는 상태를 가진다.
스레드 객체를 생성하고 호출하면 곧바로 스레드가 실행되는 것이 아니라 실행대기상태(RUNNABLE)상태가 된다.
왜? 대기하나 CPU스케쥴링에 따라 다름. CPU를 점유하고 있다가 순번이 되면 실행함.
cpu는 끊임없이 프로세스를 여러개 다 실행하고있음.
스케쥴링 = 수많은 스레드를 다 실행해줘야함.
실행스레드는 run()메소드를 모두 실행하기 전에 스케줄링에 의해 다시 실행대기상태로 돌아갈수잇다.


run안에 실행할게 많을때 시간이 지나면 실행대기로 만들고 다른 스레드 불러와서 돌림
워낙빠르기때문에 계속실행되는거처럼 보이는거임.
주어진시간이1초인데 실행시간이 10초면 하고 쉬고 하고쉬고 반복하는것임.
스레드가 run()끝날때까지 다 실행하는게 아니라 다 못하면 실행대기로 보내고 다시 실행하는 방식
스레드는 실행상태와 실행대기상태를 돌아가면서 실행시킨다.
다 하면 종료상태(TERMINATED)라고 한다.

실행상태에서 일시정지 상태로 가기도 한다.
일시정지상태는 스레드가 실행할수없는 상태를 의미한다.
스레드가 다시 실행상태로 가기 위해서는 일시정지상태에서 실행대기상태로 보내야만한다.
예를들어 sleep()한것이 일시정지상태로 보내는 것임.

스레드 현재상태 얻고싶다면?
리턴타입 Thread.State(스레드안에 중첩된 클래스or인터페이스일경우 이렇게 표현) 메소드 getState()
열거타입으로 저장되어잇음. 열거상수

BLOCKED 스캐너 nextLine()처럼 엔터키누를때까지 무한 일시정지
TIMED_WAITING 일정시간동안 멈춤 sleep()처럼 시간지나면 알아서 풀림
yield() 실행에서 즉시 실행대기로 보냄 어떤 쓰레드가 무의미한 작업을 하는 경우가 잇음.
굳이 지정된 시간만큼 할필요가 없으니 다른 스레드에게 기회를 주기 위해 바로 보내버림.

실행 -> 일시정지 sleep() join() wait()
join() wait()애들은 다른 조건이잇어서 불러와야 실행대기상태로감.
join()은 다른애들이 실행이 종료되어야 실행대기로 가기 가능
일시정지 -> 실행 interrupt() notify() notifyAll()
wait() notify() notifyAll()는 Object에서 가지고 있는 메소드이다. 얘들은 스레드 동기화에서 알아보도록 한다.
그외는 Thread 클래스의 메소드이다.

14.5.1 주어진 시간 동안 일시 정지

스레드의 정적메소드인 sleep()을 사용하고 밀리세컨드 1/1000단위로 시간을 주면된다.
1초동안이면? Thread.sleep(1000);
얘는 InterruptedException 이 발생함. (일반예외) try catch 예외처리 코드를 꼭 작성해줘야함.
보통 예외는 발생안해서 내용은 안넣어도되나 작성은 해둬야함.
대부분은 일시정지중 예외발생안함.

public class SleepExample {
public static void main(String[] args) {
    Toolkit toolkit = Toolkit.getDefaultToolkit();
    for(int i=0; i<10; i++) {
        toolkit.beep();
        try {
            Thread.sleep(3000);
        } catch(InterruptedException e) {
        }}}}

14.5.2 다른스레드의 종료를 기다림

일시정지상태에 잇다가 다른 스레드가 종료되면 일시정지에서 풀려남
스레드a에 넣음 threadB join(); 결합하겟다(참여한다) 일시정지하고 b가 완전하게 할때가지 기다렷다가 a실행하겟다.

public class SumThread extends Thread {
private long sum;

public long getSum(){
    return sum;
}

public void setSum(long sum) {
    this.sum = sum;
}

@Override
public void run() {
    for (int i = 1; i <=100; i++) {
        sum += i;
    }}}

 public class JoinExample {
public static void main(String[] args) {

    SumThread sumThread = new SumThread();
    sumThread.start(); //실행대기상태
    System.out.println("1~100의 합: " + sumThread.getSum() ); //메인에서 바로실행 값: 0

    try {
        sumThread.join();
    } catch (InterruptedException e) {    
    }

    System.out.println("1~100의 합: " + sumThread.getSum() ); //메인실행하다 join으로 연산후 실행 메인다시 실행 값: 5050
}}

join안하고 실행하면 메인이 바로 실행해버려서 다 계산을 못하고 값을 뱉음.
join을 하면 다 실행하고 다시 메인으로 돌아와서 실행해서 값을 뱉음.
바로 출력하면 0임 start는 실행대기 상태이기때문
또한 실행도중에 getSum해버리면 안나옴.
다 작업을 하고 getSum()을 하고 싶다면?
join을 하고 작업을 한후 main실행하기

14.5.3 다른스레드에게 실행양보

스레드가 가끔 무의미한 반복을 하는 경우가 잇다.
while(){if()}에서 안에 if문이 false라 아무것도 실행안하고 반복하면? 무의미함
자신을 실행대기로 만들고 다른 스레드 실행하는게 더 효율적이다.

public class WorkThread extends Thread{
//필드
public boolean work = true;

//생성자
public WorkThread(String name) {
    setName(name); //Thread 상속받았으니 Thread의 setName 사용하는거임.
}

//메소드
@Override
public void run() {
    while(true) {
        if(work) {
            System.out.println(getName() + ": 작업처리");
        } else {
            //false면 yield로 
            Thread.yield();
        }}}}

   public class YieldExample {
public static void main(String[] args) {
    WorkThread workThreadA = new WorkThread("workThreadA");
    WorkThread workThreadB = new WorkThread("workThreadB");
    //생성자public WorkThread(String name) {setName(name);} 객체 생성하는데 이름을 얻고 생성 그이름을 설정
    workThreadA.start();
    workThreadB.start();
    //뭐가먼저시작할지는 몰루

    try {Thread.sleep(5000); 
    //메인스레드 5초 쉬고 false로 변경 5초이전까진 a가 열심히 작업하다가 b로 이동
    } catch (Exception e) {}
    workThreadA.work = false;

    try {Thread.sleep(10000); // 10초뒤에 다시 true로 바꿈 다시a도 나옴.
    } catch (Exception e) {}
    workThreadA.work = true; 
}}

14.6 스레드 동기화

멀티스레드는 하나의 객체를 공유해서 작업할수도 있다.
다른스레드에 의해 객체 내부 데이터가 쉽게 변경될 수 있기때문에 의도했던 것과는 다른 결과가 나올 수 있다.


저장햇던값을 제대로 나오게하려면 다른 스레드가 쉴동안 못바꾸게 해야함.
잠궈버리면 다른 스레드가 사용못한다. 출력후에 잠금이 풀리면 그때서야 사용
동기화된 메소드 하면 락을 해버리고 다른 스레드가 이 동기화메소드를 호출할수없다. 일반메소드는 실행가능.

14.6.1 동기화 메소드 및 블록 선언

public synchronized void method(){//단 하나의 스레드만 실행하는 영역임}
인스턴스와 정적 어디든 붙일수잇다.

동기화블록은
메소드 전체가 아닌 메소드 일부만 잠금을 하는것임. 특정 코드 부분만
public void method(){
다른 내용
synchronized(공유객체){//하나만}
다른내용
}

public class Calculator {
private int memory;

public int getMemory() {
    return memory;
}

//동기화 메소드 만들기
public synchronized void setMemory(int memory) {
    this.memory = memory;
    try {
        Thread.sleep(2000);
    } catch(Exception e) {}
    System.out.println(Thread.currentThread().getName() + ": " + this.memory);
}

//동기화 블록 만들기
public void setMemory2(int memory) {
    //....코드 하나잇고
    System.out.println(Thread.currentThread().getName() + ": hi " );
    synchronized(this) {
    //잠금을 할 객체 synchronized(Calculator) this라 자기자신을 잠금
    this.memory = memory;
    try {
        Thread.sleep(2000);
    } catch(Exception e) {}
    System.out.println(Thread.currentThread().getName() + ": " + this.memory);
    }
    //..또다른 코드 잇을경우 사용
}}

public class User1Thread extends Thread {
private Calculator calculator;

public User1Thread () {
    setName("User1Thread");
}

//공유객체인 Calculator을 받아서 저장
public void setCalculator(Calculator calculator) {
    this.calculator = calculator;
}

@Override
public void run() {
    calculator.setMemory2(100);
}}

public class User2Thread extends Thread {
private Calculator calculator;

public User2Thread () {
    setName("User2Thread");
}

public void setCalculator(Calculator calculator) {
    this.calculator = calculator;
}

@Override
public void run() {

    calculator.setMemory2(50);
}}

public class SyncronizedExample {
public static void main(String[] args) {
    //유저1와 유저2가 사용할 매개값으로 객체를 제공 Calculator calculator
    //Calculator calculator = new Calculator(); 얘가 10번지라하면
    //유저1과 유저2의 필드 calculator =  Calculator() 10번지인얘가 넣어짐
    //this.calculator = calculator;했으니가
    //그리고 Calculator의 setMemory실행
    //스타트하면 run()실행함.

    Calculator calculator = new Calculator();

    User1Thread user1Thread = new User1Thread();
    user1Thread.setCalculator(calculator);
    user1Thread.start();

    User2Thread user2Thread = new User2Thread();
    user2Thread.setCalculator(calculator);
    user2Thread.start();
}}

14.6.2 wait()와 notify()을 이용한 스레드제어

object클래스가 가지고 있어서 모든 객체가 wait(), notify() notifyAll()가지고잇음.
a wait() 하면 일시정지 다른스레드가 notify() 하면 a실행

경우에따라서는 두개의 스레드를 교대로 번갈아가며 실행할 때도있다.
정확한 교대 작업이 필요할 경우 자신의 작업이 끝나면 상대방 스레드를 일시정지에서 풀어주고 자신은 일시정지상태로 만들면된다.
이방법의 핵심은 공유객체에 있다. 공유객체는 두 스레드가 작업할 내용을 각각 동기화 메소드로 정해 놓는다.
한스레드가 작업을 완료하면 notify()메소드를 호출해서 일시정지사앹에 있는 다른 스레드를 실행대기상태로만들고 자신은 두번작업을 하징 않도록 wait()메소드를 호출하여 일시정지상태로만든다.

이 메소드들은 동기화와 관련이잇다. 동기화 메소드, 동기화블록안에서만 사용이 가능하다.
두 스레드가 번갈아 하는 이유 ? 데이터를 보내고 쉬고 데이터를 보내고 쉬고 이렇게 해야함. 읽기전에 보내면 날라가기 때문
데이터를 주고받을때 사용

public class WorkObject {
public synchronized void methodA() {
    Thread thread = Thread.currentThread();
    System.out.println(thread.getName() + ": methodA 작업 실행");
    notify(); //다른스레드를 실행대기 상태로 만듬
    try {
        wait();
    } catch(Exception e) {}
}

public synchronized void methodB() {
    Thread thread = Thread.currentThread();
    System.out.println(thread.getName() + ": methodB 작업 실행");
    notify(); //다른스레드를 실행대기 상태로 만듬
    try {
        wait();
    } catch(Exception e) {}
}

// 공유객체에서 메소드a가 작업1 b가 작업2라고 생각하기.}

public class ThreadA extends Thread{
private WorkObject workObject;

public ThreadA(WorkObject workObject) { // 이번엔 생성자에서 공유객체를받음
    setName("ThreadA");
    this.workObject = workObject;
}

@Override
public void run() {
    for(int i = 0; i <10 ; i++) {
        workObject.methodA();
        //메소드 a 작업1 실행
    }}}

public class ThreadB extends Thread{
private WorkObject workObject;

public ThreadB(WorkObject workObject) {
    setName("ThreadB");
    this.workObject = workObject;
}

@Override
public void run() {
    for(int i = 0; i <10 ; i++) {
        workObject.methodB();
        //메소드 b 작업2 실행
    }}}

 public class WaitNotifyExample {
public static void main(String[] args) {
    WorkObject workObject = new WorkObject();

    ThreadA threadA = new ThreadA(workObject);
    ThreadB threadB = new ThreadB(workObject);

    threadA.start();
    threadB.start();
}}

공유객체!!!! 잘알아두자!!!이부분이 헷각ㄹ렷는데 2회독에서 다시읽으니 이해됨!!!

14.7 스레드 안전 종료

스레드 실행흐름인데 갑자기 멈추면? 예를들어 예외발생해서 파일 작업중 갑자기 중단하면 파일이 깨질수도잇다.
데이터 손실이 발생할수잇다. 가급적이면 스레드 안전종료하기 위해 설계필요
원래는 run()모두실행되면 제대로 멈춤
강제종료하기 위해 Thread에 stop()메소드가 있지만 안전한 종료가 아니기때문에 deprecated(더이상 사용하지않음)로 되어잇다.
리소스들을 안전하게 종료할 기회를 주고 run()을 종료시켜라

14.7.1 조건이용

스레드가 While문으로 반복 실행할 경우 조건을 이용해서 run()메소드의 종료를 유도할 수있다. stop필드 조건에 따라서 run()메소드의 종료를 유도한다.

  public class PrintThread extends Thread{
private boolean stop;

public void setStop(boolean stop) {
    this.stop = stop;
}

@Override
public void run() {
    //stop이 false가 아니면 실행 현재 stop false임(boolean기본값이 false이기때문)
    while(!stop) { 
        System.out.println("실행 중");
    }
    System.out.println("리소스 정리");
    System.out.println("실행 종료");
}}

 public class SafeStopExample {
public static void main(String[] args) {
    PrintThread printThread = new PrintThread();
    printThread.start();

    try {
        Thread.sleep(3000);
    } catch(Exception e) {}

    //메인 3초후에 stop을 true로 변경
    printThread.setStop(true); // PrintThread 종료시키기위해 stop필드값변경
}}

14.7.2 interrupt()메소드 이용

interrupt()메소드는 스레드가 일시정지에 잇을때 InterruptedException 예외를 발생시키는 역할을 함.
이것을 이용해서 run()메소드를 정상종료시키는 것이다.
sleep, join, wait에서 사용
따라서 일시정지를 방해하는 것이 interrupt()메소드이다.
3초쉬고있는데 이 일시정지중에 꺼버리고싶다. join도 쉬다가 인터럽트 시켜버려서 종료시키고싶음.

public class PrintThread extends Thread{
@Override
public void run() {
    try {
        while (true) {
            System.out.println("실행 중");
            Thread.sleep(1);
        }
    } catch (Exception e) {
        System.out.println(e.getMessage());
    }
    System.out.println("리소스 정리");
    System.out.println("실행 종료");
}}

public class InterruptedExample {
public static void main(String[] args) {
    Thread thread = new PrintThread();

    thread.start();

    //너무빨리끝나서 메인 3초하고 인터럽트
    try {
        Thread.sleep(3000);
    } catch (InterruptedException e) {}

    thread.interrupt(); //메인이쉴때발생하는게아니라 PrintThread가 0.001초 쉬는데 이때 예외발생시켜버림
}}

interrupt()는 실행을 방해하는게 아니라 일시정지상태를 방해하는거임
일시정지 만드는 코드가 필요함.
그런데 매번 0.0001초씩 쉬기때문에 너무 비효율적임.
그럼 이렇게 안하고 쉬게만드는방법이 뭘까?
일시정지 ㅁ나들지않고 interrupt()메소드호출 여부를 아는 방법이 있다.
바로 Thread의 interrupted()와 isinterrupted() 메소드가 있다.
interrupt()메소드 호출 여부를 리턴하는 것이다. 전자는 정적메소드이고 후자는 인스턴스 메소드이다.
둘다 boolean값인 True false 리턴함.

if(Thread.interrupted()) {
    break;
}

interrupt() 호출되엇냐? 맞으면 true 그래서 break;실행하고 빠져나감

public class InterruptedExample {
public static void main(String[] args) {
    Thread thread = new PrintThread();

    thread.start();

    try {
        Thread.sleep(1000);
    } catch (InterruptedException e) {}

    thread.interrupt();}}

14.8 데몬스레드

데몬스레드는 주 스레드의 작업을 돕는 보조적인 역할을 수행하는 스레드이다.
주스레드 = 주인이 되는 스레드 그 스레드가 데몬스레드를 만들수잇다.
주스레드 종료시 데몬스레드도 종료한다.
워드프로세스 자동저장, 미디어플레이어의 동영상 및 음악재생, JVM의 가비지 컬렉터 등이 이와 같은 종류라 할 수 있다.
주 스레드가 데몬이 될 스레드의 setDaemon(true)를 호출하면 된다.
2회독에서의 발견 save()가 원래는 무한히 실행되야하는데 catch문에 break;를 안넣고 while문에 braek를 처 넣어놔서 실제대로 안됨.

public class AutoSaveThread extends Thread{
public void save() {
    System.out.println("작업 내용을 저장함");
}

@Override
public void run() {
    while(true) {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            break;
        }
        save();
    }}}

  public class DaemonExample {
public static void main(String[] args) {
    AutoSaveThread autoSaveThread = new AutoSaveThread();
    //이걸실행하는 것이 메인스레드임 그래서 메인스레드에  AutoSaveThread()를 데몬스레드로 만들어버림
    //데몬스레드로 안만들면 main이 종료되어도 계속해서 스레드가 실행됨
    autoSaveThread.setDaemon(true); // AutoSaveThread()를 데몬 스레드로 만듬
    autoSaveThread.start();

    try {
        Thread.sleep(3000);
    } catch(Exception e) {}

    System.out.println("메인스레드 종료");
}}

14.9 스레드풀

매우 매우 중요!!!!!!!
그때 그때 만들어서 병렬적으로 처리가능하지만 미리만들어놓는게 좋다.
왜? 스레드를 만드는 시간도 필요하다. for 100번반복하면서 스레드를 계속만들면? 스레드만들고실행만들고실행을 반복하니 비효율적임.
그래서 스레드 생성시간을 줄여주기 위해 스레드풀을 만든다.
스레드풀은 작업 처리에 사용되는 스레드를 제한 된 개수 만큼 정해놓고 작업 큐에 들어오는 작업들을 스레드가 하나씩 맡아 처리하는 것이다.
작업처리가 끝난 스레드는 다시 작업 큐에서 새로운 작업을 가져와 처리한다. 스레드를 재사용하게 되는거임.
이렇게하면 작업량이 증가해도 스레드 개수가 늘어나지 않아 애플리켄이션의 성능이 급격히 저하되지 않는다.
예를들어 for문 100번 스레드 생성 -> 100개 만드는게 아니라 일끝난거 재사용해서 10개만으로 해결할수도 잇다. 스레드 낭비 막음.


풀이란? 여러개의 객체를 관리한다라고 이해하면 좋음 큐? 먼저 들어온놈을 먼저 해결하는 것 ->나중에 컬렉션에서 FIFO하면서 등장
스레드 개수는 경험에 따라 개수를 관리

14.9.1 스레드풀생성

java.util.concurrent 패키지에서 ExecutorService인터페이스와 Executors클래스를 사용한다.
Executors의 정적메소드를 이용하면 간단하게 스레드풀인 ExecutorService 구현객체를 만들 수 있다.
newCachedThreadPool() 초기수 0 코어수 0 최대수 integer.MAX_VALUE(최대 21억갠데 불가능)
작업량에 따라 자동으로 만들어지는데 작업량이 많아지면 스레드갯수제한이 없으니 스레드가 많아져서 그다지 사용x
메모리 사용량 예측힘듦

newFixedThreadPool(int nThreads) 초기수0 코어수 생성된수 최대수 nThreads
()사이의 주어진 수만큼만 만듦.
단점 10개스레드만들엇음녀 11번째는 완료될때까지 기다려야함. 작업대기 시간이 길어진다라는 단점이 있음.

초기수는 스레드풀이 생성될때 기본적으로 생성되는 스레드수
코어수는 스레드가 증가된 후 사용되지 않는 스레드를 제거할때 최소한 풀에서 유지하는 스레드수
최대수는 증가되는 스레드의 한도 수이다.

작업처리를 하다가 놀고있으면 비효율적임 이걸 어떻게 종료시킬건가?
newCachedThreadPool()는 작업 개수가 많아지면 새스레드를 생성시켜 작업을 처리, 60초동안 스레드가 아무작업을 안하면 제거된다.
newFixedThreadPool(int nThreads)는 정해진 수만큼의 최대로 만들고 생성된 스레드는 제거안함.

위 두 메소드를 사용하지 않고 직접 ThreadPoolExecutor로 스레드풀을 생성할수도있다.
구체적으로 스레드풀을 정의할때 사용함.

ExecutorService threadPool = new ThreadPoolExecutor(
        3,                                 //코어 스레드 개수
        100,                             //최대 스레드 개수
        120L,                             //놀고있는시간 120초동안 놀고있으면 종료
        TimeUnit.SECONDS,                 //놀고있는시간단위 
        new SynchronousQueue<Runnable>() //작업큐    
        //동기화된 큐; 작업큐에 스레드를 넣을때 하나씩만 넣겟다.
        // 작업큐에 작업을 넣는거도 스레드임 한번에 하나의 스레드에만 작업을 넣겟다
        );

14.9.2 스레드풀 종료

스레드풀의 스레드는 기본적으로 데몬 스레드가 아니기때문에 바로 종료 안됨.
즉 메인이 종료가 되도 스레드 종료가 바로 되는게아님.
스레드풀의 모든 스레드를 종료하려면 ExecutorSercive의 메소드를 실행해야한다.
void shutdown() 현재 처리중인 작업뿐만아니라 작업 큐에 대기하고 있는 모든 작업을 처리한뒤 스레드풀 종료
List<Runnable> shutdownNow() 현재 작업 처리중인 스레드를 interrupt해서 작업을 중지시키고 스레드풀을 종료한다.
리턴값은 작업큐에 있는 미처리된 작업의 목록이다.
작업큐에있는 미처리된 값들이 List에 들어감 이걸 가지고 나중에 작업하면됨
가능하면 shutdown()을 사용하는게 좋다.

new SynchronousQueue<Runnable>()이부분에서
Runnable이 뭐냐? Runnable=작업내용을 가지고잇는객체, 한마디로 작업객체 스레드가 처리해야할 작업내용을 가지고있는 객체
Thread thread = new Thread(Runnable target) 맨처음에 이렇게 배웠을때 Runnable이있엇음
스레드가 처리할것을 앞으로 Runnable 구현 객체로 만들어야함.
위의 사진에서 네모 하나하나가 Runnable구현객체인것임.
위의 SynchronousQueue<Runnable>() 이녀석 제네릭 타입임. 결국 작업객체의 종류가 들어와야함.
List<Runnable> shutdownNow() Runnable들을 담는거임.

14.9.3 작업생성과 처리요청

작업생성:Runnable객체를 생성하는거 처리요청 :Runnable객체를 작업큐에 넣는행위
어떤스레드에서 처리하도록 요청하는 것은 스레드풀이 알아서 결정함.
우리는 작업생성만 만들면됨
하나의 작업은Runnable또는 Callable 구현 클래스로 표현한다.
Runnable객체와 Callable객체의 차이점은 작업처리후 리턴값이 있느냐 없느냐의 차이이다.
없으면 전자 있으면 후자 Callable은 throws Exception 필요
그러나 대부분의 작업은 리턴값이 없어서 전자를 사용한다.
interface Callable<V> 은 리턴타입V을가진 call(); 메소드를 가지고잇다.

void execute(Runnable command) / Runnable을 작업큐에 저장 작업처리결과를 리턴하지 않음.
Future<T> submit(Callable<T> task) /Callable(구현객체)를 작업큐에 저장 작업처리결과를 받을수잇도록 Future라는 제네릭타입을 리턴 <T>는 결과타입
리턴타입이 미래인이유? 바로 실행이아니라 작업큐에 넣으라 이기때문에 아직 처리가 안된거임. 앞에서부터 처리임.즉 미래에 처리되기때문이다.
Runnable객체와 Callable객체가 ExecutorService의 작업큐에 들어가면 처리할 스레드가 잇는지 보고 없으면 스래드를 새로 생성시킨다.
run() 또는 call()메소드를 실행하면서 작업을 처리한다.

  public class RunnableExecuteExample {
public static void main(String[] args) {
    String[][] mails = new String[1000][3]; // 2차원함수
    for(int i = 0 ; i< mails.length; i++ ) { // 1000번돌림
        mails[i][0] = "admin@my.com";
        mails[i][1] = "member" + i + "@my.com";
        mails[i][2] = "신상품 입고";
    }
    //ExecutorService 생성
    ExecutorService executorService = Executors.newFixedThreadPool(5);
    //1000개의 메일 5개 스레드로 하겟다.

    //이메일을 보내는 작업 생성 및 처리모듈
    for (int i =0; i<1000; i++ ) {
        final int idx = i; //final 값저장 변경불가 final 안써도됨
        //final을 넣은이유? 로컬변수를 로컬클래스안에서 사용하면(구현객체도 같음) final특성을 가짐 
        //밑에 run()메소드의 익명객체에서 선언되면 그러면 i값이 증가가 안됨.
        //여기선 익명객체 이니가 미리 for문에서 정의해두기
        executorService.execute(new Runnable() {
        @Override
        public void run() {
            Thread thread = Thread.currentThread(); //현재 작업하는 스레드의 참조 얻기
            String from = mails[idx][0];
            String to = mails[idx][1];
            String content = mails[idx][2];
            System.out.println("["+ thread.getName() + "]" + from +"==>" + to + ": " + content);
            //execute로 스레드에 넣어서 천번 돌림.
            }
        });
    }
    //ExecutorService종료
    executorService.shutdown();
}}

public class CallableSubmitExample {
public static void main(String[] args) {
    ////ExecutorService 생성
    ExecutorService executorService = Executors.newFixedThreadPool(5);

    //계산 작업 생성 및 처리요청
    for(int i = 1 ; i<=100 ; i++) {
        final int idx = i;
        //스레드풀에 submit로 넣기 자연수 넣기이니 정수인 Integer사용
        Future<Integer> future = executorService.submit(new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                int sum = 0;
                for (int i= 1 ; i<= idx;i++ ) { //idx만큼반복하는거
                    sum += i;
                }
                Thread thread = Thread.currentThread();
                System.out.println("[" + thread.getName() + "] 1~}" + idx + "합 계산");
                return sum; //call()메소드는 리턴값이 잇어서 리턴값을 해줘야함.
            }
        });
    //submit의 리턴값이 future임 future.get() 퓨처 리턴값을 얻어라 
    try {
        int result = future.get(); 
        //Callabe의 call() 메소드가 리턴한 값을 얻고 다시 result에 리턴 
        //get()이 바로 결과값 리턴안하고 일시정지함.
        //리턴값 다시얻으면 for문이니 밑에 출력하고 다시 위로가서 리턴값 만들고 리턴값 다시줌 
        //이러면 리턴값 다시얻엇으니 일시정지가 풀리고 출력 반복
        System.out.println("\t리턴값: " + result);
    } catch (Exception e) {
        e.printStackTrace();
    }

}
//ExecutorService 종료
executorService.shutdown();
}}

2022.12.17 리뷰

18일에 적는 17일 리뷰
15장은 18일 19일 양일에 걸쳐서 진행하려고 한다.
일요일, 국취제 상담이슈
정말 시간이 빨리간다
중요한 것은 꺾이지 않는 마음.

+ Recent posts