● 동기화(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 |