멀티스레드 프로그램에서 스레드들이 번갈아 실행되는 스케쥴은 임의로 정해지게 되는데, 적절하지 못한 순간에 다른 스레드로 제어가 넘어가서 예기치 못한 문제가 발생할 수 있습니다. 이런 문제는 주로 공유 데이터를 사용할 때 일어나는데, 이런 부분을 Critical section(임계영역)이라 합니다.

 

이러한 Critical section을 스레드가 사용할 때, 다른 스레드가 중간에 끼어들 수 없도록 만드는 것을 Critical section Synchronization(동기화)라고 합니다.

 

 

위 그림과 같은 기능을 하는 프로그램을 만들어보겠습니다.

 

먼저 계좌정보가 들어가는 Account 클래스(Account.java)입니다.

class Account {
    String accountNo;    // 계좌번호
    String ownerName;    // 예금주 이름
    int balance;         // 잔액
    Account(String accountNo, String ownerName, int balance) {   
        this.accountNo = accountNo; 
        this.ownerName = ownerName;  
        this.balance = balance;       
    }
    void  deposit(int amount)  {       
        balance += amount;
    }
    int withdraw(int amount) {
        if (balance < amount)
            return 0;
        balance -= amount;
        return amount;
    }
}

 

 

공유영역 클래스(SaredArea.java)

class SharedArea {
    Account account1;   // 이몽룡의 계좌
    Account account2;   // 성춘향의 계좌
}

 

 

계좌를 이체하는 스레드 클래스(TransferThread.java)

class TransferThread extends Thread {
    SharedArea sharedArea;
    TransferThread(SharedArea area) {   // 생성자
        sharedArea = area;
    }
    public void run() {
       for (int cnt = 0; cnt < 12; cnt ++) {
      	   sharedArea.account1.withdraw(1000000);
            System.out.print("이몽룡 계좌: 100만원 인출,");
            sharedArea.account2.deposit(1000000);
            System.out.println("성춘향 계좌: 100만원 입금");
        }
    }
}

 

계좌 잔액 합계를 출력하는 스레드 클래스(PrintThread.java)

class PrintThread extends Thread {
    SharedArea sharedArea;
    PrintThread(SharedArea area) {   // 생성자
        sharedArea = area;
    }
    public void run() {
       for (int cnt = 0; cnt < 3; cnt ++) {
     		   int sum = sharedArea.account1.balance +
    				   sharedArea.account2.balance;
    		   System.out.println("계좌 잔액 합계: " + sum);
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
                System.out.println(e.getMessage());
            }
        }
    }
}

 

실행결과

이몽룡 계좌: 100만원 인출,계좌 잔액 합계: 29000000
성춘향 계좌: 100만원 입금
이몽룡 계좌: 100만원 인출,성춘향 계좌: 100만원 입금
이몽룡 계좌: 100만원 인출,성춘향 계좌: 100만원 입금
이몽룡 계좌: 100만원 인출,성춘향 계좌: 100만원 입금
이몽룡 계좌: 100만원 인출,성춘향 계좌: 100만원 입금
이몽룡 계좌: 100만원 인출,성춘향 계좌: 100만원 입금
이몽룡 계좌: 100만원 인출,성춘향 계좌: 100만원 입금
이몽룡 계좌: 100만원 인출,성춘향 계좌: 100만원 입금
이몽룡 계좌: 100만원 인출,성춘향 계좌: 100만원 입금
이몽룡 계좌: 100만원 인출,성춘향 계좌: 100만원 입금
이몽룡 계좌: 100만원 인출,성춘향 계좌: 100만원 입금
이몽룡 계좌: 100만원 인출,성춘향 계좌: 100만원 입금
계좌 잔액 합계: 30000000
계좌 잔액 합계: 30000000

 

프로그램을 몇번 실행하다보면, 계좌 잔액 합계가 : 30000000이 아닌, 29000000 으로 나오는 경우가 생깁니다.

실행결과를 살펴보면 왜 이런경우가 생겼는지 알 수 있습니다. 이몽룡 계좌에서 100만원이 인출되고 난 이후 성춘향 계좌에 100만원을 입금을 해야하는데, 그 중간에 PrintThread로 실행의 제어가 넘어가서 계좌 잔액 합계가 출력이 되어버린 것입니다.

