자바에서의 동시성 문제 해결
Java에서 동시성 문제 해결 방법
1. syncrhonized : 한번에 한 쓰레드만 접근 가능하게 함. 특정 section에 락을 걸고 그 섹션이 끝나면 자동으로 락이 풀림. 암묵적인 락.
2. ReentrantLock (Java SE 7) : syncrhonized보다 다양한 기능을 제공. 직접 락을 걸고 해제해야됨. 락이 걸려있을때 synchronized처럼 OS Thread 레벨에서 블락되는게 아니기 때문에 VirtualThread에서 사용 가능한 쓰레드 방식. (synchronized를 VirtualThread에서 쓰면 pinning이 일어남). 내부적으로 Unsafe를 사용해 구현됨(Compare And Set 메소드)
3. java.util.concurrent 패키지 컬렉션 사용 : ConcurrentHashMap, BlockingQueue 등... 이들은 내부적으로 volatile 변수, synchronized, reentrantlock, unsafe클래스 등을 활용
4. Atomic클래스 : concurrent 패키지에 있는 콜렉션중 일부인데 AtomicInteger 등의 클래스가 있다 .CAS(Compare And Swap)으로 동기화하기 때문에 lock-free하고 높은 동시성이 요구되는 환경에서 좋은 퍼포먼스가 나옴. 내부적으로 volatile과 unsafe 사용
concurrent 콜렉션이 동시성 문제를 해결하는 여러 가지 기법들
1. volatile 변수
메모리에 직접 읽고 씀으로써 CPU Cache와 메모리 상의 값 불일치 문제 해결.
가장 최신의 값을 보장함.
변수를 동기화하기 위해 근본적으로 가장 중요한 개념 중 하나. (하나의 쓰레드가 쓰고있고 나머지가 읽고있을때 읽고있는 쓰레드들이 캐시값이 아니라 최신의 메모리값을 가져오게 하는데에 쓰임. 당연히 cpu cache를 이용하는 일반적인 방법보다 연산이 비싸지만 load performance는 normal performance랑 엄청 큰 차는 없다고함)
2. Unsafe class
코어 자바 라이브러리에서만 쓰고 일반 유저들은 사용하지 말라고 만든 클래스.
로우레벨 메커니즘을 사용하기 위해 만들어짐
메모리 할당을 직접 받을수도 있고 atomic한 compareAndSet을 지원하는데, compare and set이 될때까지 busy waiting함.
예시
@IntrinsicCandidate
public final int getAndAddInt(Object o, long offset, int delta) {
int v;
do {
v = getIntVolatile(o, offset);
} while (!weakCompareAndSetInt(o, offset, v, v + delta)); // <- busy waiting
return v;
}
근본적으로 가장 중요한 Compare And Swap
특정 메모리에 있는 값이 내가 기대하는 값이랑 일치하면(compare해서 확인) 내가 준값으로 swap한다.
public final native boolean compareAndSetInt(Object o, long offset,
int expected,
int x);
위는 자바로 코드가 구현되어 있지 않은 네이티브 함수.
o는 오브젝트 레퍼런스, long은 offset -> C언어에서처럼 o + offset으로 메모리 주소 계산
expected는 내가 기대하는 값
x는 바꾸고 싶은 값
예를 들어 lock이 false면 true로 바꾸고싶을때 사용 가능
오프셋은 Unsafe클래스로 이렇게 가져옴
private static final long STATE
= U.objectFieldOffset(AbstractQueuedSynchronizer.class, "state"); // U가 Unsafe클래스 인스턴스
objectFieldOffset함수는
private native long objectFieldOffset1(Class<?> c, String name);
public long objectFieldOffset(Class<?> c, String name) {
if (c == null || name == null) {
throw new NullPointerException();
}
return objectFieldOffset1(c, name);
}