2022.11.29-1 JAVA 멀티 스레드
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 = new Thread(Runnable target); thread.start();하면 실행 생성후 실행 필요
생성자안에 들어가는게 뭐냐? -> 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()메소드 호출후 같이 돌아감.
예제 소리를 내면서 출력하는 프로그램 싱글스레드는 소리내고 출력하고 이런식인데 멀티에서는 동시에 가능함.
main이 실행하는 동안 작업스레드를 실행함
api도큐먼트 확인시 java.lang.Thread에 들어있음.
package ch14.sec03.exam01;
import java.awt.Toolkit;
public class BeepPrintExample {
public static void main(String[] args) {
//작업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) {}
}14.3.2 thread 자식 클래스로 생성
자식클래스를 만들고 자식스레드에 스레드 내부의 run메소드를 재정의 한다.
이전에 한것은 Runnable의 run()을 재정의 한거고 이것음 Thread의 run()을 재정의 하는 것이기때문에 구분필요함!
익명자식객체이서도 사용가능하다.
package ch14.sec03.exam02;
import java.awt.Toolkit;
public class BeepPrintExample {
public static void main(String[] args) {
// 멀티스레드 설계 작업1 익명 자식클래스 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) {}
}항상익명자식객체가 좋은 것은 아님
명시적으로 재정의 해두고 사용하는게 더 재사용성은 좋다.
package ch14.sec03.exam01;
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) {}
}
}}작업2를 직접 따로 인터페이스 만들어서 해보기
Runnable을 구현한거는 작업내용을 가지고 잇는 클래스이지 스레드가 아님!
이 코드 자체가 여기서만 사용되고 다른데서 사용안되면 그냥 익명객체로
아니면 명시적으로 클래스를 만들어 두는게 필요함.
재사용이 좋아짐.
//Task task = new Task();
//Runnable task = new Task(); Task가 Runnable을 구현한것이니.
//Thread thread2 = new Thread(task);
Thread thread2 = new Thread(new Task());
thread2.start();package ch14.sec03.exam02;
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) {}
}}} //직접 자식객체 만들어서 해보기
//Thread Thread2 = new PrintThread();
PrintThread Thread2 = new PrintThread();
Thread2.start();14.4 스레드 이름
스레드는 이름을 가지고 있다. 기본적인 이름 과 필요에따라 이름 주기 가능
현재 코드가 어떤 스레드를 실행하는지 확인할대 사용함.
main / Thread-n의 이름을 가진다.
thread.setName("스레드이름")으로 이름을 지어줄수잇다.
디버깅할때 사용함.
정적메소드 currentTread()를 사용해서 현재 스레드의 번지를 리턴하고 그 것의 getName()으로 이름을 얻음.
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();
}
Thread chatThread = new Thread() {
@Override
public void run() {
System.out.println(getName() + " 실행");
}
};
//스레드이름변경후 실행
//시작하기전에 이름을 바꾸려면 먼저 바꾸기
chatThread.setName("chat-thread");
chatThread.start();
14.5 스레드 상태
스레드는 상태를 가진다.
스레드 객체를 생성하고 호출하면 곧바로 스레드가 실행되는 것이 아니라 실행대기상태(RUNNABLE)상태가 된다.
왜? 대기하나 CPU스케쥴링에 따라 다름. CPU를 점유하고 있다가 순번이 되면 실행함.
스케쥬링 = 수많은 스레드를 다 실행해줘야함.
실행스레드는 run()메소드를 모두 실행하기 전에 스케줄링에 의해 다시 실행대기상태로 돌아갈수잇다.
run안에 실행할게 많을때 시간이 지나면 실행대기로 만들고 다른 스레드 불러와서 돌림
워낙빠르기때문에 계속실행되는거처럼 보이는거임.
사실 cpu가 실행하는게 아니라 cpu안의 코어에서 실행시키는거임 연산을 하는 표면적인 단위
코어 하나당 하나의 스레드를 실행하는데
논리프로세서는 코어는 하나인데 두개로 나누어서 사용
스레드가 run()끝날때까지 다 실해한느게 아니라 다 못하면 실행대기로 보내고 다시 실행하는 방식
스레드는 실행상태와 실행대기상태를 돌아가면서 실행시킨다.
다 하면 종료상태(TERMINATED)라고 한다.
실행상태에서 일시정지 상태로 가기도 한다. 일시정지상태는 스레드가 실행할수없는 상태를 의미한다.
스레드가 다시 실행상태로 가기 위해서는 일시정지상태에서 실행대기상태록 ㅏ야하만하다.
예를들어 sleep()한것이 일시정지상태로 보내는 것임.
스레드 현재상태 얻고싶담면? Thread.State(스레드안에 중첩된 클래스or인터페이스일경우 이렇게 표현) getState()
열거타입으로 저장되어잇음.
BLOCKED 스캐너 nextLine()처럼 엔터키누를때까지 무한 일시정지
TIMED_WAITING 일정시간동안 멈춤 sleep()처럼
yield() 실행에서 즉시 실행대기로 보냄 어떤 쓰레드가 무의미한 작업을 하는 경우가 잇음.
굳이 지정된 시간만크 할필요가 없으니 다른 스레드에게 기회를 주기 위해 바로 보내버림.
실행 -> 일시정지 sleep() join() wait()
join() wait()애들은 다른 조건이잇어서 불러와야 실행대기상태로감.
일시정지 -> 실행 interrupt() notify() notifyAll()
wait() notify() notifyAll()는 Object에서 가지고 있는 메소드이다. 얘ㅔ들은 스레드 동기화에서 알아보도록 한다.
14.5.1 주어진 시간동안 일시정지
스레드의 정적메소드인 sleep()을 사용하고 밀리세컨드 1/1000단위로 시간을 주면된다.
얘는 InterruptedException 이 발생함. (일반예외) try catch 예외처리 코드를 꼭 작성해줘야함.
보통 예외는 발생안해서 내용은 안넣어도되나 작성은 해둬야함. 대부분은 일시정지주 예외발생안함.
14.5.2 다른스레드의 종료를 기다림
일시정지상태에 잇다가 다른 스레드가 종료되면 일시정지에서 풀려남
스레드a에 넣음 threadB join(); 결합하겟다 일시정지하고 b가 완전하게 할때가지 기다렷다가 a실행하겟다.
package ch14.sec05.exam02;
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;
}
}}package ch14.sec05.exam02;
public class JoinExample {
public static void main(String[] args) {
SumThread sumThread = new SumThread();
sumThread.start();
System.out.println("1~100의 합: " + sumThread.getSum() ); //바로실행
try {
sumThread.join();
} catch (InterruptedException e) {
}
System.out.println("1~100의 합: " + sumThread.getSum() ); //join으로 연산후 실행
}
}
바로 출력하면 0임 start는 실행대기 상태이기때문
또한 실행도중에 getSum해버리면 안나옴.
다 작업을 하고 getSum()을 하고 싶다면?
join을 하고 작업을 한후 main실행하기
14.5.3 다른스레드에게 실행양보
스레드가 가끔 무의미한 반복을 하는 경우가 잇다.
while(){if()}에서 안에 if문이 false라 아무것도 실해안하고 반복하면? 무의미함
자신을 실행대기로 만들고 다른 스레드 실행하는게 더 효율적이다.
package ch14.sec05.exam03;
public class WorkThread extends Thread{
//필드
public boolean work = true;
//생성자
public WorkThread(String name) {
setName(name);
}
//메소드
@Override
public void run() {
while(true) {
if(work) {
System.out.println(getName() + ": 작업처리");
} else {
Thread.yield();
}
}
}}package ch14.sec05.exam03;
public class YieldExample {
public static void main(String[] args) {
WorkThread workThreadA = new WorkThread("workThreadA");
WorkThread workThreadB = new WorkThread("workThreadB");
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(공유객체){//하나만} 여러개 가능}
package ch14.sec06.exam01;
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) {
//....코드 하나잇고
synchronized(this) {
this.memory = memory;
try {
Thread.sleep(2000);
} catch(Exception e) {}
System.out.println(Thread.currentThread().getName() + ": " + this.memory);
}
//..또다른 코드 잇을경우 사용
}} package ch14.sec06.exam01;
public class User1Thread extends Thread {
private Calculator calculator;
public User1Thread () {
setName("User1Thread");
}
public void setCalculator(Calculator calculator) {
this.calculator = calculator;
}
@Override
public void run() {
calculator.setMemory(100);
}}package ch14.sec06.exam01;
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.setMemory(50);
}}package ch14.sec06.exam01;
public class SyncronizedExample {
public static void main(String[] args) {
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() 인 이유 다른 거를 실행대기로 바꿔두고 자신이 일시정지로 간다.
두스레드가 번갈아 하는 경우 ? 데이터를 보내고 쉬고 데이터를 보내고 쉬고 이렇게 해야함. 읽기전에 보내면 날라가기 때문
package ch14.sec06.exam02;
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라고 생각하기.
}package ch14.sec06.exam02;
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 실행
}}} package ch14.sec06.exam02;
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 실행
}}}package ch14.sec06.exam02;
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();
}}
14.7 스레드 안전종료
스레드 실행흐름인데 갑자기 멈추면? 예를들어 예외발생해서 파일 작업중 갑자기 중단하면 파일이 깨질수도잇다.
데이터 손실이 발생할수잇다. 가급적이면 스레드 안전종료하기 위해 설계필요
run()모두실행되면 제대로 멈춤
강제종료하기 위해 Thread에 stop()메소드가 있지만 안전한 종료가 아니기때문에 deprecated(더이상 사용하지않음)로 되어잇다.
안전하게 종료하는 기회를 주고 빨리 종료하도록 하는것.
조건 이용방법과 interrupt()이있다.
14.7.1 조건이용
stop변수값을가지고 run의 종료를 유도
package ch14.sec07.exam01;
public class PrintThread extends Thread{
private boolean stop;
public void setStop(boolean stop) {
this.stop = stop;
}
@Override
public void run() {
while(!stop) {
System.out.println("실행 중");
}
System.out.println("리소스 정리");
System.out.println("실행 종료");
}}package ch14.sec07.exam01;
public class SafeStopExample {
public static void main(String[] args) {
PrintThread printThread = new PrintThread();
printThread.start();
try {
Thread.sleep(3000);
} catch(Exception e) {}
printThread.setStop(true); // PrintThread 종료시키기위해 stop필드값변경
}}14.7.2 interrupt()메소드 사용
interrupt()메소드는 스레드가 일시정지에 잇을때 InterruptedException 예외를 발생시키는 역할을 함.
이것을 이용해서 run()메소드를 정상종료시키는 것이다.
sleep, join, wait에서 사용
따라서 일시정지를 방해하는 것이 interrupt()메소드이다.
package ch14.sec07.exam02;
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("실행 종료");
}} package ch14.sec07.exam02;
public class InterruptedExample {
public static void main(String[] args) {
Thread thread = new PrintThread();
thread.start();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {}
thread.interrupt();
}}interrupted() 와 isInterrupted()
스레드가 실행대기 실행상태일때는 interrupt()메소드가 호출되어도 예외가 발생하지 않는다.
그러나 스레드가 어떤 이유로 일시정지과 되면 예외가 발생한다. 그래서 ㅉ랍은시간이나마 일시정지를 위해 Thread.sleep(1);했다.
일시정지를 만들지 않고도 interrupt()메소드 호출여부를 알수잇는방법이 잇는데 Thread의 interrupted()와 isInterrupted()메소드는
interrupt() 메소드 호출 여부를 리턴한다.
interrupted()는 정적메소드고 isInterrupted() 인스턴스 메소드이다.
package ch14.sec07.exam03;
public class PrintThread extends Thread{
@Override
public void run() {
while (true) {
System.out.println("실행 중");
if(Thread.interrupted()) {
break;
}
}
System.out.println("리소스 정리");
System.out.println("실행 종료");
}}package ch14.sec07.exam03;
public class InterruptedExample {
public static void main(String[] args) {
Thread thread = new PrintThread();
thread.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {}
thread.interrupt();
}} 
2022.11.29 리뷰
딱히 쓸말이 없다
중요한 것은 꺽이지 않는 마음