자바의 정석 <기초편> 📌chapter13. 쓰레드

모깅·2023년 2월 24일
0

📖 01. 프로세스(process)와 쓰레드(thread)

-> 프로그램을 실행하면 OS로부터 실행에 필요한 자원(메모리)를 할당받아 프로세스가 된다.

-> 하나의 새로운 프로세스를 생성하는 것보다 하나의 새로운 쓰레드를 생성하는 것이 더 적은 비용이 든다.


📖 02. 멀티쓰레딩의 장단점


📖 03. 쓰레드의 구현과 실행


📖 04. 쓰레드의 구현과 실행 예제

<예제 13-1 >

✍️ 입력

class Ex13_1 {
	public static void main(String args[]) {
		ThreadEx1_1 t1 = new ThreadEx1_1();

		Runnable r = new ThreadEx1_2();
		Thread t2 = new Thread(r);	  // 생성자 Thread(Runnable target)

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

💻 출력
Thread-0
Thread-0
Thread-0
Thread-1
Thread-1
Thread-1
Thread-1
Thread-1
Thread-0
Thread-0

  • 조상인 Thread의 getName()을 호출
  • Thread.currentThread() - 현재 실행중인 Thread를 반환한다.

📖 05. 쓰레드의 실행 - start()

  • start()가 호출되었다고 해서 바로 실행되는 것은 아니다. 쓰레드 대기상태!
  • 하나의 쓰레드에 대해 start()가 한 번만 호출될 수 있다.
    -> 쓰레드의 작업을 한 번 더 수행해야 한다면 쓰레드를 다시 생성해서 start()를 호출해야 한다.

cf) 쓰레드의 실행순서는 OS의 스케줄러가 작성한 스케줄에 의해 결정된다.


📖 06. start()와 run()

  1. main메서드에서 쓰레드의 start()를 호출한다.
  2. start()는 새로운 쓰레드를 생성하고, 쓰레드가 작업하는데 사용될 호출스택을 생성한다,
  3. 새로 생성된 호출스택에 run()이 호출되어, 쓰레드가 독립된 공간에서 작업을 수행한다.
  4. 이제는 호출스택이 2개이므로 스케줄러가 정한 순서에 의해서 번갈아 가면서 실행된다.
  • 모든 쓰레드는 독립적인 작업을 수행하기 위해 자신만의 호출스택을 필요로 하기 때문에, 새로운 쓰레드를 생성하고 실행시킬 때마다 새로운 호출스택이 생성되고 쓰레드가 종료되면 작업에 사용된 호출스택은 소멸된다.

📖 07. main쓰레드

  • 작업을 수행하려면 최소한 하나의 쓰레드가 필요하다. -> main메서드도 쓰레드이다.
  • main메서드가 종료되더라도 쓰레드가 남아있다면 프로그램은 종료되지 않는다.

📖 08. 싱글쓰레드와 멀티쓰레드

  • 멀티쓰레드가 오히려 시간이 더 걸린다.
    -> why? : 쓰레드간의 작업 전환(context switching)에 시간이 걸리기 때문이다.

  • 작업 전환을 할 때는 현재 진행 중인 작업의 상태, 예를 들면 다음에 실행해야할 위치(PC, 프로그램 카운터) 등의 정보를 저장하고 읽어 오는 시간이 소요된다.

  • 싱글 코어에서 단순히 CPU만을 사용하는 계산작업이라면 오히려 멀티쓰레드보다 싱글쓰레드로 프로그래밍하는 것이 더 효율적이다.


📖 09. 싱글쓰레드와 멀티쓰레드 예제1

<예제 13-2 >

✍️ 입력

class Ex13_2 {
	public static void main(String args[]) {
		long startTime = System.currentTimeMillis();

		for(int i=0; i < 300; i++)
			System.out.printf("%s", new String("-"));		

		System.out.print("소요시간1:" +(System.currentTimeMillis()- startTime)); 

		for(int i=0; i < 300; i++) 
			System.out.printf("%s", new String("|"));		

 		System.out.print("소요시간2:"+(System.currentTimeMillis() - startTime));
	}
}

💻 출력
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------소요시간1:28||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||소요시간2:32


📖 10. 싱글쓰레드와 멀티쓰레드 예제2

<예제 13-3 >

✍️ 입력

class Ex13_3 {
	static long startTime = 0;

