KH/JAVA

# 29 동기화( Synchronization ) / ATM 만들기

오늘의 진 2022. 7. 22. 13:34

 

동기화( Synchronization ) 

 

:지금까지의 스레드는 각기 독립적으로 작동했다. 

이런 경우에 여러 개의 스레드가 동시에 공유된 하나의 값에 접근한다면 문제가 생길 수 있다. 

 

예를 들어 ATM(은행 자동화 기기 )을 사용할 경우에 ATM을 사용할 수 있는 카드를 여러 장 발급할 수 있다.

만약 세사람이 각자 같은 계좌를 이용할 수 있는 ATM을 이용해 입출금을 시도할 수 있다. 

이럴 경우에 발생하는 문제를 해결하는 방법을 제공한다.

 

여러 개의 은행카드는 하나의 계좌에 입출금을 할 수 있다.

이런 경우 동시에 공유된 값에 접근할 수 있는 스레드는 하나로 제한할 필요가 있다. 이런 작업을 처리하기 위해 자바에서는 동기화 기법이 존재한다. 즉 Synchronization는 한 시점에 하나의 스레드만 공유된 값을 변경시킬 수 있다.

 

이때 스레드에 의해 공유된 값을 임계영역(Critical Section) 이라 하고 임계 영역 지정은 Synchronized 키워드를 이용해 처리한다. 하나의 스레드가 임계 영역으로 지정된 변수나 메소드를 호출할 때 다른 스레드가 그 값에 접근할 수 없도록 lock(락)을 걸고 사용이 끝나면 락을 해제한다. (예 - 은행계좌에 한명이 접속하면 나머지 사람들은 동일계좌에 접근불가)

 

 

동기화 처리방법

 

방법 1. 공유 데이터에 접근하는 메소드를  synchronized 키워드로 처리한다.

synchronized 메소드 ( ){
......;  // 공유데이터 접근 
}

방법  2. 공유 데이터에 접근되는 영역을  synchronized 키워드로 블럭화 처리한다.

이때 공유된 자원을 가지고 있는 클래스 객체를 사용한다.

 synchronized(클레스 객체){
    ..............; // 처리할 블럭
    }

 

 

 

(예시) - 방법1을 이용하여 ATM만들기

class ATM {
	private int total;

	public ATM(int total) {
		this.total = total;
	}

	// 공유되는 메소드에 synchronized 붙여줌 - 동시접근을 막아줌
	synchronized void deposit(int amount, String name) {
		total += amount;
		System.out.println(name + "님 입금금액 : " + amount + " 원");
	}

	synchronized void withdraw(int amount, String name) {
		if ((total - amount) > =0) {
			total -= amount;
			System.out.println(name + "님 출금금액 : " + amount + " 원");
		} else {
			System.out.println(name + "님 잔액이 부족해 출 금할 수 없습니다.");
		}
	}

	public void getTotal() {
		System.out.println("\n 현재 계좌의 금액 : " + total + "\n");
	}
}

class ATM_USER extends Thread { // ATM 사용자

	boolean flag = false; // 입금, 출금

	ATM obj;

	public ATM_USER(ATM obj, String name) {
		super(name);
		this.obj = obj;
	}

	@Override
	public void run() {

		for (int i = 0; i < 5; i++) { // 한사람이 다섯번씩 입출금을 반복하겠다
			try {
				sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}

			if (flag) {// 입금
				obj.deposit((int) (Math.random() * 10000), getName());
				obj.getTotal();
			} else {// 출금
				obj.withdraw((int) (Math.random() * 10000), getName());
				obj.getTotal();
			}

			flag = !flag; // 입금 - 출금 - 입금 반복하도록 만듬
		}
	}
}

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

		ATM atm = new ATM(10000); // 계좌에 10000원이 들어있음

		// 사용자 생성
		ATM_USER user1 = new ATM_USER(atm, "유현진");
		ATM_USER user2 = new ATM_USER(atm, "손흥민");
		ATM_USER user3 = new ATM_USER(atm, "우상혁");

		// 계좌사용
		user1.start();
		user2.start();
		user3.start();
	}
}

( 해석 )

user을 thread로 만드는 이유 : 여러명이 동시다발적으로 접근하도록 하기위해서 

이때 동시다발적으로 접근하면 입,출,총액이 엉망이됨.

만원밖에 없는데 user 1,2 가 7000, 5000 출금한다면 총액이 음수가 되어버린다.

이를 막기위해  여러명이 접근하는 deposit , withdraw를 한번에 한명씩만 접근할 수 있도록 

synchronized 키워드를 붙여서 한명의 사용자가 접근하면 다른 사용자가 접근하지 못하도록 막아준다. 

