8월 7일 - 고유 락

Yullgiii·2024년 8월 7일
0

[Java] 고유 락 (Intrinsic Lock)과 동기화

Java의 고유 락, 또는 Intrinsic Lock은 모든 Java 객체가 가지고 있는 잠금 메커니즘이다. 이 메커니즘은 Synchronized 블록을 통해서 스레드의 접근을 제어하는 데 사용된다. 이를 통해 멀티스레드 환경에서 안전하게 객체에 접근할 수 있다.

Intrinsic Lock / Synchronized Block / Reentrancy

Intrinsic Lock

  • Java의 모든 객체는 고유한 락을 가지고 있다.
  • Synchronized 블록은 이러한 락을 이용하여 스레드의 접근을 제어한다.
  • Synchronized 메서드나 블록은 한 번에 하나의 스레드만 접근할 수 있도록 한다.
public class Counter {
    private int count;

    public int increase() {
        return ++count;  // Thread-safe 하지 않은 연산
    }
}


## 질문: ++count 문이 원자적(atomic) 연산인가?
-: 아니다. ++count는 read (count 값을 읽음) -> modify (count 값 수정) -> write (count 값 저장)의 세 단계로 구성되므로, 여러 스레드가 동시에 접근할 경우 동시성 문제가 발생할 수 있다.

## Synchronized 블록을 사용한 Thread-safe Case
```java
public class Counter {
    private Object lock = new Object(); // 모든 객체가 가능 (Lock이 있음)
    private int count;

    public int increase() {
        synchronized(lock) { // lock을 이용하여, count 변수에의 접근을 막음
            return ++count;
        }
    }
}

위 코드는 lock 객체를 사용하여 count에 대한 동시 접근을 막는다. 이렇게 함으로써 count 변수에 대한 모든 접근이 스레드 안전하게 된다.

public class Counter {
    private int count;

    public synchronized int increase() {
        return ++count;
    }
}

위 코드처럼 synchronized 키워드를 메서드에 직접 사용하여 더 간단하게 구현할 수 있다.

Reentrancy

재진입성(Reentrancy)란 한 스레드가 이미 획득한 락을 다시 요청할 때 대기하지 않고 계속해서 사용할 수 있는 것을 의미한다. Java의 고유 락은 재진입성을 지원하여 동일한 스레드가 여러 번 락을 획득할 수 있도록 한다.

public class Reentrancy {
    // b가 Synchronized로 선언되어 있더라도, a 진입시 lock을 획득하였음.
    // b를 호출할 수 있게 됨.
    public synchronized void a() {
        System.out.println("a");
        b();
    }
    
    public synchronized void b() {
        System.out.println("b");
    }
    
    public static void main(String[] args) {
        new Reentrancy().a();
    }
}

위 코드는 a 메서드가 b를 호출할 때 재진입성을 보여준다. a에서 락을 획득하면 b 호출 시에도 추가적인 대기 없이 실행할 수 있다.

Structured Lock vs Reentrant Lock

  • Structured Lock: Java의 고유 락(synchronized 블록)은 구조적 락으로, 락의 획득과 해제가 명확한 블록 범위 내에서 이루어진다.
  • Reentrant Lock: 명시적 락으로, 더 복잡한 락 획득/해제 순서가 필요할 때 사용된다.
Lock lock = new ReentrantLock();
try {
    lock.lock();
    // critical section
} finally {
    lock.unlock();
}

위 코드는 ReentrantLock을 사용하여 명시적으로 락을 관리하는 예제이다. 이 방식은 복잡한 락 순서가 필요한 경우 유용하다.

Visibility (가시성)

가시성이란 여러 스레드가 동시에 작동할 때 한 스레드가 기록한 값을 다른 스레드가 볼 수 있는지를 의미한다. Java의 고유 락과 ReentrantLock은 스레드 간의 가시성을 보장한다.

  • 문제: 하나의 스레드가 쓴 값을 다른 스레드가 볼 수 없으면 문제가 발생할 수 있다.
  • 원인: 최적화를 위해 컴파일러나 CPU에서 발생하는 코드 재배열로 인해, CPU 코어의 캐시 값이 메모리에 제때 기록되지 않아 발생할 수 있다.
public class VisibilityExample {
    private boolean stopRequested = false;

    public synchronized void requestStop() {
        stopRequested = true;
    }

    public void run() {
        while (!stopRequested) {
            // 작업 수행
        }
    }
}

위 예제는 stopRequested 변수의 가시성을 보장하기 위해 synchronized를 사용하여 상태를 안전하게 공유하는 방법을 보여준다.

So...

Java의 고유 락을 사용하면 멀티스레드 환경에서 객체에 안전하게 접근할 수 있다. synchronized 키워드는 동기화된 블록 내에서 한 번에 하나의 스레드만이 접근하도록 하여 데이터의 일관성을 유지한다. 재진입성은 동일 스레드가 동일한 락을 여러 번 획득할 수 있게 하여 개발자가 동기화 코드를 쉽게 작성할 수 있도록 한다. 그러나, 구조적 락의 제약이 있거나 복잡한 락 제어가 필요할 때는 ReentrantLock을 사용하는 것이 좋다. 스레드 간의 가시성을 유지하기 위해 락을 적절히 사용하는 것이 중요하다

profile
개발이란 무엇인가..를 공부하는 거북이의 성장일기 🐢

0개의 댓글