TIL

  • 스레드의 동기화

 


 

스레드의 동기화

  • 어떤 스레드가 진행 중인 작업을 다른 스레드가 간섭하지 못하게 막는 것
  • 여러 스레드가 같은 자원을 공유하기 때문에 메모리도 공유를 한다.
  • 그래서 A 스레드가 작업하던 것을 마치지 못하고 다른 스레드로 차례가 넘어갔을 때 A 스레드가 다른 스레드 작업에 영향을 줄 수 있다.
  • 진행 중인 작업이 다른 스레드에게 간섭받지 않게 하려면 동기화가 필요한다.
  • 동기화를 하기 위해서는?
    • 간섭받지 않아야 하는 문장들을 임계영역으로 설정한다.
    • 임계영역은 락(lock🔒)을 얻은 단 하나의 스레드만 출입이 가능하다. (객체 1개에 락 1개)

 

 

 

 

synchronized keyword를 이용한 동기화

// 1. 메소드 전체를 임계영역으로 지정
public synchronized void calcSum() {
    // 임계영역
}

// 2. 특정한 영역을 임계영역으로 지정
synchronized(객체의 참조변수) {
    // 임계영역
}
  • synchronized는 임계영역을 설정할 때 사용한다.
  • 가능하면 영역을 최소화 해야한다. 2번 방식을 권장한다.

 

 

 

package org.example;

public class Main {
    public static void main(String[] args) {
        Runnable r = new RunnableEx();
        new Thread(r).start();
        new Thread(r).start();
    }


}

class Account {
    private int balance = 1000; // private으로 해야 동기화가 의미가 있다. public이면 아무데서나 값 바꿀 수 있기 때문에.

    public int getBalance() {
        return balance;
    }

    public void withdraw(int money) {
        if (balance >= money) {
            try { Thread.sleep(1000); } catch (InterruptedException e) {}
            balance -= money;
        }
    }
}

class RunnableEx implements Runnable {
    Account account = new Account();

    @Override
    public void run() {
        while (account.getBalance() > 0) {
            // 100, 200, 300중의 한 값을 임의로 선택해서 출금(withdraw)
            int money = (int)(Math.random() * 3 + 1) * 100;
            account.withdraw(money);
            System.out.println("balance: "+account.getBalance());
        }
    }
}
balance: 700
balance: 700
balance: 400
balance: 400
balance: 300
balance: 100
balance: 100
balance: -100
balance: 0

잔금에 (-)값이 출력됐다. withdraw(int money)를 보면 if (balance >= money) 이 조건문으로 인해 (-)값은 절대 안 나올 것 같은데 대체 왜 (-)값이 출력됐을까?

 

왜냐하면 withdraw(int money)syncronized 키워드가 붙지않아서다. 그림을 보자. 스레드1은 잔금이 200원인 상태에서 100원을 출금하려 한다. 메소드를 실행해 if문 안의 코드를 실행하던 중에 주어진 작업시간이 끝났다. 동기화가 되어있지 않으므로 스레드2가 들어와 기존 잔금 200원에 200을 출금하려 한다. 모든 코드를 다 실행하고 빠져나간다. 스레드1이 다시 작업을 시작한다. 그러나 잔금과 출금 금액을 비교하는 if문은 이미 통과했고 잔금은 스레드2에 의해 0원이 됐으므로 0원에서 100원을 출금하면 (-)값이 나와버린다.

 

withdraw(int money) 메소드에 syncronized 키워드를 붙이면 스레드1이 작업을 할 때, lock을 갖고 들어와서 해당 작업이 끝날 때까지 스레드2가 접근하지 못한다. 그리고 사실 getBalance()에도 syncronized를 붙여주는게 맞다. 잔금을 get하는 과정에서도 동기화가 되어있어야 하니까.

 

 

 

 

📚 참고
자바의 정석