Java의 고유 락, 또는 Intrinsic Lock은 모든 Java 객체가 가지고 있는 잠금 메커니즘이다. 이 메커니즘은 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)란 한 스레드가 이미 획득한 락을 다시 요청할 때 대기하지 않고 계속해서 사용할 수 있는 것을 의미한다. 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 호출 시에도 추가적인 대기 없이 실행할 수 있다.
Lock lock = new ReentrantLock();
try {
lock.lock();
// critical section
} finally {
lock.unlock();
}
위 코드는 ReentrantLock을 사용하여 명시적으로 락을 관리하는 예제이다. 이 방식은 복잡한 락 순서가 필요한 경우 유용하다.
가시성이란 여러 스레드가 동시에 작동할 때 한 스레드가 기록한 값을 다른 스레드가 볼 수 있는지를 의미한다. Java의 고유 락과 ReentrantLock은 스레드 간의 가시성을 보장한다.
public class VisibilityExample {
private boolean stopRequested = false;
public synchronized void requestStop() {
stopRequested = true;
}
public void run() {
while (!stopRequested) {
// 작업 수행
}
}
}
위 예제는 stopRequested 변수의 가시성을 보장하기 위해 synchronized를 사용하여 상태를 안전하게 공유하는 방법을 보여준다.
Java의 고유 락을 사용하면 멀티스레드 환경에서 객체에 안전하게 접근할 수 있다. synchronized 키워드는 동기화된 블록 내에서 한 번에 하나의 스레드만이 접근하도록 하여 데이터의 일관성을 유지한다. 재진입성은 동일 스레드가 동일한 락을 여러 번 획득할 수 있게 하여 개발자가 동기화 코드를 쉽게 작성할 수 있도록 한다. 그러나, 구조적 락의 제약이 있거나 복잡한 락 제어가 필요할 때는 ReentrantLock을 사용하는 것이 좋다. 스레드 간의 가시성을 유지하기 위해 락을 적절히 사용하는 것이 중요하다