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);
	}
}

출처 - 유튜브 남궁성의 정석코딩 [자바의 정석 - 기초편] 

+ Recent posts