데이터를 동시에 공유하고 일관성을 보장하기 위한 도구로서 잠금은 JAVA 플랫폼에서 여러 구현을 갖습니다(예: 동기화 및 ReentrantLock 등). 이미 작성된 이러한 잠금은 개발에 편의를 제공하지만 잠금의 구체적인 특성과 유형은 거의 언급되지 않습니다. 이 기사 시리즈에서는 JAVA의 일반적인 잠금 이름과 특성을 분석하여 질문에 답변합니다.
1. 스핀 잠금
스핀 잠금은 루프 본문 내에서 현재 스레드가 계속해서 실행되도록 하여 구현됩니다. 다른 스레드에 의해 루프 조건이 변경되는 경우에만 임계 섹션에 들어갈 수 있습니다. 다음과 같이 코드를 복사합니다.
공개 클래스 SpinLock {
private AtomicReference<Thread> sign =new AtomicReference<>();
공개 무효 잠금(){
스레드 전류 = Thread.currentThread();
while(!sign .compareAndSet(null, 현재)){
}
}
공개 무효 잠금 해제 (){
스레드 전류 = Thread.currentThread();
sign .compareAndSet(current, null);
}
}
CAS 원자 연산을 사용하여 잠금 기능은 소유자를 현재 스레드로 설정하고 원래 값이 비어 있다고 예측합니다. 잠금 해제 함수는 소유자를 null로 설정하고 예측 값은 현재 스레드입니다.
두 번째 스레드가 잠금 작업을 호출하면 소유자 값이 비어 있지 않기 때문에 첫 번째 스레드가 잠금 해제 함수를 호출하여 소유자를 null로 설정하고 두 번째 스레드가 임계 섹션에 들어갈 수 있을 때까지 루프가 실행됩니다.
스핀락은 스레드 상태를 변경하지 않고 루프 본문을 실행하는 현재 스레드만 유지하므로 응답 속도가 더 빠릅니다. 그러나 스레드 수가 계속 증가하면 각 스레드를 실행해야 하고 CPU 시간을 차지하므로 성능이 크게 저하됩니다. 스레드 경쟁이 심하지 않고 일정 기간 동안 잠금이 유지되는 경우. 스핀 잠금 장치와 함께 사용하기에 적합합니다.
참고: 이 예는 불공평한 잠금입니다. 잠금을 획득하는 순서는 잠금에 들어가는 순서를 기반으로 하지 않습니다.
2. 다른 유형의 스핀락
위에서 스핀 잠금에 대해 이야기했습니다. 스핀 잠금에는 TicketLock, CLHlock 및 MCSlock이라는 세 가지 일반적인 잠금 형식이 있습니다.
티켓 잠금은 주로 액세스 순서 문제를 해결합니다. 주요 문제는 멀티 코어 CPU에 있습니다.
다음과 같이 코드 코드를 복사합니다.
패키지 com.alipay.titan.dcc.dal.entity;
import java.util.concurrent.atomic.AtomicInteger;
공개 클래스 TicketLock {
개인 AtomicInteger serviceNum = new AtomicInteger();
개인 AtomicInteger ticketNum = new AtomicInteger();
private static final ThreadLocal<Integer> LOCAL = new ThreadLocal<Integer>();
공개 무효 잠금() {
int myticket = ticketNum.getAndIncrement();
LOCAL.set(myticket);
while (myticket != serviceNum.get()) {
}
}
공개 무효 잠금 해제() {
int myticket = LOCAL.get();
serviceNum.compareAndSet(myticket, myticket + 1);
}
}
serviceNum 서비스 번호는 매번 쿼리해야 하며 이는 성능에 영향을 미칩니다(메인 메모리에서 읽어야 하며 다른 CPU가 이를 수정하지 못하도록 해야 함).
CLHLock과 MCSLock은 연결된 목록 형식으로 정렬된 두 가지 유사한 공정 잠금 유형입니다.
다음과 같이 코드 코드를 복사합니다.
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
공개 클래스 CLHLock {
공개 정적 클래스 CLHNode {
개인 휘발성 부울 isLocked = true;
}
@SuppressWarnings("사용되지 않음")
개인용 휘발성 CLHNode 꼬리;
private static final ThreadLocal<CLHNode> LOCAL = new ThreadLocal<CLHNode>();
private static final AtomicReferenceFieldUpdater<CLHLock, CLHNode> UPDATER = AtomicReferenceFieldUpdater.newUpdater(CLHLock.class,
CLHNode.class, "꼬리");
공개 무효 잠금() {
CLHNode 노드 = 새로운 CLHNode();
LOCAL.set(노드);
CLHNode preNode = UPDATER.getAndSet(this, node);
if (preNode != null) {
동안(preNode.isLocked) {
}
preNode = null;
LOCAL.set(노드);
}
}
공개 무효 잠금 해제() {
CLHNode 노드 = LOCAL.get();
if (!UPDATER.compareAndSet(this, node, null)) {
node.isLocked = 거짓;
}
노드 = null;
}
}
CLHlock은 계속해서 전구체 변수를 쿼리하므로 NUMA 아키텍처에서 사용하기에 부적합합니다(이 아키텍처에서는 각 스레드가 서로 다른 물리적 메모리 영역에 분산됨).
MCSLock은 지역 변수의 노드를 반복합니다. CLHlock에는 문제가 없습니다.
다음과 같이 코드 코드를 복사합니다.
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
공개 클래스 MCSLock {
공개 정적 클래스 MCSNode {
휘발성 MCSNode 다음;
휘발성 부울 isLocked = true;
}
private static final ThreadLocal<MCSNode> NODE = new ThreadLocal<MCSNode>();
@SuppressWarnings("사용되지 않음")
개인용 휘발성 MCSNode 대기열;
private static final AtomicReferenceFieldUpdater<MCSLock, MCSNode> UPDATER = AtomicReferenceFieldUpdater.newUpdater(MCSLock.class,
MCSNode.class, "큐");
공개 무효 잠금() {
MCSNode currentNode = 새로운 MCSNode();
NODE.set(현재노드);
MCSNode preNode = UPDATER.getAndSet(this, currentNode);
if (preNode != null) {
preNode.next = currentNode;
동안(currentNode.isLocked) {
}
}
}
공개 무효 잠금 해제() {
MCSNode currentNode = NODE.get();
if (currentNode.next == null) {
if (UPDATER.compareAndSet(this, currentNode, null)) {
} 또 다른 {
동안(currentNode.next == null) {
}
}
} 또 다른 {
currentNode.next.isLocked = 거짓;
currentNode.next = null;
}
}
}
코드 관점에서 볼 때 CLH는 MCS보다 간단합니다.
CLH 대기열은 암시적 대기열이며 실제 후속 노드 속성이 없습니다.
MCS 대기열은 실제 후속 노드 속성을 가진 명시적 대기열입니다.
JUC ReentrantLock에서 내부적으로 사용하는 기본 잠금은 CLH 잠금입니다(스핀 잠금을 차단 잠금으로 교체하는 등 많은 개선이 이루어졌습니다).
(전체 텍스트 끝)