	public static void main(String args[]) {
		ThreadEx3_1 th1 = new ThreadEx3_1();
		th1.start();
		startTime = System.currentTimeMillis();

		for(int i=0; i < 300; i++)
			System.out.printf("%s", new String("-"));	

		System.out.print("소요시간1:" + (System.currentTimeMillis() - Ex13_3.startTime));
	} 
}

class ThreadEx3_1 extends Thread {
	public void run() {
		for(int i=0; i < 300; i++)
			System.out.printf("%s", new String("|"));	

		System.out.print("소요시간2:" + (System.currentTimeMillis() - Ex13_3.startTime));
	}
}

💻 출력
-----------------------------------------------||||||--------|||||||||||---|||---|||||||||||||||||||||||||||||||||||||||||||||-----------||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||---------------------------------||||||||||||||||||-------||||||||||||--------||||||||||||||||||||||||------------------------------------------------||||---------------------------------|||||||||||||||||||---------------------------------------------------------------------------------------------------||||||||||||||||||||||||||||||||||소요시간1:32||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||소요시간2:34

  • 실행할 때마다 다른 결과를 얻을 수 있다.
    -> OS의 프로세스 스케줄러의 영향을 받기 때문이다.
    -> 프로세스 스케줄러에 의해서 실행순서와 실행시간이 결정되기 때문에 매 순간 상황에 따라 실행시간이 일정하지 않고 쓰레드에게 할당되는 시간 역시 일정하지 않다.
    -> 쓰레드가 이러한 불확실성을 가지고 있다는 것을 염두해 두어야 한다.

  • 자바가 OS 독립적이라고 하지만 실제로 OS종속적이 부분이 몇가지 있는데 쓰레드가 그 중 하나이다.


📖 11. 쓰레드의 I/O블락킹(blocking)

-> 두 개의 쓰레드로 처리한다면 사용자의 입력을 기다리는 동안 다른 쓰레드가 작업을 처리할 수 있기 때문에 보다 효율적인 CPU의 사용이 가능하다.


📖 12. 쓰레드의 I/O블락킹(blocking) 예제1

<예제 13-4 >

✍️ 입력

import javax.swing.JOptionPane;

class Ex13_4 {
	public static void main(String[] args) throws Exception {
		String input = JOptionPane.showInputDialog("아무 값이나 입력하세요."); 
		System.out.println("입력하신 값은 " + input + "입니다.");

		for(int i=10; i > 0; i--) {
			System.out.println(i);
			try {
				Thread.sleep(1000);  // 1초간 시간을 지연한다.
			} catch(Exception e ) {}
		}
	}
}

💻 출력
입력하신 값은 fdfdf입니다.
10
9
8
7
6
5
4
3
2
1


📖 13. 쓰레드의 I/O블락킹(blocking) 예제2

<예제 13-5 >

✍️ 입력

import javax.swing.JOptionPane;

class Ex13_5 {
	public static void main(String[] args) throws Exception  {
		ThreadEx5_1 th1 = new ThreadEx5_1();
		th1.start();

		String input = JOptionPane.showInputDialog("아무 값이나 입력하세요."); 
		System.out.println("입력하신 값은 " + input + "입니다.");
	}
}

class ThreadEx5_1 extends Thread {
	public void run() {
		for(int i=10; i > 0; i--) {
			System.out.println(i);
			try {
				sleep(1000);
			} catch(Exception e ) {}
		}
	} // run()
}

💻 출력
10
9
8
7
입력하신 값은 dfdfdfdf입니다.
6
5
4
3
2
1


📖 14. 쓰레드의 우선순위

