⦁ join()
- 지정된 시간동안 특정 쓰레드가 작업하는 것을 기다린다.
void join() // 작업이 모두 끝날때까지
void join(long millis) // 천분의 일초 동안
void join(long millis, int nanos) // 천분의 일초 + 나노초 동안
- 예외처리를 해야 한다(InterruptedException이 발생하면 작업 재개)
class ThreadEx11_1 extends Thread {
public void run() {
for(int i=0; i<300; i++) {
System.out.print(new String("-"));
}
}
}
class ThreadEx11_2 extends Thread {
public void run() {
for (int i=0; i <300; i++) {
System.out.print(new String("|"));
}
}
}
public class Ex13_11 {
static long startTime = 0;
public static void main(String[] args) {
ThreadEx11_1 th1 = new ThreadEx11_1();
ThreadEx11_2 th2 = new ThreadEx11_2();
th1.start();
th2.start();
startTime = System.currentTimeMillis();
try {
th1.join(); // main쓰레드가 th1의 작업이 끝날 때까지 기다린다.
th2.join(); // main쓰레드가 th2의 작업이 끝날 때까지 기다린다.
} catch (InterruptedException e) {}
System.out.print("소요시간:" + (System.currentTimeMillis() - startTime));
}
}
ex) join() 예시 – Garbage Collection
public void run() {
while(true) { // 데몬스레드
try {
Thread.sleep(10*1000); // 10초를 기다린다.
} catch (InterruptedException e) {
System.out.println("Awaken by interrupt().");
}
gc(); // garbage collection을 수행한다.
System.out.println("Garbage Collected. Free Memory :" + freeMemory());
}
}
for( int i=0; i<20; i++) {
requiredMemory = (int)(Math.random() * 10) * 20;
// 필요한 메모리가 사용할 수 있는 양보다 적거나 전체 메모리의 60% 이상 사용했을 경우 gc를 깨운다.
if (gc.freeMemory() < requiredMemory || gc.freeMemory() < gc.totalMemory() * 0.4) {
gc.interrupt(); // 잠자고 있는 쓰레드 gc를 깨운다
try {
gc.join(100); // gc가 작업할 시간을 주는 것
} catch (InterruptedException e) {}
}
gc.usedMemory += requiredMemory;
System.out.println("usedMemory:"+gc.usedMemory);
}
⦁ yield() (static 메소드)
- 남은 시간을 다음 쓰레드에게 양보하고, 자신(현재 쓰레드)은 실행대기 한다.
- yield()와 interrupt()를 적절히 사용하면, 응답성과 효율을 높일 수 있다.
class MyThread implements Runnable {
volatile boolean suspended = false; // 쉽게 바뀌는 변수, cache의 복사본이 아닌 원본을 사용함.
volatile boolean stopped = false;
Thread th;
MyThread(String name) {
th = new Thread(this, name); // Thread(Runnable r, String name)
}
void start() {
th.start();
}
void stop() {
stopped = true;
th.interrupt(); // 자고 있을 경우 깨우기 위해( Thread.sleep(1000) 때문에 )
}
void suspend() {
suspended = true;
th.interrupt(); // 자고 있을 경우 깨우기 위해 ( Thread.sleep(1000) 때문에 )
}
void resume() {
suspended = false;
}
public void run() {
while(!stopped) {
if(!suspended) {
System.out.println(Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch(InterruptedException e) {}
} else {
Thread.yield(); // 양보, OS스케쥴러에게 통보
}
}
}
}
⦁ 쓰레드의 동기화(synchronization)
- 멀티 쓰레드 프로세스에서는 다른 쓰레드의 작업에 영향을 미칠 수 있다.
- 진행중인 작업이 다른 쓰레드에게 간섭받지 않게 하려면 ‘동기화’가 필요
쓰레드의 동기화 : 한 쓰레드가 진행중인 작업을 다른 쓰레드가 간섭하지 못하게 막는 것
- 동기화 하려면 간섭받지 않아야 하는 문장들을 ‘임계 영역’으로 설정
- 임계영역은 락(lock)을 얻은 단 하나의 쓰레드만 출입가능(객체 1개에 락 1개)
⦁ synchronized를 이용한 동기화
- synchronized로 임계영역(lock이 걸리는 영역)을 설정하는 방법 2가지
1. 메소드 전체를 임계 영역으로 지정 ( 임계 영역은 최소화 하는 것이 좋다 )
public synchronized void calcSum() {
//...
}
2. 특정한 영역을 임계 영역으로 지정
synchronized(객체의 참조변수) {
//...
}
ex) synchronized를 이용한 동기화
class Account2 {
private int balance = 1000; // private으로 해야 동기화가 의미가 있다.
public int getBalance() {
return balance;
}
public synchronized void withdraw(int money) { // synchronized로 메소드를 동기화
if(balance >= money) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {}
balance -= money;
}
}
}
class RunnableEx13 implements Runnable {
Account2 acc = new Account2();
public void run() {
while(acc.getBalance() > 0) {
// 100, 200, 300중의 한 값을 임의로 선택해서 출금(withdraw)
int money = (int)(Math.random() * 3 + 1) * 100;
acc.withdraw(money);
System.out.println("balance: " + acc.getBalance());
}
}
}
public class Ex13_13 {
public static void main(String[] args) {
Runnable r = new RunnableEx13();
new Thread(r).start();
new Thread(r).start();
}
}
⦁ wait()과 notify()
- 동기화의 효율을 높이기 위해 wait(), notify()를 사용.
- Object클래스에 정의되어 있으며, 동기화 블록 내에서만 사용할 수 있다.
wait() : 객체의 lock을 풀고 쓰레드를 해당 객체의 waiting pool에 넣는다.
notify() : waiting pool에서 대기중인 쓰레드 중의 하나를 깨운다.
notifyAll() : waiting pool에서 대기중인 모든 쓰레드를 깨운다.
ex)
import java.util.ArrayList;
class Customer2 implements Runnable {
private Table2 table;
private String food;
Customer2(Table2 table, String food) {
this.table = table;
this.food = food;
}
public void run() {
while(true) {
try { Thread.sleep(1000);} catch(InterruptedException e) {}
String name = Thread.currentThread().getName();
table.remove(food);
System.out.println(name + " ate a " + food);
} // while
}
}
class Cook2 implements Runnable {
private Table2 table;
Cook2(Table2 table) { this.table = table; }
public void run() {
while(true) {
int idx = (int)(Math.random()*table.dishNum());
table.add(table.dishNames[idx]);
try { Thread.sleep(1000);} catch(InterruptedException e) {}
} // while
}
}
class Table2 {
String[] dishNames = { "donut","donut","burger" }; // donut의 확률을 높인다.
final int MAX_FOOD = 6;
private ArrayList<String> dishes = new ArrayList<>();
public synchronized void add(String dish) {
while(dishes.size() >= MAX_FOOD) {
String name = Thread.currentThread().getName();
System.out.println(name+" is waiting.");
try {
wait(); // COOK쓰레드를 기다리게 한다.
Thread.sleep(500);
} catch(InterruptedException e) {}
}
dishes.add(dish);
notify(); // 기다리고 있는 CUST를 깨우기 위함.
System.out.println("Dishes:" + dishes.toString());
}
public void remove(String dishName) {
synchronized(this) {
String name = Thread.currentThread().getName();
while(dishes.size()==0) {
System.out.println(name+" is waiting.");
try {
wait(); // CUST쓰레드를 기다리게 한다.
Thread.sleep(500);
} catch(InterruptedException e) {}
}
while(true) {
for(int i=0; i<dishes.size();i++) {
if(dishName.equals(dishes.get(i))) {
dishes.remove(i);
notify(); // 잠자고 있는 COOK을 깨우기 위함
return;
}
} // for문의 끝
try {
System.out.println(name+" is waiting.");
wait(); // 원하는 음식이 없는 CUST쓰레드를 기다리게 한다.
Thread.sleep(500);
} catch(InterruptedException e) {}
} // while(true)
} // synchronized
}
public int dishNum() { return dishNames.length; }
}
class Ex13_15 {
public static void main(String[] args) throws Exception {
Table2 table = new Table2();
new Thread(new Cook2(table), "COOK").start();
new Thread(new Customer2(table, "donut"), "CUST1").start();
new Thread(new Customer2(table, "burger"), "CUST2").start();
Thread.sleep(2000);
System.exit(0);
}
}
출처 - 유튜브 남궁성의 정석코딩 [자바의 정석 - 기초편]
'Java > Java의 정석' 카테고리의 다른 글
220331 Java - Chapter 14. 람다와 스트림(Lambda & Stream) Part 2 (0) | 2022.03.31 |
---|---|
220330 Java - Chapter 14. 람다와 스트림(Lambda & Stream) Part 1 (0) | 2022.03.30 |
220328 Java - Chapter 13. 쓰레드(Thread) (0) | 2022.03.29 |
220325 Java - Chapter.12 지네릭스, 열거형, 애너테이션 Part.3 (0) | 2022.03.25 |
220324 Java - Chapter 12. 지네릭스, 열거형, 애너테이션 Part.2 (0) | 2022.03.24 |