멀티 쓰레드 프로세스에서는 다른 쓰레드의 작업에 영향을 미칠 수 있다.
그러므로 진행중인 작업이 다른 쓰레드에게 간섭받지 않게 하려면 '동기화' 가 필요하다.
쓰레드의 동기화 : 한 쓰레드가 진행중인 작업을 다른 쓰레드가 간섭하지 못하게 막는 것
동기화를 하려면 간섭받지 않아야 하는 문장들을 '임계 영역'으로 설정한다.
임계 영역은 락(lock)을 얻은 단 하나의 쓰레드만 출입이 가능하다. (객체 1개당 락 1개)
다중 스레드 환경에서 데이터의 일관성을 유지하고 경쟁 조건을 방지하기 위한 동기화 메커니즘 중 하나이다.
여러 스레드가 공유 데이터 또는 리소스에 동시에 접근하는 경우 문제가 발생할 수 있다. 락은 이러한 문제를 방지하기 위해 특정 코드 영역에 대한 동기화를 제공한다.
다중 스레드 환경에서의 문제 중 하나는 "경쟁 조건(Race Condition)"이다. 경쟁 조건은 두 개 이상의 스레드가 공유 데이터에 동시에 접근하고, 적절한 동기화 없이 데이터가 변경되거나 읽힐 때 발생할 수 있는 문제이다.
락은 이러한 경쟁 조건을 방지하고 상호 배제(mutual exclusion)를 제공하여 한 번에 하나의 스레드만이 특정 코드 영역에 접근할 수 있도록 한다.
메서드 전체를 임계 영역으로 설정한다.
해당 메서드를 호출하는 스레드가 해당 객체의 락을 획득하고, 메서드 실행이 완료되면 락을 해제한다.
이 경우, 메서드가 정적 메서드인지 인스턴스 메서드인지에 따라 Class Lock 또는 Instance Lock 이 적용된다.
// Class Lock을 사용한 예시 (정적 메서드)
public static synchronized void staticMethod() {
// 메서드 전체가 임계 영역, Class Lock이 획득됨
}
// Instance Lock을 사용한 예시 (인스턴스 메서드)
public synchronized void instanceMethod() {
// 메서드 전체가 임계 영역, 현재 인스턴스에 대한 Instance Lock이 획득됨
}
클래스 수준에서 동기화를 담당하는 락
정적 메서드에 synchronized 키워드를 사용하거나, 클래스 객체를 사용하여 동기화를 구현한다.
public class MyClass {
// Class Lock을 사용한 정적 메서드
public static synchronized void staticMethod() {
// Class Lock 범위: MyClass 클래스의 모든 인스턴스에 대한 락
}
// Class Lock을 사용한 정적 블록
public static void staticBlock() {
synchronized (MyClass.class) {
// Class Lock 범위: MyClass 클래스의 모든 인스턴스에 대한 락
}
}
}
staticMethod 는 클래스 수준에서 동기화되며, 해당 클래스의 모든 인스턴스 간에 락이 공유된다. 즉, 동일 클래스 내의 모든 객체가 해당 메서드에 동시에 액세스하지 못하도록 보장한다.
인스턴스 수준에서 동기화를 담당하는 락
일반 메서드에 synchronized 키워드를 사용하거나, 객체를 사용하여 동기화를 구현한다.
public class MyClass {
// Instance Lock을 사용한 인스턴스 메서드
public synchronized void instanceMethod() {
// Instance Lock 범위: 현재 객체(this)에 대한 락
}
// Instance Lock을 사용한 인스턴스 블록
public void instanceBlock() {
synchronized (this) {
// Instance Lock 범위: 현재 객체(this)에 대한 락
}
}
}
instanceMethod 는 각 인스턴스에 대한 락을 소유하며, 서로 다른 인스턴스는 독립적으로 동작한다. 즉, 한 인스턴스에서 해당 메서드가 실행 중일 때 다른 인스턴스에서는 동일한 메서드에 동시에 액세스할 수 있다.
하지만 임계 영역은 많을 수록 성능이 저하되기 때문에 최소화 해야한다. (임계 1번당 1쓰레드)
그러므로 메소드 전체를 임계 영역으로 지정하는 것은 지양한다.
특정 코드 블록을 임계 영역으로 설정한다.
특정 객체의 락을 획득하고, 블록 내의 코드를 실행한 후 락을 해제한다.
public class SynchronizedBlockExample {
// 공유 자원
private int sharedResource = 0;
// 락 객체
private final Object lockObject = new Object();
// 동기화된 블록을 사용한 메서드
public void synchronizedMethod() {
// 다른 비동기적인 작업들...
synchronized (lockObject) {
// synchronized 키워드를 사용한 특정 코드 블록
// 여러 스레드가 접근할 수 있는 공유 자원에 대한 작업 수행
sharedResource++;
// 동기화된 블록을 벗어나면 락이 자동으로 해제됨
}
// 다른 비동기적인 작업들...
}
}
둘 다 다중 스레드 환경에서 데이터 일관성을 유지하고 경쟁 조건을 방지하기 위해 사용된다.
여기서 중요한 점은 Class Lock은 클래스 전체에 대한 락을 관리하고, Instance Lock은 해당 인스턴스에 대한 락을 관리한다는 것이다. 이러한 락을 적절하게 활용하여 동시성 문제를 해결할 수 있다.
[자바의 정석 - 기초편] ch13-30~33 쓰레드의 동기화
https://youtu.be/g4vP5wuAoPI?si=SgpLg4c7f79XcwES