남궁성의 정석코딩 : 자바의 정석 기초편(2020최신) ch13
사용자 스레드가 0개일 때 프로그램 종료
스레드 도대체 무엇이길래 나를 힘들게 하나?(JAVA)
run() 오버라이드run() 구현start()를 메인스레드의 호출스택에 올림start() 호출run()을 올리고 호출class GoraniExt extends Thread {
@Override
public void run() {
System.out.println(getName());
System.out.println("RUN : I extended Thread");
}
}
class GoraniImpl implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread());
System.out.println("RUN : I implemented Runnable");
}
}
public class Test {
public static void main(String[] args) {
GoraniExt ge = new GoraniExt();
ge.start();
// Thread-0
// RUN : I extended Thread
GoraniImpl gi = new GoraniImpl();
Thread giThread = new Thread(gi);
giThread.start();
// Thread[Thread-1,5,main]
// RUN : I implemented Runnable
}
}
join() 메소드로 생성한 스레드의 종료를 기다릴 수 있음
public class Test {
public static void main(String[] args) {
GoraniExt ge = new GoraniExt();
ge.start();
Thread giThread = new Thread(new GoraniImpl());
giThread.start();
try {
ge.join();
giThread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Main : End");
// ge, giThread가 종료된 다음 "Main : End"출력
}
}
입출력시 작업을 중단하는 것
한 스레드 내에서 외부장치와 입출력을 수행하는 동안 아무 작업도 하지 않음
아래 코드는 다음과 같이 동작
run() 실행, 그 동시에 사용자 입력 받음run() 실행class GoraniExt extends Thread {
@Override
public void run() {
System.out.println(getName() + " : start");
for(int i=0; i<5; i++)
System.out.println(getName() + " " + i);
System.out.println(getName() + " : end");
}
}
public class Test {
public static void main(String[] args) throws InterruptedException {
GoraniExt t1 = new GoraniExt();
GoraniExt t2 = new GoraniExt();
t1.start();
Scanner sc = new Scanner(System.in);
System.out.println(sc.next());
t2.start();
// join
t1.join();
t2.join();
System.out.println("main thread : end");
}
}
JVM에서 스레드 우선순위를 1~10까지 설정가능
숫자가 높은(우선순위가 높은) 스레드에 많은 작업시간 할당
메인스레드 그룹은 아래와 같이 설정되어있음
변경과 조회 가능
JVM단에서 우선순위를 설정했다고 하여도 실제로 OS단에서 스케쥴링이 어떻게 될지는 알 수 없음
아래 코드는 더 낮은 우선순위 스레드가 먼저 끝날 수도 있음을 보임
class Gorani extends Thread {
private char ch;
public Gorani(char ch) { this.ch = ch; }
@Override
public void run() {
System.out.println("\n" + getName() + " : start");
for(int i=0; i<100; i++)
System.out.print(ch);
System.out.println("\n" + getName() + " : end");
}
}
public class Test {
public static void main(String[] args) throws InterruptedException {
Gorani t1 = new Gorani('O');
Gorani t2 = new Gorani('X');
t1.setPriority(1);
t2.setPriority(10);
System.out.println(t1.getPriority() + " " + t2.getPriority()); // 1 10
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("main thread : end");
}
// < t2 (Thread-1)가 빨리 종료되는 예시 >
// Thread-0 : start
// Thread-1 : start
// XOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXOOOOOOOOXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXOOOOOOOXXXXXXXXXXXXXXXXXXOOOOOOOOOOOOOOOOOOOO
// Thread-1 : end
// OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO
// Thread-0 : end
// main thread : end
// < t1 (Thread-0)가 발리 종료되는 예시 >
// Thread-0 : start
// Thread-1 : start
// XOOOOOOOOOOOOOOOOOOOOXXXOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
// Thread-0 : end
// XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
// Thread-1 : end
// main thread : end
}
모든 스레드는 반드시 하나의 스레드 그룹에 속함 (지정하지 않는 경우 메인스레드 그룹)
Thread생성자의 첫 번째 인수는 스레드 그룹 식별자
어떤 스레드가 속한 그룹은 ThreadGroup getThreadGroup()으로 알 수 있음
특징
생성자
ThreadGroup(String name)ThreadGroup(ThreadGroup parent, String name)대표적 메소드 (대체로 그룹내 스레드에 일괄적으로 적용되는 동작)
| 메소드 | 동작 |
|---|---|
| int activeCount() | 그룹 내 활성상태 스레드 개수 반환 |
| int activeGroupCount() | 그룹 내 활성상태 그룹 개수 반환 |
| void destroy() | 해당 그룹과 하위 그룹들을 삭제(비어있어야 동작) |
| int enumerate(Thread[], boolean) | 그룹 내 스레드를 배열에 추가. bool값이 true이면 하위그룹의 스레드도 추가. 추가한 스레드 개수 반환 |
| int enumerate(ThreadGroup[], boolean) | 그룹 내 그룹을 배열에 추가. bool값이 true이면 하위그룹의 그룹도 추가. 추가한 그룹 개수 반환 |
| void uncaughtException(Thread, Throwable) | 그룹 내 스레드가 JVM에 에러를 던졌을 때 호출 |
| String getName() | 그룹 이름 반환 |
| ThreadGroup getParent() | 부모 그룹 반환 |
| void interupt() | 그룹 내 스레드 인터럽트 |
| void setDaemon(boolean) | 그룹을 데몬 스레드 그룹으로 설정(true)/해제(false) |
일반 스레드의 보조역할
일반스레드가 모두 종료되면 데몬스레드도 자동종료됨
일반적으로 무한루프를 돌고 특정 조건이 만족되면 작업을 수행하도록 작성
while(true){
Thread.sleep(1000);
if(exp){
// .. Do something
}
}
아래 과정에 따라 데몬스레드 설정 및 실행
1. 스레드 선언, 생성
2. 데몬 스레드로 지정 setDaemon(true)
3. 실행 start()
class Gorani extends Thread {
public boolean condition = false;
DaemonGorani daemon = null;
public Gorani() {
daemon = new DaemonGorani();
daemon.setDaemon(true);
}
@Override
public void run() {
daemon.start();
for(int i=0; i<10; i++) {
try { Thread.sleep(500); }
catch (InterruptedException e) { e.printStackTrace(); }
System.out.println(i);
if(i>3)
condition = true;
}
}
class DaemonGorani extends Thread {
@Override
public void run() {
while(true) {
try { Thread.sleep(500); }
catch (InterruptedException e) { e.printStackTrace(); }
if(condition)
System.out.println("GORANI IS CRYING");
}
}
}
}
public class Test {
public static void main(String[] args) throws InterruptedException {
Gorani t = new Gorani();
t.start();
t.join();
System.out.println("main thread : end");
}
// 0
// 1
// 2
// 3
// 4
// 5
// GORANI IS CRYING
// 6
// GORANI IS CRYING
// 7
// GORANI IS CRYING
// 8
// GORANI IS CRYING
// GORANI IS CRYING
// 9
// main thread : end
}
start()가 호출되기 전의 상태class Gorani extends Thread {
@Override
public void run() {
try {
for(int i=0; i<1000; i++) {
Thread.sleep(500, 0);
System.out.println("i : "+i+" / interrupted : "+this.isInterrupted());
}
} catch (InterruptedException e) {
System.out.println("[Gorani] : I am interrupted");
}
}
}
public class Test {
public static void main(String[] args) throws InterruptedException {
Gorani t = new Gorani();
t.start();
Scanner sc = new Scanner(System.in);
System.out.println(sc.next());
t.interrupt();
t.join();
System.out.println(t.isInterrupted());
System.out.println("main thread : end");
}
// i : 0 / interrupted : false
// i : 1 / interrupted : false
// i : 2 / interrupted : false
// i : 3 / interrupted : false
// asdasdsadas
// asdasdsadas
// [Gorani] : I am interrupted
// false
// main thread : end
}
세 메소드 모두 교착상태를 유발할 가능성이 있어 deprecated됨
Java Thread Primitive Deprecation
class Gorani implements Runnable {
@Override
public void run() {
for(int i=0; i<100; i++) {
try {
Thread.sleep(400);
System.out.println(Thread.currentThread());
} catch (InterruptedException e) { }
}
}
}
public class Test {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new Gorani());
Thread t2 = new Thread(new Gorani());
t1.start();
t2.start();
Thread.sleep(1000); t1.suspend();
Thread.sleep(1000); t1.resume();
Thread.sleep(1000); t2.stop();
Thread.sleep(2000); t1.stop();
t1.join();
t2.join();
System.out.println("main thread : end");
}
//Thread[Thread-1,5,main]
//Thread[Thread-0,5,main]
//Thread[Thread-1,5,main]
//Thread[Thread-0,5,main]
//Thread[Thread-1,5,main]
//Thread[Thread-1,5,main]
//Thread[Thread-0,5,main]
//Thread[Thread-1,5,main]
//Thread[Thread-0,5,main]
//Thread[Thread-1,5,main]
//Thread[Thread-0,5,main]
//Thread[Thread-1,5,main]
//Thread[Thread-0,5,main]
//Thread[Thread-0,5,main]
//Thread[Thread-0,5,main]
//Thread[Thread-0,5,main]
//Thread[Thread-0,5,main]
//main thread : end
}
sleep()과 유사)class Gorani implements Runnable {
@Override
public void run() {
for(int i=0; i<15; i++) {
try {
if(i<10) {
Thread.sleep(200);
System.out.print('#');
} else {
Thread.yield();
}
} catch (InterruptedException e) { }
}
}
}
public class Test {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(new Gorani());
t.start();
t.join(1000);
System.out.println("Main thread waited for 1 sec");
t.join();
System.out.println("Thread t is terminated");
t = new Thread(new Gorani());
t.start();
}
// ####Main thread waited for 1 sec
// ######Thread t is terminated
}
임계영역 설정을 통해 한 스레드가 진행중인 작업이 다른 스레드에 의해 방해되지 않도록 하는 것
임계영역은 lock을 얻은 하나의 스레드만 진입 가능
블록 또는 메소드를 임계영역으로 지정하는 키워드
class Gorani implements Runnable {
private static int n = 1000;
private boolean mode; // false : 빼기, true : 더하기
private int reps;
public Gorani(boolean m, int r) {
mode = m;
reps = r;
}
public static int getN() {
synchronized (this) {
return n;
}
}
@Override
public synchronized void run() {
if(mode) {
for(int i=0; i<reps; i++)
n += 1;
} else {
for(int i=0; i<reps; i++)
n -= 1;
}
}
}
public class Test {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new Gorani(false, 1000)); // 1씩 1000번 빼기
Thread t2 = new Thread(new Gorani(true, 500)); // 1씩 500번 더하기
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("Thread t is terminated");
System.out.println("n : " + Gorani.getN());
// 동기화 하면 500, 동기화가 없으면 결과 알 수 없음
}
}
synchronized 키워드만 사용했을 때 한 스레드가 lock을 계속 쥐고있어 무한루프가 발생할 수 있음
이를 해결하기 위해 추가로 wait, notify를 사용
예시 시나리오
CropsNum을 가지고, 최대로 자랄 수 있는 작물은 10개임makeCrop() 행위를 함. 작물이 10개이면 대기.eatCrop() 행위를 함. 작물이 0개이면 대기.class Field {
private int cropsNum = 5;
public synchronized int getNum () { return cropsNum; }
public synchronized void eatCrop(String name) {
while (cropsNum <= 0) {
System.out.println(name + " : There is no crop");
try {
wait();
Thread.sleep(500);
} catch (InterruptedException e) { }
}
cropsNum--;
notify();
}
public synchronized void makeCrop(String name) {
while (cropsNum >= 10) {
System.out.println(name + " : There is no room for new crops");
try {
wait();
} catch (InterruptedException e) { }
}
cropsNum++;
notify();
}
}
class Farmer implements Runnable{
Field field = null;
String name;
public Farmer(Field field, String name) {
this.field = field;
this.name = name;
}
@Override
public void run() {
while(true) {
try { Thread.sleep(150); } catch (InterruptedException e) { }
field.makeCrop(name);
System.out.println(name + " : grow grow / cropsNum : " + field.getNum());
}
}
}
class Gorani implements Runnable {
Field field = null;
String name;
public Gorani(Field field, String name) {
this.field = field;
this.name = name;
}
@Override
public void run() {
while(true) {
try { Thread.sleep(200); } catch (InterruptedException e) { }
field.eatCrop(name);
System.out.println(name + " : nyam nyam. / cropsNum : " + field.getNum());
}
}
}
public class Test {
public static void main(String[] args) throws InterruptedException {
Field field = new Field();
Thread farmer = new Thread(new Farmer(field, "farmer"));
Thread gorani1 = new Thread(new Gorani(field, "Wonjin"));
Thread gorani2 = new Thread(new Gorani(field, "Java"));
farmer.start();
gorani1.start();
gorani2.start();
farmer.join();
gorani1.join();
gorani2.join();
System.out.println("All child threads are terminated");
}
// farmer : grow grow / cropsNum : 6
// Wonjin : nyam nyam. / cropsNum : 4
// Java : nyam nyam. / cropsNum : 4
// farmer : grow grow / cropsNum : 5
// Wonjin : nyam nyam. / cropsNum : 4
// Java : nyam nyam. / cropsNum : 3
// farmer : grow grow / cropsNum : 4
// farmer : grow grow / cropsNum : 4
// Wonjin : nyam nyam. / cropsNum : 4
// Java : nyam nyam. / cropsNum : 3
// farmer : grow grow / cropsNum : 4
// Wonjin : nyam nyam. / cropsNum : 3
// Java : nyam nyam. / cropsNum : 2
// farmer : grow grow / cropsNum : 3
// Wonjin : nyam nyam. / cropsNum : 2
// Java : nyam nyam. / cropsNum : 1
// farmer : grow grow / cropsNum : 2
// Wonjin : nyam nyam. / cropsNum : 1
// Java : nyam nyam. / cropsNum : 1
// farmer : grow grow / cropsNum : 1
// farmer : grow grow / cropsNum : 2
// Wonjin : nyam nyam. / cropsNum : 1
// Java : nyam nyam. / cropsNum : 0
// farmer : grow grow / cropsNum : 1
// Wonjin : nyam nyam. / cropsNum : 0
// Java : There is no crop
// Java : nyam nyam. / cropsNum : 0
// Wonjin : There is no crop
// farmer : grow grow / cropsNum : 0
// farmer : grow grow / cropsNum : 1
// Wonjin : nyam nyam. / cropsNum : 0
// Java : nyam nyam. / cropsNum : 0
// farmer : grow grow / cropsNum : 1
// farmer : grow grow / cropsNum : 1
// Wonjin : nyam nyam. / cropsNum : 0
// Java : There is no crop
// farmer : grow grow / cropsNum : 1
// Java : nyam nyam. / cropsNum : 1
// .................. 계속
}
윗 절에서 사용하는 notify()는 특정한 스레드가 아닌 랜덤한 스레드를 깨움.
따라서 불필요하게 깨어나는 스레드가 생겨 성능상 손해가 있을 수 있음
e.g. 작물이 없어서 notify()했는데 farmer가 아니라 gorani가 깨어나는 경우
이를 해결하기 위해 waiting pool을 분리하고, 특정 조건에 맞는 스레드를 깨우는 방법을 사용
Hello, Dev World! : Lock 클래스 / Condtion 클래스
그릿 속의 해빗 : 멀티쓰레드(Multi Thread)의 동기화 2 - Lock과 Condition을 이용한 동기화