JAVA
[Java] Thread 쓰레드
soyeonisgood
2023. 2. 13. 07:59
Thread
- Process
- 실행 중인 프로그램
- 프로그램을 실핼하면 OS로부터 실행에 필요한 자원(메모리)을 할당받아 프로세스가 됨
- 프로그램을 수행하는 데 필요한 자원(데이터,메모리), 쓰레드로 구성됨
- Thread
- 프로세스의 자원을 이용해서 실제로 작업을 수행
- Multi Thread
- 하나의 프로세스 내에서 여러 쓰레드가 동시에 작업 수행
- 장점
- CPU 사용률 향상
- 자원을 효율적으로 사용
- 사용자에 대한 응답성 향상
- 작업이 분리되어 코드 간결
- 단점
- 동기화 (Synchronization)
- 교착상태 (deadlock)
- 두 쓰레드가 자원을 점유한 상태에서 서로 상대편이 점유한 자원을 사용하려고 기다리느라 진행이 멈춰있는 상태
- 서버 프로그램
- 여러 사용자에게 서비스해줌 → 멀티쓰레드로 작성하는 것 필수!
- 하나의 서버 프로세스가 여러 개의 쓰레드를 생성해서 쓰레드와 사용자의 요청이 일대일로 처리되도록 프로그래밍 해야함.
- Thread 구현 방법
Thread 클래스 상속- 다른 클래스 상속 불가 (다중 상속 X)
- Runnable 인터페이스 구현
- run() 만 정의되어 있는 간단한 인터페이스
- 추상 메소드인 run()의 몸통을 만들어줘야함.
- Thread 를 구현한다는 것은, 쓰레드를 통해 작업하고자 하는 내용으로 run() 를 작성하는 것!
// Thread 클래스 상속
class MyThread extends Thread {
public void run() { } // run() 오버라이딩
}
// Runnable 인터페이스 구현
class MyThread implements Runnable {
public void run() { } // run() 구현
}
package pratice.threadEx;
public class ThreadEx1 {
public static void main(String[] args) {
ThreadEx1_1 t1 = new ThreadEx1_1();
Runnable r = new ThreadEx1_2();
Thread t2 = new Thread(r);
t1.start();
t2.start();
}
}
class ThreadEx1_1 extends Thread {
public void run() {
for (int i=0; i<5; i++) {
System.out.println(getName()); // Thread의 getName() 호출.
}
}
}
class ThreadEx1_2 implements Runnable {
public void run() {
for (int i=0; i<5; i++){
// Thread.currentThread() // 현재 실행 중인 Thread 반환.
System.out.println(Thread.currentThread().getName());
}
}
}
- start() : 쓰레드의 실행
- 쓰레드는 start() 를 호출하여 실행
- 일단 실행 대기 상태에서 있다가, 자신의 차례가 되어야 실행됨.
- 실행 순서는 OS의 스케쥴러가 작성한 스케쥴에 의해 결정됨.
- 종료된 쓰레드는 다시 실행할 수 없다.
- start() 는 한 번만 호출됨
- 종료된 쓰레드의 작업을 다시 수행하려면, 새로운 쓰레드를 생성한 후 start() 를 호출해야함.
- 두 번 이상 호출하면 IllegalThreadStateException 발생
- start() 와 run()
- 쓰레드 실행은 run()이 아닌, start()
- run() 호출은 생성된 쓰레드를 실행시키는 것이 아닌, 단순히 클래스에 선언된 메소드 호출
- strat()는 호출스택(start stack)을 생성한 다음 run() 을 호출해서, 생성한 호출스택에 run()이 첫 번째로 올라가게 한다!
- 호출스택의 가장 위에 있는 메소드가 현재 실행 중인 메소드. 다른 메소드들은 대기상태.
- run() 수행이 종료된 쓰레드는 호출스택이 비워지고 이 쓰레드가 사용하던 호출스택은 사라진다
- 싱글코어 싱글쓰레드 vs 싱글코어 멀티쓰레드
- 싱글코어 싱글쓰레드
- 단순 CPU만 사용하는 계산 작업 효율적
- 싱글코어 멀티쓰레드
- 2개 이상의 쓰레드가 번갈아 가면서 작업 수행 = 동시에 여러 작업 처리되는 것 처럼 느낌
- 쓰레드간의 작업 전환(context switching)에 시간 소요
- 다음 실행에 대한 정보를 저장하고 읽어 오는 시간
- 싱글코어 싱글쓰레드
- 싱글코어 멀티쓰레드 vs 멀티코어 멀티쓰레드
- 싱글코어 싱글쓰레드
- 멀티쓰레드여도 하나의 코어가 번갈아가면서 수행하므로, 두 작업이 절대 겹치지않음 (병렬)
- 멀티코어 멀티쓰레드
- 두 작업을 수행하면, 동시에 두 쓰레드 수행(병행)됨 → 겹치는 부분이 발생하므로 자원(console)을 놓고 두 쓰레드가 경쟁
- 두 쓰레드가 서로 다른 자원을 사용하는 작업에 효율적
- ex) 사용자로부터 데이터 입력 받는 작업, 네트워크로 파일 주고받는 작업, 프린터 파일 출력 등 외부기기의 입출력 필요로 하는 경우
- 싱글코어 싱글쓰레드
package pratice.threadEx;
import javax.swing.*;
// Multi Thread Ex
public class Thread7 {
public static void main(String[] args) {
ThreadEx7_1 th1 = new ThreadEx7_1();
th1.start();
String input = JOptionPane.showInputDialog("값을 입력하세요.");
System.out.println("입력한 값: "+input);
}
}
class ThreadEx7_1 extends Thread {
public void run() {
for(int i=10; i>0; i--){
System.out.println(i);
try {
sleep(1000);
} catch (Exception e) {
}
}
}
}
- 쓰레드의 우선순위
- 작업의 중요도에 따라 쓰레드의 우선순위를 서로 다르게 지정하여 특정 쓰레드가 더 많은 작업 시간을 갖도록 할 수 있음
- 우선순위 범위: 1~10
- 우선순위는 생성한 쓰레드로부터 상속 받음
- main메소드의 우선 순위는 자동으로 5
- 쓰레드 실행 전(start())에만 우선순위 변경 가능
- 멀티코어에선, 쓰레드의 우선순위에 따른 차이가 거의 없음.
- 쓰레드에 높은 우선순위를 주어도 더 많은 실행시간, 기회를 갖게 될 것이라고 기대할 수 없음
- → 작업에 우선수누이를 두어 PriorityQueue에 저장하고, 우선순위가 높은 작업이 먼저 처리되도록 하는 것이 나을 수 있다!
void setPriority(int newPriority)
int getPriority()
- 데몬 쓰레드 (Demon Thread)
- 일반 쓰레드의 작업을 돕는 보조적인 역할을 수행하는 쓰레드
- ex) 가비지 컬렉터, 워드프로세서의 자동저장, 화면자동갱신 …
- 무한루프와 조건문을 이용해서 실행 후 대기하고 있다가 특정 조건이 만족되면 작업을 수행하고 다시 대기하도록 작성
- 데몬쓰레드가 생성한 쓰레드는 자동으로 데몬쓰레드가 됨
- setDeamon()은 반드시 start() 호출 전에 실행되어야함.
- 일반 쓰레드의 작업을 돕는 보조적인 역할을 수행하는 쓰레드
boolean isDeamon() // 데몬쓰레드인지 확인
void setDeamon(boolean on) // on=true로 지정하면 데몬쓰레드 또는 사용자 쓰레드로 변경.
- sleep(): 일정시간동안 쓰레드 멈춤
- sleep()에 의해 일시정지 상태가 된 쓰레드는 지정된 시간이 다 되거나 interrupt()가 호출되면(=Interrupt Exception 발생) 잠에서 깨어나 실행대기 상태가 됨
- sleep() 호출 시에는 꼭 try~catch문으로 예외 처리 필수 !!!
- try~catch문을 포함하는 새로운 메소드를 만들어서 사용하면 간편
void delay(long millis) { try { Thread.sleep(millis); } catch (InterruptedException e) { } }
- interrupt() 와 interrupted(): 쓰레드의 작업 취소
- 진행 중인 쓰레드의 작업이 끝나기 전에 취소시킬 때 사용
- ex) 큰 파일 다운로드 시 너무 오래 걸리면 다운로드 취소
- 쓰레드에게 작업을 멈추라고 요청
- 강제 종료X. 단지 요청만.
- 쓰레드의 interrupted(인스턴스 변수) 상태를 바꾸는 것일 뿐.
- interrupted(): interrupt() 가 호출되었는지 알려줌. 호출X-false, 호출O: true
- while 문 조건식 “!” 유의
- 쓰레드가 sleep(), wait(), join()에 의해 일시정지 상태(WAITING) 에 있을 때, 해당 쓰레드에 대해 interrupt()를 호출하면 해당 함수들에서 Interrupted Exception이 발생하고 쓰레드는 실행대기 상태(RUNNABLE)로 바뀐다.
- 즉, 멈춰있던 쓰레드를 깨워서 실행가능한 상태로 만드는 것.
- Thread.sleep(1000) 에서 InterruptException이 발생했으므로, 쓰레드가 sleep()으로 잠시 멈춰있을 때 intterupt()를 호출하면 InterruptException이 발생되고 interrupted상태는 false로 자동으로 초기화됨
package pratice.threadEx; import javax.swing.*; public class Thread14 { public static void main(String[] args) { ThreadEx14_1 th1 = new ThreadEx14_1(); th1.start(); String input = JOptionPane.showInputDialog("값 입력"); System.out.println("입력한 값: " + input); th1.interrupt(); System.out.println("inInterrupted(): " + th1.isInterrupted()); } } class ThreadEx14_1 extends Thread { public void run() { int i = 10; while (i != 0 && !isInterrupted()) { System.out.println(i--); try { Thread.sleep(1000); } catch (InterruptedException e) { interrupt(); // false로 자동 초기화 } } System.out.println("카운트 종료!!"); } }
- yield(): 다른 쓰레드에게 주어진 실행시간 양보
- 진행 중인 쓰레드의 작업이 끝나기 전에 취소시킬 때 사용
package pratice.threadEx;
public class ThreadEx20 {
public static void main(String[] args) {
ThreadEx20_1 gc = new ThreadEx20_1();
gc.setDaemon(true); // 쓰레드gc를 데몬쓰레드로 설정
gc.start();
int requiredMemory = 0;
for (int i = 0; i < 20; i++) {
requiredMemory = (int) (Math.random() * 10) * 20;
// 필요 메모리가 사용할 수 있는 양보다 크거나, 전체 메모리의 60% 이상을 사용한 경우
if (gc.freeMemory() < requiredMemory
|| gc.freeMemory() < gc.totalMemory() * 0.4) {
gc.interrupt(); // 잠자고 있는 쓰레드gc 깨우기
try {
gc.join(100);
} catch (InterruptedException e) { }
}
gc.usedMemory += requiredMemory;
System.out.println("usedMemory: " + gc.usedMemory);
}
}
}
class ThreadEx20_1 extends Thread {
final static int MAX_MEMORY = 1000;
int usedMemory = 0;
public void run() {
while (true) {
try {
Thread.sleep(10 * 1000);
} catch (InterruptedException e) {
System.out.println("Awaken by interrupt()");
}
gc();
System.out.println("Garbage Collected. Free Memory :" + freeMemory());
}
}
public void gc() {
usedMemory -= 300;
if (usedMemory < 0) {
usedMemory = 0;
}
}
public int totalMemory() {
return MAX_MEMORY;
}
public int freeMemory() {
return MAX_MEMORY - usedMemory;
}
}
참고: 자바의 정석, 남궁 성