본문 바로가기

웹 개발/Java

[Web_JAVA] 38

● 동기화(Synchronized)

- 하나의 쓰레드가 자원에 접근 중일 때 다른 쓰레드가 동시에 같은 자원을 접근하지 못하게 막는 것

- 즉, 자원 공유 문제를 해결할 수 있다.

 

 

 

● 동기화 사용

1. synchronized(mutex) {...}

- 동기화 블럭이라고 부르며, 일부 소스코드만 동기화를 걸어준다.

 

※ mutex : 자원이 있는 객체

 

 

2. synchronized

- 영역 전체에 동기화를 걸어주며, 메소드 리턴타입 앞에 작성하면 해당 메소드 전체에 동기화가 걸린다.

 

 

 


 

실습(Thread(1))

1. 문제

- 길동이네 동물원에는 3마리의 동물이 있다.
- 각 동물은 울음소리가 다르고 2마리의 동물은 동시에 운다.
- 나머지 1마리 동물은 2마리 동물이 모두 울고 나서 마지막에 운다.

- package명은 thread.threadTest1로 만들고 클래스는 2개만 선언한다.
- 하나의 클래스에는 main 쓰레드가 있다.
- Runnable 인터페이스로 멀티 쓰레드를 구현하고 반드시 join()을 사용한다.
※ 각 동물은 3번씩만 운다.

 

 

 

2. 코드

- run()을 재정의하는 Animal.java 생성

public class Animal implements Runnable{
	
	@Override
	public void run() {
		for (int i = 0; i < 3; i++) {
			
			System.out.println(Thread.currentThread().getName());
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {;}
			
		}
	}
	
}

 

 

- Zoo.java에서 실행

public class Zoo {
	public static void main(String[] args) {
		
		String[] sounds = {"어흥", "꿱", "펭하"};
		Animal[] animals = new Animal[sounds.length];  // 3마리의 동물을 담아두는 변수
		Thread[] threads = new Thread[sounds.length];  // start()를 사용하기 위한 Thread 선언
		
		for (int i = 0; i < animals.length; i++) {
			animals[i] = new Animal();  // 반복문 실행 시마다 새로운 Animal 생성
			threads[i] = new Thread(animals[i], sounds[i]);
		}
		
		for (int i = 0; i < threads.length; i++) { 
			threads[i].start();
			if(i != 0) {
				// 이미 start()된 쓰레드는 join()의 영향을 받지 않는다.
				// i가 0일 때에는 if문에 들어오지 않기 때문에 그대로 실행된다.
				// i가 1일 때에는 if문에 들어가기 때문에 join()이 실행된다.
				// == i가 1일 때의 실행이 끝나야 다음 반복이 실행된다.
				// 따라서 i가 2일 때에는 앞의 반복이 모두 끝나면 실행된다.
				try {
					threads[i].join();
				} catch (InterruptedException e) {;}
				
			}
		}
		
	}
}

 

결과

 

 

 

 

실습(Thread(2))

1. 문제

// 3개의 쓰레드가 있다.
// Thread1, Thread2, Thread3
// 사용자가 입력한 순서대로 각각 알맞는 문자열이 출력된다.

// 입력 예) 3 1 2 
// 출력 예) third first second

// 단, Thread들은 항상 1, 2, 3 순서로 실행되어야 한다.

// Thread1 : third
// Thread2 : first
// Thread3 : second

// 출력 시 쓰레드의 번호도 출력할 경우 가산점으로 처리한다.

 

 

 

2. 코드

- run() 메소드를 재정의하는 ThreadTask.java 생성

public class ThreadTask implements Runnable{
	
	public static int count;
	
	public void printFirst(Runnable first) {
		first.run();
	}
	
	public void printSecond(Runnable second) { 
		second.run();
	}
	
	public void printThird(Runnable third) {
		third.run();
	}
	
	@Override
	public void run() {
		switch (Thread.currentThread().getName()) {
		case "1":
			printFirst(() -> System.out.println("Thread" + ++count + " : first"));
			break;
		case "2":
			printSecond(() -> System.out.println("Thread" + ++count + " : second"));
			break;
		case "3":
			printThird(() -> System.out.println("Thread" + ++count + " : third"));
			break;
		default:
			break;
		}
	}
	
}

 

 

- ThreadMain.java에서 실행

import java.util.Scanner;

public class ThreadMain {
	public static void main(String[] args) {

		Scanner sc = new Scanner(System.in);
		
		int[] arInput = new int[3];  // 입력받은 정수를 담아준다.
		Thread[] arThread = new Thread[3];  // Thread 객체 생성
		
		ThreadTask tt = new ThreadTask();  // ThreadTask 객체 생성 
		
		for (int i = 0; i < arThread.length; i++) {
			arThread[i] = new Thread(tt);  // 동일한 자원에 접근한다.
		}
		
		System.out.print("입력 : ");
		
		for (int i = 0; i < arInput.length; i++) {
			arInput[i] = sc.nextInt();
			
			// 3 입력 : arThread[0] == Thread1 == third(3)
			arThread[i].setName(arInput[i] + "");  // 문자열로 형변환
		}
		
		for (int i = 0; i < arThread.length; i++) {
			arThread[i].start();
			try {
				// 너무 빨라서 의도한대로 출력되지 않기 때문에 join()을 사용한다.
				arThread[i].join();
			} catch (InterruptedException e) {;}
		}
		
	}
	
}

 

결과

 

 

 

 

실습(동기화(1))

1. 문제

- 아들과 엄마가 돈을 동시에 일정 금액만큼 출금하는 프로그램

 

 

 

2. 코드

- 동기화 문법을 사용한 메소드 생성 및 run() 메소드를 재정의하는 ATM.java 생성

public class ATM implements Runnable{
	
	int money = 10000;