이러한 경우가 생기는 것을 방지하기 위해서 Critical sectionSynchronization가 필요합니다.

그렇다면 위의 소스중에서 Critical section이 어디인지 알아야합니다.

 

위의 소스에서는 두가지 부분이 Critical section이 될 수 있는데, 아래와 같습니다.

 

TransferThread.java

            sharedArea.account1.withdraw(1000000);
            System.out.print("이몽룡 계좌: 100만원 인출,");
            sharedArea.account2.deposit(1000000);
            System.out.println("성춘향 계좌: 100만원 입금");

 

PrintThread.java

          int sum = sharedArea.account1.balance + sharedArea.account2.balance;

 

이 두 영역에 대해서는 Synchronization가 필요합니다. Synchronization지정은 아래와 같이 지정하면 됩니다.

 

계좌를 이체하는 스레드 클래스(TransferThread.java)

class TransferThread extends Thread {
    SharedArea sharedArea;
    TransferThread(SharedArea area) {   // 생성자
        sharedArea = area;
    }
    public void run() {
       for (int cnt = 0; cnt < 12; cnt ++) {
    	   synchronized(sharedArea){ 
    	   sharedArea.account1.withdraw(1000000);
            System.out.print("이몽룡 계좌: 100만원 인출,");
            sharedArea.account2.deposit(1000000);
            System.out.println("성춘향 계좌: 100만원 입금");
    	   }
        }
    }
}

 

계좌 잔액 합계를 출력하는 스레드 클래스(PrintThread.java)

class PrintThread extends Thread {
    SharedArea sharedArea;
    PrintThread(SharedArea area) {   // 생성자
        sharedArea = area;
    }
    public void run() {
       for (int cnt = 0; cnt < 3; cnt ++) {
    	   synchronized(sharedArea){
    		   int sum = sharedArea.account1.balance +
    				   sharedArea.account2.balance;
    		   System.out.println("계좌 잔액 합계: " + sum);
    	   }
    	   
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
                System.out.println(e.getMessage());
            }
        }
    }
}

 

 

synchronized(sharedArea){}부분은, sharedArea부분과 밀접한 관련이 있기 때문에, sharedArea클래스 안에 메소드로 선언할 수 있습니다.

 

 

공유영역 클래스(SaredArea.java)

 

class SharedArea {
    Account account1;   // 이몽룡의 계좌
    Account account2;   // 성춘향의 계좌
    
    void transfer(){  //인출, 입금 메소드
 	   synchronized(this){ 
 		 account1.withdraw(1000000);
         System.out.print("이몽룡 계좌: 100만원 인출,");
         account2.deposit(1000000);
         System.out.println("성춘향 계좌: 100만원 입금");
 	   }
    }
    
    int getTotal(){ //합계구하는 메소드
    	synchronized(this){
		   return account1.balance + account2.balance;
    	}
    }
}

계좌를 이체하는 스레드 클래스(TransferThread.java)

class TransferThread extends Thread {
    SharedArea sharedArea;
    TransferThread(SharedArea area) {   // 생성자
        sharedArea = area;
    }
    public void run() {
       for (int cnt = 0; cnt < 12; cnt ++) {
    	   	sharedArea.transfer();
        }
    }
}

계좌 잔액 합계를 출력하는 스레드 클래스(PrintThread.java)

class PrintThread extends Thread { SharedArea sharedArea; PrintThread(SharedArea area) { // 생성자 sharedArea = area; } public void run() { for (int cnt = 0; cnt < 3; cnt ++) { int sum=sharedArea.getTotal(); System.out.println("계좌 잔액 합계: " + sum); } try { Thread.sleep(1); } catch (InterruptedException e) { System.out.println(e.getMessage()); } } }

 

 

메소드의 내용 전체가 블록일 경우 synchronized(this){}말고 메소드 전체를 synchronized 할 수 있습니다.

 

 

공유영역 클래스(SaredArea.java)

class SharedArea {
    Account account1;   // 이몽룡의 계좌
    Account account2;   // 성춘향의 계좌
    
    synchronized void transfer(){
 	   	 account1.withdraw(1000000);
         System.out.print("이몽룡 계좌: 100만원 인출,");
         account2.deposit(1000000);
         System.out.println("성춘향 계좌: 100만원 입금");
    }
    
    synchronized int getTotal(){
    	  return account1.balance + account2.balance;
    }
}