  • 쓰레드의 우선순위는 쓰레드를 생성한 쓰레드로부터 상속받는다.
    -> main메서드를 수행하는 쓰레드는 우선순위가 5이므로 main메서드 내에서 생성하는 쓰레드의 우선순위는 자동적으로 5가 된다.

📖 15. 쓰레드의 우선순위 예제

<예제 13-6 >

✍️ 입력

class Ex13_6 {
	public static void main(String args[]) {
		ThreadEx6_1 th1 = new ThreadEx6_1();
		ThreadEx6_2 th2 = new ThreadEx6_2();

		th2.setPriority(7);

		System.out.println("Priority of th1(-) : " + th1.getPriority());
		System.out.println("Priority of th2(|) : " + th2.getPriority());
		th1.start();
		th2.start();
	}
}

class ThreadEx6_1 extends Thread {
	public void run() {
		for(int i=0; i < 300; i++) {
			System.out.print("-");
			for(int x=0; x < 10000000; x++);
		}
	}
}

class ThreadEx6_2 extends Thread {
	public void run() {
		for(int i=0; i < 300; i++) {
			System.out.print("|");
			for(int x=0; x < 10000000; x++);
		}
	}
}

💻 출력
Priority of th1(-) : 5
Priority of th2(|) : 7
-|-|--------------------------------------------------------------||||||||||||||||||||----------------------------------------||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||----|----------||||||||||||||||||||||||||||||||||-|||||||||||||||||||||||||||||||||||||||||||||||||||||-------------------|||||||||||||||||-------------|||||||||||||||||||||||||||||||||||||||||||||||-----------------------------------------------------------------------------------------------------------------------------------------------------

-> th1은 main메서드에서 생성하였기 때문에 우선순위는 5이다(main메서드 우선순위가 5이다.)

-> OS에 종속적이기 때문에 우선순위를 넣는다고 해도 예측만 가능할 뿐 정확히 알 수는 없다.


📖 16. 쓰레드 그룹(thread group) && 17. 쓰레드 그룹(thread group)의 메서드

  • 자바 어플리케이션이 호출되면, JVM은 main과 system이라는 쓰레드 그룹을 만들고 JVM운영에 필요한 쓰레드들을 생성해서 이 쓰레드 그룹에 포함시킨다.
    -> 우리가 생성하는 모든 쓰레드 그룹은 main쓰레드 그룹의 하위 쓰레드 그룹이 되며, 쓰레드 그룹을 지정하지 않고 생성한 쓰레드는 자동적으로 main쓰레드 그룹에 속하게 된다.

📖 18. 데몬 쓰레드(daemon thread)

  • start() 이전에 데몬 쓰레드로 만들어주어야 한다.

📖 19. 데몬 쓰레드(daemon thread) 예제

<예제 13-7 >

✍️ 입력

class Ex13_7 implements Runnable  {
	static boolean autoSave = false;

	public static void main(String[] args) {
		Thread t = new Thread(new Ex13_7());
		t.setDaemon(true);		// 이 부분이 없으면 종료되지 않는다.
		t.start();

		for(int i=1; i <= 10; i++) {
			try{
				Thread.sleep(1000);
			} catch(InterruptedException e) {}
			System.out.println(i);

			if(i==5) autoSave = true;
		}

		System.out.println("프로그램을 종료합니다.");
	}

	public void run() {
		while(true) {
			try { 
				Thread.sleep(3 * 1000); // 3초마다
			} catch(InterruptedException e) {}

			// autoSave의 값이 true이면 autoSave()를 호출한다.
			if(autoSave) autoSave();
		}
	}

