멀티스레딩 환경에서 잠금(Lock)이 필요한 이유
멀티스레딩 환경에서는 두 개 이상의 스레드가 동시에 공유 자원에 접근할 수 있으며, 이로 인해 시스템이 일관성 없는 동작을 보일 위험이 있습니다. 자바(Java)는 이러한 문제를 방지하기 위해 잠금(Lock) 개념을 사용하여 공유 자원이나 객체에 대한 동시 접근을 제한합니다. 자바의 잠금은 크게 두 가지 수준으로 나눌 수 있습니다.
- 객체 수준 잠금(Object Level Lock) : 비정적(non-static) 메서드나 코드 블록을 한 번에 오직 하나의 스레드만 접근하도록 제한하고 싶을 때 사용합니다.
- 클래스 수준 잠금(Class Level Lock) : 런타임에 존재하는 해당 클래스의 모든 인스턴스 중 어느 것이라도, 여러 스레드가 synchronized 블록에 동시에 진입하지 못하도록 할 때 사용합니다. 정적(static) 데이터를 스레드 안전하게 만들 때 반드시 사용해야 합니다.
객체 수준 잠금과 클래스 수준 잠금 비교
| 번호 | 구분 | 객체 수준 잠금 | 클래스 수준 잠금 |
|---|---|---|---|
| 1 | 기본 개념 | 비정적(non-static) 메서드나 코드 블록을 오직 하나의 스레드만 접근하도록 할 때 사용합니다. | 런타임에 존재하는 모든 인스턴스에서 여러 스레드가 synchronized 블록에 진입하지 못하도록 할 때 사용합니다. |
| 2 | Static / Non-static | 비정적(non-static) 데이터를 스레드 안전하게 만들 때 사용해야 합니다. | 정적(static) 데이터를 스레드 안전하게 만들 때 사용해야 합니다. |
| 3 | 잠금의 개수 | 해당 클래스의 모든 객체가 각각 자신만의 고유한 잠금을 가집니다. | 인스턴스가 여러 개 존재하더라도, 클래스 객체(Class Object)에 대한 잠금은 항상 단 하나만 존재합니다. |
잠금을 획득하는 방법
1. 객체 수준 잠금 획득
- 인스턴스 메서드에
synchronized키워드를 붙입니다. - 코드 블록 내에서
synchronized(this)를 사용합니다.
2. 클래스 수준 잠금 획득
- 정적(static) 메서드에
synchronized키워드를 붙입니다. - 코드 블록 내에서
synchronized(ClassName.class)를 사용합니다.
클래스 수준 잠금 예제
public class ClassLevelLockExample {
public void classLevelLockMethod() {
synchronized (ClassLevelLockExample.class) {
//여기에 작업 코드를 작성합니다
}
}
}
위 예제처럼 ClassName.class를 대상으로 synchronized 블록을 작성하면, 해당 클래스의 어떤 인스턴스를 통해서 호출되더라도 동일한 클래스 잠금을 공유하게 됩니다.
객체 수준 잠금 예제
public class ObjectLevelLockExample {
public void objectLevelLockMethod() {
synchronized (this) {
//여기에 작업 코드를 작성합니다
}
}
}
synchronized(this)를 사용하면 현재 인스턴스에 대한 잠금을 획득합니다. 따라서 서로 다른 인스턴스의 메서드는 서로 간섭 없이 병렬로 실행될 수 있습니다.
정리
두 잠금의 핵심 차이는 잠금의 범위입니다. 객체 수준 잠금은 인스턴스 단위로 동작하며 인스턴스마다 독립적인 잠금을 가지는 반면, 클래스 수준 잠금은 JVM당 클래스당 하나만 존재하므로 정적 데이터나 전역적으로 공유되는 자원을 보호할 때 적합합니다. 상황에 맞는 잠금 수준을 선택하면 불필요한 성능 저하 없이 스레드 안전성을 확보할 수 있습니다.