운영체제

Synchronized

으엉어엉 2024. 12. 15. 17:14
728x90

멀티스레드를 사용할 때 가장 주의해야 할 점은, 같은 자원(리소스)에 여러 스레드가 동시에 접근할 때 발생하는 동시성 문제이다. 참고로 여러 스레드가 접근하는 자원을 공유 자원이라고 한다. 대표적인 공유 자원은 인스턴스의 필드(멤버 변수)이다. 멀티스레드를 사용할 때는 이런 공유 자원에 대한 접근을 적절하게 동기화(synchronization)해서 동시성 문제가 발생하지 않게 방지하는 것이 중요하다.

 

import static util.MyLogger.log;
import static util.ThreadUtils.sleep;

public class BankMain {
    public static void main(String[] args) throws InterruptedException {
        BankAccount account = new BankAccountV1(1000);
        Thread t1 = new Thread(new WithdrawTask(account,800),"t1");//800원 출금
        Thread t2 = new Thread(new WithdrawTask(account,800),"t1");//800원 출금

        t1.start();
        t2.start();

        sleep(500); // 검증 완료까지 잠시 대기
        log("t1 state: " + t1.getState());
        log("t2 state: " + t2.getState());

        t1.join();
        t2.join();
        log("최종 잔액: "+ account.getBalance());
    }
}

이 검증 로직을 통과하고 바로 잔액을 줄였다면 이런 문제가 발생하지 않겠지만, t1 이 검증 로직을 통과하고 잔액을 줄이기도 전에 먼저 t2 가 검증 로직을 확인한 것이다.

한 번에 하나의 스레드만 실행 출금()이라는 메서드를 한 번에 하나의 스레드만 실행할 수 있게 제한한다면 어떻게 될까?

이렇게 하면 공유 자원인 balance 를 한 번에 하나의 스레드만 변경할 수 있다. 따라서 계산 중간에 다른 스레드가 balance 의 값을 변경하는 부분을 걱정하지 않아도 된다. (참고로 여기서는 출금 메서드를 호출할 때만 잔액 balance )의 값이 변경된다.)

 

 

임계 영역

  • 여러 스레드가 동시에 접근하면 데이터 불일치나 예상치 못한 동작이 발생할 수 있는 위험하고 또 중요한 코드 부 분을 뜻한다.
  • 여러 스레드가 동시에 접근해서는 안 되는 공유 자원을 접근하거나 수정하는 부분을 의미한다.

출금 로직이 바로 임계 영역이다. balance 는 여러 스레드가 동시에 접근해서는 안되는 공유 자원이다. 이러한 하나의 스레만 접근할 수 있도록 임계 영역을 보호 할 수 있는 것이 synchronized이다.

 

@Override
public synchronized boolean withdraw(int amount) {
    log("거래 시작: " + getClass().getSimpleName());
    log("[검증 시작] 출금액: " + amount + ", 잔액: " + balance);
    //잔고가 출금액 보다 작으면?
    if (balance < amount) {
        log("[검증 실패] 출금액: " + amount + ", 잔액: " + balance);
        return false;
    }
    log("[검증 완료] 출금액: " + amount + ", 잔액: " + balance);
    sleep(1000); // 출금에 걸리는 시간으로 가정
    balance = balance - amount;
    log("[출금 완료] 출금액: " + amount + ", 변경 잔액: " + balance);
    log("거래 종료");
    return true;
}

synchronized를 붙히면 하나의 스레드만 사용이 가능하다.

 

모든 객체(인스턴스)는 내부에 자신만의 락(lock) 을 가지고 있다.  - > 모니터 락(monitor lock)이라도고 부른다. 객체 내부에 있기에 우리가 확인하기는 어렵다. 

스레드가 synchronized 키워드가 있는 메서드에 진입하려면 반드시 해당 인스턴스의 락이 있어야 한다.

T1이 먼저 들어가면 T2는 BLOCKED 상태로 무한정 대기를 한다. LOCK이 풀릴때 까지. CPU 실행 스케쥴링에 또한 들어가지 않는다. Interrupt도 안된다. 

 

락을 획득하는 순서는 보장되지 않는다.  이때 어떤 순서로 락을 획득하는지는 자바 표준에 정의되어 있지 않다. 따라서 순서를 보장하지 않고, 환경에 따라서 순서가 달라질 수 있다. volatile 를 사용하지 않아도 synchronized 안에서 접근하는 변수의 메모리 가시성 문제는 해결된다. 둘중 뭐를 언제 쓸지 한번 더 생각해보는 것이 좋을 것 같다.

지선생님 답변

 

 

synchronized 코드 블럭

synchronized 의 가장 큰 장점이자 단점은 한 번에 하나의 스레드만 실행할 수 있다는 점이다. 여러 스레드가 동시에 실행하지 못하기 때문에, 전체로 보면 성능이 떨어질 수 있다. 따라서 synchronized 를 통해 여러 스레드를 동시에 실행할 수 없는 코드 구간은 필요한 곳으로 한정해서 설정해야 한다.

 

이런식으로도 사용 가능하다. 조금 더 메소드보다 디테일하게 synchronized 를 사용할 수 있다.

synchronized (this) {
    log("[검증 시작] 출금액: " + amount + ", 잔액: " + balance);
    if (balance < amount) {
        log("[검증 실패] 출금액: " + amount + ", 잔액: " + balance);
        return false;
    }
    log("[검증 완료] 출금액: " + amount + ", 잔액: " + balance);
    sleep(1000);
    balance = balance - amount;
    log("[출금 완료] 출금액: " + amount + ", 변경 잔액: " + balance);
}

 

자바에서 동기화(synchronization)는 여러 스레드가 동시에 접근할 수 있는 자원(예: 객체, 메서드)에 대해 일관성 있 고 안전한 접근을 보장하기 위한 메커니즘이다. 동기화는 주로 멀티스레드 환경에서 발생할 수 있는 문제, 예를 들어 데이터 손상이나 예기치 않은 결과를 방지하기 위해 사용된다

728x90

'운영체제' 카테고리의 다른 글

생산자 소비자 문제  (0) 2024.12.18
concurrent Lock  (0) 2024.12.16
Volatile - 메모리가시성  (0) 2024.12.15
Thread - interrupt  (0) 2024.12.14
Thread - Join  (0) 2024.12.14