Synchronized

 

여러개의 스레드가 한개의 자원을 사용하고자 할 때, 현재 데이터를 사용하고 있는 해당 스레드를 제외하고 나머지 스레드들은 데이터에 접근 할 수 없도록 막는 개념이라고 보면 된다.

 

 

Synchronized는 메서드와 변수에 선언하여 사용이 가능하다.

 

// 1. 메서드에서 사용하는 경우

public synchronized void method(){
	// 코드
}

 

// 2. 객체 변수에 사용하는 경우

private Object obj = new Object();
public void exampleMethod(){
	synchronized(obj){
		//코드 
	}
}

동기화가 안되어 있을 때 발생할 수 있는 문제는 어떤게 있을까?

 

가장 대표적으로 행 계좌에 연결하여 생각할 수 있다.

아래의 코드를 보자

 

package ThreadSynchronized;

public class ThreadSynchronizedTest {

	public static void main(String[] args) {
	// TODO Auto-generated method stub
		Task task = new Task();
	        Thread t1 = new Thread(task);
	        Thread t2 = new Thread(task);
	        


	        t1.setName("t1-Thread");
	        t2.setName("t2-Thread");
	        
	        t1.start();
	        t2.start();
	}

}
class Account{
    int balance = 1000;
     
    public void withDraw(int money){
    
        if(balance >= money){
            try{
            	Thread thread = Thread.currentThread();
            	System.out.println(thread.getName() + " 출금 금액 ->> "+money);
                Thread.sleep(1000);
                balance -= money;
                System.out.println(thread.getName()+" balance:" + balance);
                
            }catch (Exception e) {}
      
        }
    }
}
class Task implements Runnable{
    Account acc = new Account();
     
    @Override
    public void run() {
        while(acc.balance > 0){
            // 100, 200, 300 중의 한 값을 임의로 선택해서 출금(withDraw)한다.
            int money = (int)(Math.random() * 3 + 1) * 100;
            acc.withDraw(money);
      
        }
    }
}

 

 

Account 라는 클래스에는 balance(잔액)이 있고, 이 잔액을 지속적으로 인출을 하는 인출메서드가 있다.

 

Runnable을 구현하여 만든 Task에 100,200,300 중 랜덤하게 값을 전달받아 moneny 변수에 할당하고 그 금액만큼 Account 인스턴스의 인출메서드를 호출해 balance (잔액)을 출금한다.

 

다음 main 메서드에서 스레드 t1, t2를 만들고 각각의 스레드 이름을 정의한다.

t1, t2 스레드가 시작하면 잔액이 0이 될 때까지 두 스레드가 경쟁하며 출금이 시작되는 것을 확인할 수 있다.

 

결과화면 

 

결과를 보면 분명 잔액이 인출할 금액 이상일때만 인출이 되도록 했지만 잔액이 마이너스가 될 때까지 인출이 된 것을 확인할 수 있다.

 

여기서 멀티스레드의 문제점을 알 수 있다.

 

balance(잔액) thread-safe가 되지 않았기 때문에 아래와 같은 로직이 진행된다.

 

  1.  t1 스레드가 잔액을 삭감하기 전에 
  2.  t2가 balance(잔액)에 접근해 삭감을 해버리고
  3.  다시 t1이 slee()에서 깨어나 balance(잔액) 을 삭감

 

그렇게 잔액이 0 이하의 마이너스 값을 가지게 된다.


해결방법

 

해결방법은 의외로 간단하다.

공유데이터에 대한 접근과 수정이 이루어지는 메서드에 synchronized 키워드를 리턴타입 앞에 붙여주면 된다.

 

  1. t1스레드가 먼저 공유데이터나 메서드에 점유하고 있는 상태인 경우 block으로 처리하기 때문에 t1 이외의 스레드의 접근을 막는다. 
  2. t1스레드가 작업을 다 끝냈다면 .unblock으로 처리하여 t1 이외의 스레드의 접근을 허락한다.
package ThreadSynchronized;

public class ThreadSynchronizedTest {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		   Task task = new Task();
	        Thread t1 = new Thread(task);
	        Thread t2 = new Thread(task);
	        
	        t1.setName("t1-Thread");
	        t2.setName("t2-Thread");
	        
	        t1.start();
	        t2.start();
	}

}
class Account{
    int balance = 1000;
     
    public synchronized void withDraw(int money){
    
        if(balance >= money){
            try{
            	Thread thread = Thread.currentThread();
            	System.out.println(thread.getName() + " 출금 금액 ->> "+money);
                Thread.sleep(1000);
                balance -= money;
                System.out.println(thread.getName()+" balance:" + balance);
                
            }catch (Exception e) {}
      
        }
    }
}
class Task implements Runnable{
    Account acc = new Account();
     
    @Override
    public void run() {
        while(acc.balance > 0){
            // 100, 200, 300 중의 한 값을 임의로 선택해서 출금(withDraw)한다.
            int money = (int)(Math.random() * 3 + 1) * 100;
            acc.withDraw(money);
      
        }
    }
}

 

결과화면

 

synchronized 키워드를 사용함으로써 balance 공유데이터에 대한 thread-safe를 시켰기 때문에
데이터나 메서드 점유하고 있는 스레드가 온전히 자신의 작업을 마칠 수 있게 되었다.

+ Recent posts