	public void autoSave() {
		System.out.println("작업파일이 자동저장되었습니다.");
	}
}

💻 출력
1
2
3
4
5
작업파일이 자동저장되었습니다.
6
7
8
작업파일이 자동저장되었습니다.
9
10
프로그램을 종료합니다.


📖 20. 쓰레드의 상태

  1. 쓰레드를 생성하고 start()를 호출하면 바로 실행되는 것이 아니라 실행대기열에 저장되어 자신의 차례가 될 때까지 기다려야 한다. 실행대기열은 큐(queue)와 같은 구조로 먼저 실행대기열에 들어온 쓰레드가 먼저 실행된다.

  2. 실행대기상태에 있다가 자신의 차례가 되면 실행상태가 된다.

  3. 주어진 실행시간이 다되거나 yield()를 만나면 다시 실행대기상태가 되고 다음 차례의 쓰레드가 실행상태가 된다.

  4. 실행 중에 suspend(), sleep(), wait(), join(), I/O block에 의해 일시정지상태가 될 수 있다. I/O block은 입출력작업에서 발생하는 지연상태를 말한다. 사용자의 입력을 기다리는 경우를 예로 들 수 있는데, 이런 경우 일시정지 상태에 있다가 사용자가 입력을 마치면 다시 실행대기상태가 된다.

  5. 지정된 일시정지시간이 다되거나(time-out), notify(), resume(), interrupt()가 호출되면 일시정지상태를 벗어나 다시 실행대기열에 저장되어 자신의 차례를 기다리게 된다.

  6. 실행을 모두 마치거나 stop()이 호출되면 쓰레드는 소멸된다.


📖 21. 쓰레드의 실행제어

  • 효율적인 멀티쓰레드 프로그램을 만들기 위해서는 보다 정교한 스케줄링을 통해 프로세스에게 주어진 자원과 시간을 여러 쓰레드가 낭비없이 잘 사용하도록 프로그래밍 해야 한다.
    -> 쓰레드의 상태와 관련 메서드를 잘 알아야 스케줄링을 잘 할 수 있다.


📖 22. sleep()

static void sleep(long millis)
  • 지정된 시간동안 쓰레드를 멈추게 한다.
  • sleep()에 의해 일시정지 상태가 된 쓰레드는 지정된 시간이 다 되거나 interrupt()가 호출되면, InterruptedException이 발생되어 깨어나 실행대기 상태가 된다.
  • sleep()을 호출할 때는 항상 try-catch문으로 예외처리 해주어야 한다.
    -> 예외처리 방법으로 깨어나게 한거니까 의문갖지 말 것!

📖 23. sleep() 예제

<예제 13-8 >

✍️ 입력

class Ex13_8 {
	public static void main(String args[]) {
		ThreadEx8_1 th1 = new ThreadEx8_1();
		ThreadEx8_2 th2 = new ThreadEx8_2();
		th1.start(); th2.start();

		try {
			th1.sleep(2000);	
		} catch(InterruptedException e) {}

		System.out.print("<<main 종료>>");
	} // main
}

class ThreadEx8_1 extends Thread {
	public void run() {
		for(int i=0; i < 300; i++) System.out.print("-");
		System.out.print("<<th1 종료>>");
	} // run()
}

class ThreadEx8_2 extends Thread {
	public void run() {
		for(int i=0; i < 300; i++) System.out.print("|");
		System.out.print("<<th2 종료>>");
	} // run()
}

💻 출력
--------||||||||--------------------||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||------|||||||--||------||||--||||||||||||||||||||||||||||||-------|||--------||||||||||||||||||||||||-||||||||||||||------------------------------------------------------------||||||||||||||||||-----|||||||||||||||||||||||||||||||||||||||||||||||------------------------------------|||||||||||||||||||||||||----------------------------------||||||||||||||||-------------------------------------------------<<th2 종료>>--------------------------------------------------------<<th1 종료>><<main 종료>>

-> sleep()는 (static메서드) 항상 현재 실행 중인 쓰레드에 대해 작동하기 때문에 th1.sleep()를 호출해도 sleep()를 호출한 쓰레드(main)에 적용이 된다.


📖 24. interrupt()