코드에 의해 출금 - 입금 - 출금 순으로 진행되는데  어떤 유저가 먼저 실행될지는 스레드가 돌아가기에 따라 랜덤이다. 다만 user들이 무작위로 모두 입금이 끝나면 출금이 진행되는 순으로 코드가 출력됨.

 

 

 

(예시) - 위의 방법과 같지만 Runnable이용하는 방법

class ATM_s {
	private int total;

	public ATM_s(int total) {
		this.total = total;
	}

	// 공유되는 메소드에 synchronized 붙여줌 - 동시접근을 막아줌
	synchronized void deposit(int amount, String name) {
		total += amount;
		System.out.println(name + "님 입금금액 : " + amount + " 원");
	}

	synchronized void withdraw(int amount, String name) {
		if ((total - amount) > 0) {
			total -= amount;
			System.out.println(name + "님 출금금액 : " + amount + " 원");
		} else {
			System.out.println(name + "님 잔액이 부족해 출 금할 수 없습니다.");
		}
	}

	public void getTotal() {
		System.out.println("\n 현재 계좌의 금액 : " + total + "\n");
	}

}

class ATM_USER_s implements Runnable { // ATM 사용자

	boolean flag = false; // 입금, 출금

	ATM_s obj;
	String name;

	public ATM_USER_s(ATM_s obj, String name) {
		this.name = name;
		this.obj = obj;
	}

	@Override
	public void run() {

		for (int i = 0; i < 5; i++) { // 한사람이 다섯번씩 입출금을 반복하겠다
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}

			if (flag) {// 입금
				obj.deposit((int) (Math.random() * 10000), name);
				obj.getTotal();
			} else {// 출금
				obj.withdraw((int) (Math.random() * 10000), name);
				obj.getTotal();
			}

			flag = !flag; // 입금 - 출금 - 입금 반복하도록 만듬

		}
	}

}

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

		ATM_s atm = new ATM_s(10000); // 계좌에 10000원이 들어있음

		// 사용자 생성
		ATM_USER_s user1 = new ATM_USER_s(atm, "유현진");
		ATM_USER_s user2 = new ATM_USER_s(atm, "손흥민");
		ATM_USER_s user3 = new ATM_USER_s(atm, "우상혁");

		Thread t1 = new Thread(user1);
		Thread t2 = new Thread(user2);
		Thread t3 = new Thread(user3);

		// 계좌사용
		t1.start();
		t2.start();
		t3.start();

	}

}

Thread t4 = new Thread(new ATM_USER_s(new ATM_s(10000 ),"손연재"));  이런식으로 한꺼번에 만들기도 가능함

 

 

(예시)- 방법2 이용하여 ATM 만들기

class ATM_2 {
	private int total;

	public ATM_2(int total) {
		this.total = total;
	}

	void deposit(int amount, String name) {
		total += amount;
		System.out.println(name + "님 입금금액 : " + amount + " 원");
	}

	void withdraw(int amount, String name) {
		if ((total - amount) >= 0) {
			total -= amount;
			System.out.println(name + "님 출금금액 : " + amount + " 원");
		} else {
			System.out.println(name + "님 잔액이 부족해 출 금할 수 없습니다.");
		}
	}

	public void getTotal() {
		System.out.println("\n 현재 계좌의 금액 : " + total + "\n");
	}
}

class ATM_USER_2 extends Thread { // ATM 사용자

	boolean flag = false; // 입금, 출금

	ATM_2 obj;

	public ATM_USER_2(ATM_2 obj, String name) {
		super(name);
		this.obj = obj;
	}

	@Override
	public void run() {

		for (int i = 0; i < 5; i++) { // 한사람이 다섯번씩 입출금을 반복하겠다
			try {
				sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			synchronized (obj) {
				if (flag) {// 입금
					obj.deposit((int) (Math.random() * 10000), getName());
					obj.getTotal();
				} else {// 출금
					obj.withdraw((int) (Math.random() * 10000), getName());
					obj.getTotal();
				}
			}

			flag = !flag; // 입금 - 출금 - 입금 반복하도록 만듬
		}
	}
}

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

		ATM_2 atm = new ATM_2(10000); // 계좌에 10000원이 들어있음

		// 사용자 생성
		ATM_USER_2 user1 = new ATM_USER_2(atm, "유현진");
		ATM_USER_2 user2 = new ATM_USER_2(atm, "손흥민");
		ATM_USER_2 user3 = new ATM_USER_2(atm, "우상혁");

		// 계좌사용
		user1.start();
		user2.start();
		user3.start();
	}
}

 

 

 

 

 

 

 

'KH > JAVA' 카테고리의 다른 글

# 32 String  (0) 2022.07.25
# 31 Wrapper Class  (0) 2022.07.25
# 28 Thread (쓰레드)  (0) 2022.07.20
# 27 Exception Handling( 예외 처리 )  (0) 2022.07.20
# 26 Singleton pattern  (0) 2022.07.19