	@Override
	public void run() {
		for (int i = 0; i < 5; i++) {  // 5번 반복
			withdraw(1000);  // 1000원씩 출금
			try {
				Thread.sleep(500);
			} catch (InterruptedException e) {;}
		}
		
	}
	 
	public synchronized void withdraw(int money) {
//		synchronized (this) { this.money -= money;}

		this.money -= money;  // money 차감
		System.out.println(Thread.currentThread().getName() + "이(가)" + money + "원 출금");
		System.out.println("현재 잔액 : " + this.money + "원");
	}
	
}

 

 

- CU.java에서 실행

public class CU {
	public static void main(String[] args) {
		ATM atm = new ATM();
		
		Thread mom = new Thread(atm, "엄마");
		Thread son = new Thread(atm, "아들");
		
		mom.start();
		son.start();
		
	}
}

 

결과

 

 

 

 

실습(동기화(2))

※ 문제

- 빵을 20개 만드는 동안 먹고 만드는 일을 반복하는 프로그램

// 만들어져있는 빵이 10개가 되면 빵 만들기를 정지한다.
// 위의 상태에서 빵을 1개 먹으면 다시 빵 만들기를 진행한다.
// 빵이 없는 상태에서 빵을 먹으려는 시도를 하면 안내 메시지를 출력한다.
// 총 만든 빵의 수가 20개가 되면 종료한다.

 

 

 

1. BreadPlate.java 생성

- 필요한 변수 선언

// 객체를 최소 1개는 만들 수 있도록 해야 한다.
private static BreadPlate plate;
	
int breadCount;
int eatCount;

 

 

- 생성자 및 getter 생성

// 싱글톤 패턴
// 값을 수정할 수 없어야 하기 때문에 setter는 만들지 않아야 한다.
// 무조건 객체가 단 한 개만 있어야 할 때 사용한다.
private BreadPlate() {;}  // 다른 클래스에서 기본 생성자를 사용할 수 없다.

public static BreadPlate getInstance() {
	if(plate == null) {  // 객체는 1개만 생성하여 사용하므로 null일 때에만 새로 생성하도록 작성한다.
		plate = new BreadPlate();
	}
	return plate;
}

 

 

- 빵을 만드는 메소드(makeBread()) 생성

public synchronized void makeBread() {

	if(breadCount > 9) {
		System.out.println("빵이 가득 찼습니다.");
		try {
			wait();  // Thread가 멈춘다.(eatBread() 후 깨워준다.)
		} catch (InterruptedException e) {;}
	}

	breadCount++;
	System.out.println("빵을 1개 만들었습니다. 총 : " + breadCount + "개");

}

 

 

- 빵을 먹는 메소드(eatBread()) 생성

public synchronized void eatBread() {

	if(eatCount == 20) {
		System.out.println("빵이 다 떨어졌습니다.");

	} else if(breadCount < 1) {
		System.out.println("🍩🍪빵을 만들고 있습니다.🍩🍪");

	} else {
		breadCount--;  // 빵이 1개 줄어든다.
		eatCount++;  // 빵을 1개 먹는다.
		System.out.println("빵을 1개 먹었습니다. 총 : " + breadCount + "개");

		// 빵이 10개일 때 wait()인 상태이다. 
		// 이때 빵을 1개 먹는다면 빵의 개수가 9개가 되므로 wait()를 깨워주어야 한다.
		notify();  // wait()를 깨워준다.
	}

}

 

 

 

2. run() 메소드를 재정의하는 BreadMaker.java 생성

public class BreadMaker implements Runnable{
	
	@Override
	public void run() {
		for (int i = 0; i < 20; i++) {
			BreadPlate.getInstance().makeBread();
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {break;}
		}
		System.out.println("재료 소진");
	} 
	
}

 

 

 

3. Bakery.java에서 실행

import javax.swing.ImageIcon;
import javax.swing.JOptionPane;

public class Bakery {
	public static void main(String[] args) {
		
		BreadMaker maker = new BreadMaker();
		Thread makerThread = new Thread(maker);  // maker라는 자원 전달
		BreadPlate plate = BreadPlate.getInstance();
		
		String[] btns = {"빵먹기", "나가기"}; 
		int choice = 0;
		
		ImageIcon icon = new ImageIcon("src/img/bakery.gif");  // 이미지 아이콘을 적용하여 출력한다.
		
		makerThread.start();
		
		while(true) {

			// 대화상자(팝업창)
			// "" : 대화상자 안에서 출력되는 텍스트
			// "길동이네 빵집" : 대화상자의 타이틀바에 출력되는 텍스트
			// DEFAULT_OPTION : 기본 옵션으로 설정한다.
			//PLAIN_MESSAGE : 원하는 이미지를 넣어주기 위해 제공하는 이미지를 없앤다.
			// icon : 대화상자에 넣을 이미지
			// btns : 버튼 옵션(btns에 2개의 버튼 텍스트를 설정하였기 때문에 2개의 버튼이 생성된다.)

			choice = JOptionPane.showOptionDialog(null, "", "길동이네 빵집", JOptionPane.DEFAULT_OPTION,
					JOptionPane.PLAIN_MESSAGE, icon, btns, null);
			
			if(choice == 0) {  // 빵먹기
				plate.eatBread();
			} else {  // 나가기
				makerThread.interrupt();
				break;
			}
		}
		
	}
}

 

결과(팝업)

 

결과(콘솔)

 

 

 

'웹 개발 > Java' 카테고리의 다른 글

[Web_JAVA] 40  (0) 2022.03.26
[Web_JAVA] 39  (0) 2022.03.25
[Web_JAVA] 37  (0) 2022.03.23
[Web_JAVA] 36  (0) 2022.03.22
[Web_JAVA] 35  (0) 2022.03.21