Java & Kotlin
[Java 기능] 동기화 구현 (1) - 리소스 하나를 공유하는 경우
Sue
2022. 2. 18. 20:35
critical section과 semaphore
- critical section은 두개 이상의 쓰레드가 동시에 접근하는 경우 문제가 발생할 수 있기 때문에 방지해야 함
- semaphore는 특별한 형태의 시스템 객체이며 get/release 기능이 있음
- 한번에 오직 하나의 쓰레드만이 semaphore를 얻을 수 있고, 다른 쓰레드는 대기 상태(blocking)가 됨
- semaphore를 가진 쓰레드만이 critical section에 진입할 수 있음 (일종의 열쇠 역할)
예제 코드
한 계좌에서 동시에 입금과 출금이 일어날 때(두개의 쓰레드가 하나의 공유 자원을 동시에 사용할 때) 결과 확인해보기
동기화 구현하지 않은 경우
- shared resource는 money이지만 자바에서는 객체로 resource 단위를 설정하기때문에 Bank의 인스턴스를 static으로 생성
- park과 pWife 쓰레드는 각각 입금과 출금을 수행함
- 수행 후 각 쓰레드가 반환한 공유 변수의 값을 확인
package ch22;
class Bank {
private int money = 10000;
public void deposit(int save) {
int m = getMoney();
try {
Thread.sleep(3000); // 3초 소요
} catch (InterruptedException e) {
e.printStackTrace();
}
setMoney(m + save);
}
public void withdrawl(int minus) {
int m = getMoney();
try {
Thread.sleep(2000); // 2초 소요
} catch (InterruptedException e) {
e.printStackTrace();
}
setMoney(m - minus);
}
public int getMoney() {
return money;
}
public void setMoney(int money) {
this.money = money;
}
}
class Park extends Thread{
public void run() {
System.out.println("start deposit");
SyncMain.myBank.deposit(3000);
System.out.println("deposit(3000): " + SyncMain.myBank.getMoney());
}
}
class ParkWife extends Thread{
public void run() {
System.out.println("start withdrawl");
SyncMain.myBank.withdrawl(1000);
System.out.println("withdrawl(1000): " + SyncMain.myBank.getMoney());
}
}
public class SyncMain {
public static Bank myBank = new Bank(); // 공유 변수가 선언되어 있는 객체를 static으로 생성
public static void main(String[] args) {
Park park = new Park();
park.start();
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
ParkWife pWife = new ParkWife();
pWife.start();
}
}
수행 결과
- deposit이 시작하고 끝나기 전에 (수행 중일때) withdrawl이 수행되었음
- 각 쓰레드가 같은 공유변수 값을 가져와서 각자 연산 수행 후 반환
- 이를 방지하기 위해 deposit과 withdrawl이 동시에 수행되지 않도록 구현해야 함
start deposit
start withdrawl
withdrawl(1000): 9000
deposit(3000): 13000
동기화 (Synchronization)
- 두개의 쓰레드가 같은 객체에 접근하는 경우, 동시에 접근할 때 문제가 발생함
- 동기화는 critical section에 접근한 경우 공유자원을 lock하여 다른 쓰레드가 접근하지 못하도록 구현함
- 동기화를 잘못 구현하면 deadlock(교착상태)에 빠질 수 있음
자바에서의 동기화
synchronized 메서드
- synchronized 메서드가 실행되는 동안 메서드가 포함된 객체에 lock을 검
- 공유 변수를 변경하는 등 race condition이 발생할 수 있는 메서드에 사용
- 자바에서는 deadlock을 방지하는 기술이 제공되지 않으므로 synchronized 메서드에서 다른 synchronized 메서드는 호출하지 않도록 해야함
public synchronized void deposit(int save) {
...
}
public synchronized void withdrawl(int minus) {
...
}
synchronized 메서드를 이용했을때 SyncMain.java 수행결과
- 동기화를 구현하지 않았을 때와 달리 순차적으로 수행됨
- 일단 하나의 쓰레드가 공유 자원에 접근하게 되면 다른 쓰레드는 끝날 때까지 기다림 (block; 대기 상태)
start deposit
start withdrawl
deposit(3000): 13000 // deposit 수행이 끝나면 withdrawl 수행
withdrawl(1000): 12000
synchronized 블럭
- 매개 변수로 lock을 걸 객체를 입력함
- 메서드 안에서 혹은 쓰레드에서 사용할 수 있음
- 동기화가 이루어질 영역을 직접 설정할 수 있어 synchronized 메서드보다 디테일하게 설정할 수도 있음
- 메서드에서 사용하는 경우
public void deposit(int save) {
synchronized(this) { // 메서드에서 block 사용
int m = getMoney();
try {
Thread.sleep(3000); // 3초 소요
} catch (InterruptedException e) {
e.printStackTrace();
}
setMoney(m + save);
}
}
- 쓰레드에서 사용하는 경우
class Park extends Thread{
public void run() {
synchronized(SyncMain.myBank) { // 쓰레드에서 block 사용
System.out.println("start deposit");
SyncMain.myBank.deposit(3000);
System.out.println("deposit(3000): " + SyncMain.myBank.getMoney());
}
}
}