안녕하세요, 코린이의 코딩 학습기 채니 입니다.
개인 포스팅용으로 내용에 오류 및 잘못된 정보가 있을 수 있습니다.
동기화(Synchronization)란?
- 멀티 쓰레딩 프로그래밍에서 쓰레드 간 공유 자원에 대한 처리
- 공유 자원에 대해 사용 순서를 정해주는 것
- lock(key)을 획득한 쓰레드만 사용 가능
쓰레드가 공유 자원에 대한 동기화 미사용 시
두 대의 ATM기를 이용해 출금을 하는 프로그램을 구현해보았습니다.
public class SynchronizationStudy {
public static void main(String[] args) {
SynchronizationStudy study = new SynchronizationStudy();
study.test();
}
public void test() {
Account acc = new Account();
Runnable run1 = new Atm(acc);
Runnable run2 = new Atm(acc);
Thread atm1 = new Thread(run1, "ATM1");
Thread atm2 = new Thread(run2, "ATM2");
atm1.start();
atm2.start();
}
static class Atm implements Runnable {
private Account acc;
public Atm(Account acc) {
this.acc = acc;
}
@Override
public void run() {
while(acc.getBalance() > 0) {
int money = (int)(Math.random()*3 + 1)*100;
acc.withdraw(money);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + " 종료!");
}
}
static class Account {
private int balance = 1000; //잔액
public int getBalance() {
return balance;
}
public void withdraw(int money) {
String thName = Thread.currentThread().getName();
if(balance >= money) {
balance -= money;
System.out.printf("[%s] %d원 출금 -> 잔액 : %d원\n", thName, money, balance);
} else {
System.out.printf("[%s] %d원 출금시도 -> 잔액이 부족합니다.\n", thName, money);
}
}
}
}
@콘솔출력값
[ATM2] 300원 출금 -> 잔액 : 400원
[ATM1] 300원 출금 -> 잔액 : 400원
[ATM1] 100원 출금 -> 잔액 : 300원
[ATM2] 100원 출금 -> 잔액 : 300원
[ATM2] 200원 출금 -> 잔액 : 200원
[ATM1] 100원 출금 -> 잔액 : 200원
[ATM1] 100원 출금 -> 잔액 : 100원
[ATM2] 200원 출금 -> 잔액 : 100원
[ATM2] 300원 출금시도 -> 잔액이 부족합니다.
[ATM1] 300원 출금시도 -> 잔액이 부족합니다.
[ATM2] 300원 출금시도 -> 잔액이 부족합니다.
[ATM1] 300원 출금시도 -> 잔액이 부족합니다.
[ATM1] 300원 출금시도 -> 잔액이 부족합니다.
[ATM2] 100원 출금 -> 잔액 : 0원
ATM1 종료!
ATM2 종료!
출력값을 확인해보면, 1000원에서 300원을 출금했더니 잔액이 400원이 되고... 잔액이 100원일 때 200원을 출금하려고 하니 출금이 되었고 잔액이 또 100원이 남는 정말 이상한 출금 내역이 리턴 되었습니다.
이는 멀티 쓰레드가 공유 자원(Account)을 같이 사용해서인데요,
ATM2가 300원을 출금하고 있을 때 ATM1이 끼어들어서 300원을 출금하고 잔액 표시를 했기 때문인데요.
이처럼 공유 자원에 대해서 멀티 쓰레드의 동기화 사용 (사용 순서)을 하지 않으면 위와 같은 결과를 초래하게 됩니다.
동기화 사용 시
동기화 처리 방법 ① synchronized 메소드 작성
- 메소드를 가진 객체(Account)가 격리 공간으로 지정
public class SynchronizationStudy {
public static void main(String[] args) {
SynchronizationStudy study = new SynchronizationStudy();
study.test();
}
public void test() {
Account acc = new Account();
Runnable run1 = new Atm(acc);
Runnable run2 = new Atm(acc);
Thread atm1 = new Thread(run1, "ATM1");
Thread atm2 = new Thread(run2, "ATM2");
atm1.start();
atm2.start();
}
static class Atm implements Runnable {
private Account acc;
public Atm(Account acc) {
this.acc = acc;
}
@Override
public void run() {
while(acc.getBalance() > 0) {
int money = (int)(Math.random()*3 + 1)*100;
acc.withdraw(money);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + " 종료!");
}
}
static class Account {
private int balance = 1000; //잔액
public int getBalance() {
return balance;
}
public synchronized void withdraw(int money) {
System.out.println("+++++++++++++++++++++++++++");
String thName = Thread.currentThread().getName();
if(balance >= money) {
balance -= money;
System.out.printf("[%s] %d원 출금 -> 잔액 : %d원\n", thName, money, balance);
} else {
System.out.printf("[%s] %d원 출금시도 -> 잔액이 부족합니다.\n", thName, money);
}
System.out.println("---------------------------");
}
}
}
@콘솔출력값
+++++++++++++++++++++++++++
[ATM1] 100원 출금 -> 잔액 : 900원
---------------------------
+++++++++++++++++++++++++++
[ATM2] 100원 출금 -> 잔액 : 800원
---------------------------
+++++++++++++++++++++++++++
[ATM2] 300원 출금 -> 잔액 : 500원
---------------------------
+++++++++++++++++++++++++++
[ATM1] 300원 출금 -> 잔액 : 200원
---------------------------
+++++++++++++++++++++++++++
[ATM2] 200원 출금 -> 잔액 : 0원
---------------------------
+++++++++++++++++++++++++++
[ATM1] 100원 출금시도 -> 잔액이 부족합니다.
---------------------------
ATM1 종료!
ATM2 종료!
동기화를 사용하니, ATM1이 공유자원(Account)을 사용하는 동안 ATM2는 사용할 수 없어서 기다리고 있다가 ATM1이 끝나면 다시 그 공유자원을 사용하기 위해 ATM1과 ATM2가 lock을 잡아 사용하고, lock을 잡지 못한 쓰레드는 기다리게 됩니다.
따라서 위와 같이 출금 금액에 따른 잔액이 올바르게 리턴되는 것이죠.
② 메소드 내 synchronized 블록 작성
- 지정한 객체가 격리 공간으로 지정
public class SynchronizationStudy {
public static void main(String[] args) {
SynchronizationStudy study = new SynchronizationStudy();
study.test();
}
public void test() {
Account acc = new Account();
Runnable run1 = new Atm(acc);
Runnable run2 = new Atm(acc);
Thread atm1 = new Thread(run1, "ATM1");
Thread atm2 = new Thread(run2, "ATM2");
atm1.start();
atm2.start();
}
static class Atm implements Runnable {
private Account acc;
public Atm(Account acc) {
this.acc = acc;
}
@Override
public void run() {
while(acc.getBalance() > 0) {
int money = (int)(Math.random()*3 + 1)*100;
acc.withdraw(money);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + " 종료!");
}
}
static class Account {
private int balance = 1000; //잔액
public int getBalance() {
return balance;
}
public void withdraw(int money) {
synchronized(this) {
System.out.println("+++++++++++++++++++++++++++");
String thName = Thread.currentThread().getName();
if(balance >= money) {
balance -= money;
System.out.printf("[%s] %d원 출금 -> 잔액 : %d원\n", thName, money, balance);
} else {
System.out.printf("[%s] %d원 출금시도 -> 잔액이 부족합니다.\n", thName, money);
}
System.out.println("---------------------------");
}
}
}
}
@콘솔출력값
+++++++++++++++++++++++++++
[ATM1] 100원 출금 -> 잔액 : 900원
---------------------------
+++++++++++++++++++++++++++
[ATM2] 100원 출금 -> 잔액 : 800원
---------------------------
+++++++++++++++++++++++++++
[ATM1] 300원 출금 -> 잔액 : 500원
---------------------------
+++++++++++++++++++++++++++
[ATM2] 200원 출금 -> 잔액 : 300원
---------------------------
+++++++++++++++++++++++++++
[ATM1] 300원 출금 -> 잔액 : 0원
---------------------------
+++++++++++++++++++++++++++
[ATM2] 100원 출금시도 -> 잔액이 부족합니다.
---------------------------
ATM2 종료!
ATM1 종료!
마찬가지로 (this)의 lock을 잡은 ATM1 쓰레드와 ATM2 쓰레드가 사용하는 중에는 (this) lock을 잡지 못한 쓰레드는 기다리게 되고, 쓰레드가 사용을 마치면 다시 ATM1 쓰레드와 ATM2 쓰레드 중 lock을 잡은 쓰레드가 실행되게 됩니다.
'Java > Java' 카테고리의 다른 글
네트워크) TCP 소켓 프로그래밍 (채팅 프로그램 만들기) (0) | 2022.04.11 |
---|---|
네트워크) InetAddress, URL, URLConnection (0) | 2022.04.11 |
쓰레드) 쓰레드 컨트롤 (sleep, join, interrupt), Daemon 쓰레드 (0) | 2022.04.03 |
쓰레드) 쓰레드란?, 멀티쓰레드, 장단점, 쓰레드 우선 순위 (0) | 2022.04.03 |
컬렉션/Map<K, V>) Properties, 설정 파일 쓰고 읽어오기 (0) | 2022.04.02 |