  • 진행중인 쓰레드를 취소할 때 사용한다.
void interrupt()                쓰레드의 interrupted상태를 false에서 true로 변경
boolean isInterrupted()         쓰레드의 interrupted상태를 반환
static boolean interrupted()    현재 쓰레드의 interrupted상태를 반환 후, false로 변경

cf) static이 붙으면 호출한 쓰레드를 기준으로 메서드를 적용한다.


📖 25. interrupt() 예제

<예제 13-9 >

✍️ 입력

import javax.swing.JOptionPane;

class Ex13_9 {
	public static void main(String[] args) throws Exception {
		ThreadEx9_1 th1 = new ThreadEx9_1();
		th1.start();

		String input = JOptionPane.showInputDialog("아무 값이나 입력하세요."); 
		System.out.println("입력하신 값은 " + input + "입니다.");
		th1.interrupt();  // interrupt()를 호출하면, interrupted상태가 true가 된다.
		System.out.println("isInterrupted():"+ th1.isInterrupted()); // true
	}
}

class ThreadEx9_1 extends Thread {
	public void run() {
		int i = 10;

		while(i!=0 && !isInterrupted()) {
			System.out.println(i--);
			for(long x=0;x<2500000000L;x++); // 시간 지연
		}
		System.out.println("카운트가 종료되었습니다.");
	} 
}

💻 출력
10
9
8
7
입력하신 값은 gdgdd입니다.
isInterrupted():true
카운트가 종료되었습니다.


📖 26,27. suspend(), resume(), stop() - 예제

<예제 13-10 >

✍️ 입력

class Ex13_10 {
	public static void main(String args[]) {
		RunImplEx10 r = new RunImplEx10();
		Thread th1 = new Thread(r, "*");
		Thread th2 = new Thread(r, "**");
		Thread th3 = new Thread(r, "***");
		th1.start();
		th2.start();
		th3.start();

		try {
			Thread.sleep(2000);
			th1.suspend();	// 쓰레드 th1을 잠시 중단시킨다.
			Thread.sleep(2000);
			th2.suspend();
			Thread.sleep(3000);
			th1.resume();	// 쓰레드 th1이 다시 동작하도록 한다.
			Thread.sleep(3000);
			th1.stop();		// 쓰레드 th1을 강제종료시킨다.
			th2.stop();
			Thread.sleep(2000);
			th3.stop();
		} catch (InterruptedException e) {}
	} // main
}

class RunImplEx10 implements Runnable {
	public void run() {
		while(true) {
			System.out.println(Thread.currentThread().getName());
			try {
				Thread.sleep(1000);
			} catch(InterruptedException e) {}
		}
	} // run()
}

💻 출력


**


  • **
    **

**











📖 28. join()과 yield()

  • 쓰레드 자신이 하던 작업을 잠시 멈추고 다른 쓰레드가 지정된 시간동안 작업을 수행하도록 할 때 join()을 사용한다.

  • sleep()처럼 interrupt()에 의해 대기상태에서 벗어날 수 있으며, join()이 호출되는 부분을 try - catch문으로 감싸야 한다.

  • sleep() vs join()
    -> join()은 현재 쓰레드가 아닌 특정 쓰레드에 대해 작동하므로 static메서드가 아니다.

  • 쓰레드의 실행제어 예제

-> if문 안에 gc를 깨우고(interrupt) join()을 사용해서 gc가 메모리를 확보할 시간을 주어야 한다.

try{
	gc.join(100);
} catch(InterruptedException e) {}	

📖 29. join()과 yield() 예제

<예제 13-11 >

✍️ 입력

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() - Ex13_11.startTime));
	} // main
}

class ThreadEx11_1 extends Thread {
	public void run() {
		for(int i=0; i < 300; i++) {
			System.out.print(new String("-"));
		}
	} // run()
}

class ThreadEx11_2 extends Thread {
	public void run() {
		for(int i=0; i < 300; i++) {
			System.out.print(new String("|"));
		}
	} // run()
}

💻 출력
------------------------------|||||--------|||||||||-------||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||-----------------------------|||||||||||||||||||||||||||||||||||||||||||||||||||||||-----------------------------------||||||||-----------|||||||||||||||||----------------------------------------------------------------------------------------------------------||||||||||||-----------|||||||||||||||||||------------------|||||||||||||||||||||||---------||||||||||||||---------------------||||-----||||||||||----------||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||소요시간:5

-> th1과 th2의 작업을 마칠 때까지 main쓰레드가 기다리도록 했다.


📖 30. 쓰레드의 동기화(synchronization)

  • 한 번에 하나의 쓰레드만 객체에 접근할 수 있도록 객체에 락(lock)을 걸어서 데이터의 일관성을 유지하는 것.
  • 한 쓰레드가 진행 중인 작업을 다른 쓰레드가 간섭하지 못하도록 막는 것을 '쓰레드의 동기화(synchronization)'라고 한다.

📖 31. synchronized를 이용한 동기화


📖 32. synchronized를 이용한 동기화 예제 1

<예제 13-12 >

✍️ 입력

class Ex13_12 {
	public static void main(String args[]) {
		Runnable r = new RunnableEx12();
		new Thread(r).start(); // ThreadGroup에 의해 참조되므로 gc대상이 아니다.
		new Thread(r).start(); // ThreadGroup에 의해 참조되므로 gc대상이 아니다.
	}
}

class Account {
	private int balance = 1000;

	public  int getBalance() {
		return balance;
	}

	public void withdraw(int money){
		if(balance >= money) {
			try { Thread.sleep(1000);} catch(InterruptedException e) {}
			balance -= money;
		}
	} // withdraw
}

class RunnableEx12 implements Runnable {
	Account acc = new Account();

	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());
		}
	} // run()
}

💻 출력
balance:800
balance:800
balance:500
balance:200
balance:200
balance:200
balance:0
balance:-100

  • 출금(withdraw)하는 예제인데, 잔고가 출금하려는 금액보다 큰 경우에만 출금하도록 되어있다. 그러나 결과를 보면 음수나 나오는 경우가 존재한다.
    -> why? : 한 쓰레드가 if문의 조건식을 통과하고 출금하기 바로 직전에 다른 쓰레드가 끼어들어서 출금을 먼저했기 때문이다.

📖 33. synchronized를 이용한 동기화 예제 2

<예제 13-13 >

✍️ 입력

class Ex13_13 {
	public static void main(String args[]) {
		Runnable r = new RunnableEx13();
		new Thread(r).start();
		new Thread(r).start();
	}
}

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;
		}
	} // withdraw
}

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());
		}
	} // run()
}

💻 출력
balance:800
balance:500
balance:200
balance:200
balance:200
balance:200
balance:0
balance:0

  • Account클래스의 인스턴스변수인 balance의 접근 제어자가 private이어야 한다.
    -> private이 아니면 외부에서 접근 할 수 있기 때문에 synchronized가 의미 없어진다.

📖 34. wait()과 notify()

  • notify()를 호출했다고 해서 원한는 쓰레드에 락을 주는 것이 아니다.
  • notifyAll()이 호출된다고 해서모든 객체의 waiting pool에 있는 쓰레드가 깨워지는 것은 아니다. notifyAll()이 호출된 객체의 waiting pool에 대기 중인 쓰레드만 해당되는 것을 기억하자.

📖 35. wait()과 notify() 예제 1

<예제 13-14 >

✍️ 입력

import java.util.ArrayList;

class Customer implements Runnable {
	private Table  table;
	private String food;

	Customer(Table table, String food) {
		this.table = table;  
		this.food  = food;
	}

	public void run() {
		while(true) {
			try { Thread.sleep(10);} catch(InterruptedException e) {}
			String name = Thread.currentThread().getName();

			if(eatFood())
				System.out.println(name + " ate a " + food);
			else 
				System.out.println(name + " failed to eat. :(");
		} // while
	}

	boolean eatFood() { return table.remove(food); }
}

class Cook implements Runnable {
	private Table table;

	Cook(Table table) {	this.table = table; }

	public void run() {
		while(true) {
			int idx = (int)(Math.random()*table.dishNum());
			table.add(table.dishNames[idx]);
			try { Thread.sleep(100);} catch(InterruptedException e) {}
		} // while
	}
}

class Table {
	String[] dishNames = { "donut","donut","burger" };
	final int MAX_FOOD = 6;
	private ArrayList<String> dishes = new ArrayList<>();
	public synchronized void add(String dish) { // synchronized를 추가
		if(dishes.size() >= MAX_FOOD)	
			return;
		dishes.add(dish);
		System.out.println("Dishes:" + dishes.toString());
	}

	public boolean remove(String dishName) {
		synchronized(this) {	
			while(dishes.size()==0) {
				String name = Thread.currentThread().getName();
				System.out.println(name+" is waiting.");
				try { Thread.sleep(500);} catch(InterruptedException e) {}	
			}

			for(int i=0; i<dishes.size();i++)
				if(dishName.equals(dishes.get(i))) {
					dishes.remove(i);
					return true;
				}
		} // synchronized

		return false;
	}

	public int dishNum() { return dishNames.length; }
}

class Ex13_14 {
	public static void main(String[] args) throws Exception {
		Table table = new Table(); // 여러 쓰레드가 공유하는 객체

		new Thread(new Cook(table), "COOK").start();
		new Thread(new Customer(table, "donut"),  "CUST1").start();
		new Thread(new Customer(table, "burger"), "CUST2").start();

		Thread.sleep(5000);
		System.exit(0);
	}
}

💻 출력
Dishes:[donut]
CUST2 failed to eat. :(
CUST1 ate a donut
CUST1 is waiting.
CUST1 is waiting.
CUST1 is waiting.
CUST1 is waiting.
CUST1 is waiting.
CUST1 is waiting.
CUST1 is waiting.
CUST1 is waiting.


📖 36. wait()과 notify() 예제 2

<예제 13-15 >

✍️ 입력

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(100);} 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(10);} 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);
	}
}

💻 출력
Dishes:[burger]
Dishes:[burger, burger]
Dishes:[burger, burger, donut]
Dishes:[burger, burger, donut, burger]
Dishes:[burger, burger, donut, burger, donut]
Dishes:[burger, burger, donut, burger, donut, donut]
COOK is waiting.
CUST2 ate a burger
CUST1 ate a donut
Dishes:[burger, burger, donut, donut, donut]
CUST1 ate a donut
CUST2 ate a burger
Dishes:[burger, donut, donut, donut]
Dishes:[burger, donut, donut, donut, donut]
Dishes:[burger, donut, donut, donut, donut, burger]
COOK is waiting.
CUST2 ate a burger
CUST1 ate a donut
Dishes:[donut, donut, donut, burger, donut]
CUST1 ate a donut
CUST2 ate a burger
Dishes:[donut, donut, donut, donut]
Dishes:[donut, donut, donut, donut, donut]
Dishes:[donut, donut, donut, donut, donut, donut]
COOK is waiting.
CUST1 ate a donut
Dishes:[donut, donut, donut, donut, donut, donut]
CUST1 ate a donut
CUST2 is waiting.
Dishes:[donut, donut, donut, donut, donut, donut]





[출처] 자바의 정석 <기초편> (남궁 성 지음)

profile
멈추지 않기

0개의 댓글