Java의 15가지 유형의 잠금 소개
많은 동시성 기사를 읽으면서 공정한 잠금, 낙관적 잠금 등 다양한 잠금이 언급됩니다. 이 기사에서는 다양한 잠금의 분류를 소개합니다. 소개 내용은 다음과 같습니다.
공정한 잠금 / 불공정한 잠금
재진입 잠금 / 비재진입 잠금
전용잠금 / 공유잠금
뮤텍스 잠금 / 읽기-쓰기 잠금
낙관적 잠금 / 비관적 잠금
분할 잠금
바이어스 잠금 장치 / 경량 잠금 장치 / 중량 잠금 장치
스핀락
위의 내용은 많은 자물쇠 명사입니다. 이러한 분류는 모두 자물쇠의 상태를 의미하는 것은 아니며 일부는 자물쇠의 특성을 의미하고 일부는 자물쇠의 디자인을 의미합니다. 아래에 요약된 내용은 각 자물쇠에 대한 특정 설명입니다. 명사.
공정한 잠금 / 불공정한 잠금
공정한 잠금
공정한 잠금은 여러 스레드가 잠금을 적용한 순서대로 잠금을 획득함을 의미합니다.
불공정 잠금
불공정한 잠금은 여러 스레드가 잠금을 획득하는 순서가 잠금을 적용하는 순서와 다르다는 것을 의미합니다. 나중에 적용되는 스레드가 먼저 적용되는 스레드보다 먼저 잠금을 획득할 수 있습니다. 우선순위 반전이나 기아 현상이 발생할 가능성이 있습니다.
Java ReentrantLock의 경우 생성자를 통해 잠금이 공정한 잠금인지 여부를 지정하며 기본값은 불공정한 잠금입니다. 불공정 잠금의 장점은 처리량이 공정 잠금보다 크다는 것입니다. 동기화의 경우에도 불공정 잠금입니다. ReentrantLock과 같은 AQS를 통한 스레드 스케줄링을 구현하지 않기 때문에 공정한 잠금으로 전환할 수 있는 방법이 없습니다.
재진입 잠금 / 비재진입 잠금
재진입 잠금
넓은 의미에서 재진입 잠금은 외부 계층에서 잠금이 사용된 후에도 교착 상태 없이 내부 계층에서 계속 사용할 수 있는 잠금을 의미합니다(동일한 개체 또는 클래스인 경우). ) 이러한 잠금을 재진입 잠금이라고 합니다. ReentrantLock과 syncised는 모두 재진입 잠금입니다
동기화된 무효 setA()에서 예외 발생{
Thread.sleep(1000);
setB();
}
동기화된 무효 setB()에서 예외 발생{
Thread.sleep(1000);
}
위 코드는 재진입 잠금의 특징으로, 재진입 잠금이 아닌 경우 현재 스레드에서 setB가 실행되지 않아 교착 상태가 발생할 수 있습니다.
재입장 금지
재진입 잠금과 달리 비재진입 잠금은 재귀적으로 호출할 수 없으며 재귀 호출이 이루어지면 교착 상태가 발생합니다. 비재진입 잠금을 시뮬레이션하기 위해 스핀 잠금을 사용하는 고전적인 설명을 봤습니다. 코드는 다음과 같습니다
java.util.concurrent.atomic.AtomicReference 가져오기;
공개 클래스 UnreentrantLock {
private AtomicReference
공개 무효 잠금() {
스레드 전류 = Thread.currentThread();
//이 문장은 AtomicInteger
에서도 볼 수 있는 매우 고전적인 "스핀" 구문입니다. (;;) {
if (!owner.compareAndSet(null, 현재)) {
반품;
}
}
}
공개 무효 잠금 해제() {
스레드 전류 = Thread.currentThread();
owner.compareAndSet(현재, null);
}
}
코드도 상대적으로 간단합니다. 동일한 스레드가 lock() 메서드를 두 번 호출하여 잠금을 해제하면 두 번째 스핀이 호출될 때 교착 상태가 발생합니다. 이 잠금은 재진입이 아니지만 실제로는 동일한 스레드가 매번 잠금을 해제하고 잠금을 획득할 필요가 없습니다.
재진입 잠금으로 전환:
java.util.concurrent.atomic.AtomicReference 가져오기;
공개 클래스 UnreentrantLock {
private AtomicReference
개인 정수 상태 = 0;
공개 무효 잠금() {
스레드 전류 = Thread.currentThread();
if (현재 == owner.get()) {
상태++;
반품;
}
//이 문장은 AtomicInteger
에서도 볼 수 있는 매우 고전적인 "스핀" 구문입니다. (;;) {
if (!owner.compareAndSet(null, 현재)) {
반품;
}
}
}
공개 무효 잠금 해제() {
스레드 전류 = Thread.currentThread();
if (현재 == owner.get()) {
if (상태 != 0) {
상태--;
} 그 외 {
owner.compareAndSet(현재, null);
}
}
}
}
각 작업을 실행하기 전에 현재 잠금 보유자가 현재 개체인지 확인하고 매번 잠금을 해제하는 대신 상태 계산을 사용합니다.
ReentrantLock
에서 재진입 잠금 구현 불공정 잠금에 대한 잠금 획득 방법은 다음과 같습니다.
final boolean nonfairTryAcquire(int acquires) {
최종 스레드 현재 = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState (0, 획득)) {
setExclusiveOwnerThread(현재);
true를 반환합니다.
}
}
//이거다
else if (현재 == getExclusiveOwnerThread()) {
int nextc = c + 획득;
if (nextc < 0) // 오버플로
새로운 오류 발생("최대 잠금 횟수 초과");
setState(nextc);
true를 반환합니다.
}
거짓을 반환;
}
재진입 횟수를 계산하고 빈번한 보류 및 해제 작업을 방지하기 위해 AQS에서는 비공개 휘발성 int 상태가 유지됩니다. 이는 효율성을 향상시킬 뿐만 아니라 교착 상태도 방지합니다.
전용잠금 / 공유잠금
배타적 잠금과 공유 잠금. C.U.T 패키지에서 ReeReentrantLock과 ReentrantReadWriteLock을 읽어보면 그 중 하나는 배타적 잠금이고 다른 하나는 공유 잠금임을 알 수 있습니다.
배타적 잠금: 이 잠금은 한 번에 하나의 스레드만 보유할 수 있습니다.
공유 잠금: 이 잠금은 여러 스레드에 의해 공유될 수 있습니다. 일반적인 예는 ReentrantReadWriteLock의 읽기 잠금입니다. 해당 읽기 잠금은 공유할 수 있지만 쓰기 잠금은 한 번만 사용할 수 있습니다.
또한 읽기 잠금을 공유하면 동시 읽기가 매우 효율적이지만 읽기와 쓰기, 쓰기와 읽기는 상호 배타적입니다.
배타적 잠금 및 공유 잠금도 AQS를 통해 구현되며 배타적 잠금 또는 공유 잠금을 달성하기 위해 다양한 방법이 구현됩니다. 싱크로나이즈드의 경우 당연히 배타적 잠금입니다.
뮤텍스 잠금 / 읽기-쓰기 잠금
뮤텍스 잠금
공유 리소스에 액세스하기 전에 잠그고 액세스가 완료된 후에 잠금을 해제합니다. 잠긴 후 다시 잠그려는 다른 스레드는 현재 프로세스가 잠금 해제될 때까지 차단됩니다.
잠금 해제 시 둘 이상의 스레드가 차단되면 잠금의 모든 스레드가 준비 상태로 프로그래밍되고 첫 번째 스레드가 잠금 작업을 수행하고 다른 스레드는 다시 대기합니다. 이런 방식으로 단 하나의 스레드만 뮤텍스로 보호되는 리소스에 액세스할 수 있습니다
읽기-쓰기 잠금
읽기-쓰기 잠금은 뮤텍스 잠금이자 공유 잠금입니다. 읽기 모드는 공유되고 쓰기 모드는 상호 배타적입니다(배타적 잠금).
읽기-쓰기 잠금에는 읽기 잠금 상태, 쓰기 잠금 상태, 잠금 해제 상태의 세 가지 상태가 있습니다
Java에서 읽기-쓰기 잠금의 구체적인 구현은 ReadWriteLock
입니다. 한 번에 하나의 스레드만 쓰기 모드에서 읽기-쓰기 잠금을 보유할 수 있지만, 여러 스레드가 읽기 모드에서 동시에 읽기-쓰기 잠금을 보유할 수 있습니다. 하나의 스레드만이 쓰기 상태 잠금을 점유할 수 있지만 여러 스레드가 동시에 읽기 상태 잠금을 점유할 수 있으므로 높은 동시성을 달성할 수 있습니다. 쓰기 상태 잠금이 설정되어 있으면 잠금을 획득하려는 스레드는 쓰기 상태 잠금이 해제될 때까지 차단됩니다. 읽기 상태 잠금이 설정되어 있으면 다른 스레드는 읽기 상태 잠금을 획득할 수 있지만 읽기-쓰기 잠금이 스레드가 원하는 것을 감지하면 쓰기 작업을 시도하려는 스레드가 쓰기 상태 잠금을 얻지 못하도록 방지하기 위해 모든 스레드의 읽기 상태 잠금이 해제될 때까지 쓰기 상태 잠금을 얻을 수 없습니다. 쓰기 상태 잠금을 얻으려면 읽기 상태 잠금을 얻으려는 모든 후속 스레드를 차단합니다. 따라서 읽기-쓰기 잠금은 쓰기 작업보다 리소스에 대한 읽기 작업이 훨씬 더 많은 상황에 매우 적합합니다.
낙관적 잠금 / 비관적 잠금
비관적 잠금
항상 최악의 시나리오를 가정하십시오. 데이터를 얻으러 갈 때마다 다른 사람들이 데이터를 수정할 것이라고 생각하므로 데이터를 얻을 때마다 잠그게 됩니다. 잠금을 얻을 때까지 차단합니다(공유 리소스 한 번에 하나의 스레드에서만 사용되며 다른 스레드는 차단되고 리소스는 사용 후 다른 스레드로 전송됩니다). 이러한 많은 잠금 메커니즘은 행 잠금, 테이블 잠금, 읽기 잠금, 쓰기 잠금 등과 같은 기존 관계형 데이터베이스에서 사용되며 작업 전에 모두 잠깁니다. Java의 동기화 및 ReentrantLock과 같은 배타적 잠금은 비관적 잠금 아이디어를 구현한 것입니다.
낙관적 잠금
항상 최선의 상황을 가정하고 데이터를 얻으러 갈 때마다 다른 사람이 수정하지 않을 것이라고 생각하여 잠기지 않을 것입니다. 그러나 업데이트할 때 이 기간 동안 다른 사람이 데이터를 업데이트했는지 여부를 판단하게 됩니다. 버전 번호 메커니즘과 CAS 알고리즘 구현을 사용할 수 있습니다. 낙관적 잠금은 처리량을 향상시킬 수 있는 다중 읽기 애플리케이션 유형에 적합합니다. 데이터베이스에서 제공하는 write_condition 메커니즘은 실제로 제공되는 낙관적 잠금입니다. Java에서는 java.util.concurrent.atomic 패키지의 원자 변수 클래스가 낙관적 잠금 구현 방법인 CAS를 사용하여 구현됩니다.
분할 잠금
세그먼트 잠금은 실제로 특정 잠금이 아닌 잠금 설계입니다. ConcurrentHashMap의 경우 동시성 구현은 세그먼트 잠금 형태로 효율적인 동시 작업을 달성하는 것입니다.
동시 컨테이너 클래스의 잠금 메커니즘은 세분화된 분할 잠금을 기반으로 하며 분할 잠금은 다중 동시 프로그램의 성능을 향상시키는 중요한 수단 중 하나입니다.
동시 프로그램에서 직렬 작업은 확장성을 감소시키고 컨텍스트 전환도 성능을 감소시킵니다. 이 두 가지 문제는 잠금에 대한 경쟁이 발생할 때 발생합니다. 제한된 리소스를 보호하기 위해 배타적 잠금을 사용하는 경우 이는 기본적으로 한 번에 하나의 스레드만 액세스할 수 있는 직렬 방법입니다. 따라서 확장성에 대한 가장 큰 위협은 배타적 잠금입니다.
일반적으로 잠금 경쟁 정도를 줄이는 세 가지 방법이 있습니다. 1. 잠금 유지 시간을 줄입니다. 2. 잠금 요청 빈도를 줄입니다. 3. 조정 메커니즘을 갖춘 배타적 잠금을 사용하면 더 높은 동시성을 허용합니다.
어떤 경우에는 잠금 분해 기술을 추가로 확장하여 독립적인 개체 집합에 대한 잠금을 분해할 수 있으며 이는 분할된 잠금이 됩니다.
사실 간단히 말하면:
컨테이너에는 여러 개의 잠금이 있으며 각 잠금은 컨테이너의 데이터 일부를 잠그는 데 사용됩니다. 그러면 여러 스레드가 컨테이너의 서로 다른 데이터 세그먼트에 있는 데이터에 액세스할 때 스레드 간에 잠금 경쟁이 발생하지 않게 됩니다. ConcurrentHashMap에서 사용하는 잠금 분할 기술은 먼저 데이터를 세그먼트로 나누어 저장한 후 스레드가 잠금을 점유하여 액세스할 때 할당됩니다. 데이터 세그먼트의 경우, 다른 스레드에서도 다른 세그먼트의 데이터에 액세스할 수 있습니다.
예: ConcurrentHashMap에서는 16개의 잠금을 포함하는 배열이 사용됩니다. 각 잠금은 모든 해시 버킷의 1/16을 보호하고 N번째 해시 버킷은 (N mod 16)번째 잠금으로 보호됩니다. 합리적인 해싱 알고리즘을 사용하여 키를 균등하게 분배한다고 가정하면 잠금 요청을 대략 1/16로 줄일 수 있습니다. ConcurrentHashMap이 최대 16개의 동시 쓰기 스레드를 지원할 수 있는 것이 바로 이 기술입니다.
바이어스 잠금 장치 / 경량 잠금 장치 / 중량 잠금 장치
잠금 상태:
잠금 해제 상태
바이어스 잠금 상태
경량 잠금 상태
헤비급 잠금 상태
잠금 상태는 개체 헤더의 개체 모니터 필드에 표시됩니다. 4개 주는 경쟁과 함께 점차 확대될 것이며, 이는 되돌릴 수 없는 과정, 즉 다운그레이드될 수 없는 과정입니다. 이 네 가지 상태는 Java 언어의 잠금이 아니라 잠금 획득 및 해제(동기화 사용 시)의 효율성을 향상시키기 위해 Jvm에서 수행한 최적화입니다.
바이어스 잠금
편향된 잠금은 동기화 코드 조각이 항상 하나의 스레드에 의해 액세스되고 스레드가 자동으로 잠금을 획득한다는 것을 의미합니다. 잠금 획득 비용을 줄입니다.
경량
경량 잠금은 잠금이 편향된 잠금이고 다른 스레드가 액세스할 때 편향된 잠금이 경량 잠금으로 업그레이드됨을 의미합니다. 다른 스레드는 스핀을 통해 잠금을 획득하려고 시도하므로 차단되지 않고 성능이 향상됩니다.
헤비웨이트 잠금장치
Heavyweight Lock은 Lock이 Lightweight Lock일 때 다른 Thread가 회전하고 있어도 회전이 계속되지 않고 일정 횟수만큼 회전하고 Lock을 획득하지 못한 경우 Blocking 상태가 되는 것을 의미합니다. 헤비급 잠금 장치로 확장됩니다. 무거운 잠금 장치는 다른 적용 스레드를 차단하고 성능을 저하시킵니다.
스핀락
CAS 알고리즘은 낙관적 잠금의 구현 방법이라는 것을 알고 있습니다. CAS 알고리즘에는 스핀 잠금도 포함되므로 여기서는 스핀 잠금이 무엇인지 설명하겠습니다.
CAS 알고리즘에 대한 간략한 검토
CAS는 영어 단어 Compare and Swap(비교 앤 스왑)으로 유명한 Lock-Free 알고리즘입니다. 락 프리 프로그래밍이란 락을 사용하지 않고 여러 스레드 사이에서 변수를 동기화하는 것, 즉 스레드가 차단되지 않고 변수를 동기화하는 것을 의미하므로 Non-Blocking 동기화라고도 합니다. CAS 알고리즘에는 세 개의 피연산자가 포함됩니다
읽고 써야 하는 메모리 값 V
A
비교할 값 쓰여질 새로운 가치 B
변수를 업데이트할 때 변수의 기대값 A가 메모리 주소 V의 실제 값과 동일한 경우에만 메모리 주소 V에 해당하는 값이 B로 수정되고, 그렇지 않으면 아무런 작업도 수행되지 않습니다. 일반적으로 이는 스핀 작업, 즉 지속적인 재시도입니다.
스핀락이란 무엇입니까?
스핀 잠금(spinlock): 스레드가 잠금을 획득할 때 다른 스레드가 잠금을 획득한 경우 스레드는 루프에서 대기한 후 잠금을 획득할 때까지 잠금을 성공적으로 획득할 수 있는지 계속 판단합니다. 루프.
공유 자원을 보호하기 위해 제안된 잠금 메커니즘입니다. 실제로 스핀 잠금은 뮤텍스 잠금과 유사하며 둘 다 특정 리소스의 상호 배타적 사용을 해결하도록 설계되었습니다. 뮤텍스 잠금이든 스핀 잠금이든 언제든지 최대 하나의 홀더가 있을 수 있습니다. 즉, 최대 하나의 실행 단위가 언제든지 잠금을 얻을 수 있습니다. 그러나 두 가지 일정 메커니즘은 약간 다릅니다. 뮤텍스 잠금의 경우 리소스가 이미 점유된 경우 리소스 신청자는 절전 상태로만 들어갈 수 있습니다. 그러나 스핀 잠금으로 인해 호출자가 잠자기 상태가 되지는 않습니다. 다른 실행 단위가 스핀 잠금을 보유하고 있는 경우 호출자는 스핀 잠금 보유자가 잠금을 해제했는지 확인하기 위해 계속 루프를 돌게 됩니다. 그것이 그 이름을 얻은 이유입니다.
Java에서 스핀 잠금을 구현하는 방법은 무엇입니까?
다음은 간단한 예입니다:
공개 클래스 SpinLock {
private AtomicReference
공개 무효 잠금() {
스레드 전류 = Thread.currentThread();
//CAS 사용
while (!cas.compareAndSet(null, 현재)) {
// 아무것도 하지 마세요
}
}
공개 무효 잠금 해제() {
스레드 전류 = Thread.currentThread();
cas.compareAndSet(현재, null);
}
}
lock() 메서드는 CAS를 사용합니다. 첫 번째 스레드 A가 잠금을 획득하면 성공적으로 획득할 수 있으며, 이때 스레드 A가 잠금을 해제하지 않으면 다른 스레드 B가 다시 잠금을 획득합니다. 이때 CAS가 만족되지 않기 때문에 A 스레드가 잠금을 해제하기 위해 Unlock 메소드를 호출할 때까지 CAS가 만족되는지 계속 판단하는 while 루프에 들어갑니다.
스핀 잠금 문제
1. 스레드가 잠금을 너무 오랫동안 유지하면 잠금을 획득하기 위해 대기 중인 다른 스레드가 루프에 들어가 CPU를 소비하게 됩니다. 부적절하게 사용하면 CPU 사용량이 매우 높아질 수 있습니다. 2. 위의 Java에서 구현된 스핀 잠금은 공평하지 않습니다. 즉, 잠금을 먼저 획득하기 위해 대기 시간이 가장 긴 스레드를 만족시킬 수 없습니다. 불공정한 잠금은 "스레드 부족" 문제를 야기합니다.
스핀락의 장점
1. 스핀 잠금은 스레드 상태를 전환하지 않으며 항상 사용자 상태에 있습니다. 즉, 스레드가 항상 활성 상태가 되어 스레드가 차단 상태로 들어가지 않으므로 불필요한 컨텍스트 전환이 줄어듭니다. 2. Non-spin 잠금을 획득할 수 없으면 잠금이 차단 상태로 들어가고 잠금이 획득되면 커널 상태에서 복원해야 합니다. 스레드 컨텍스트 전환이 필요합니다. (스레드가 차단된 후 커널(Linux) 스케줄링 상태로 들어갑니다. 이로 인해 시스템이 사용자 모드와 커널 모드 사이를 왔다 갔다 하게 되어 잠금 성능에 심각한 영향을 미치게 됩니다.)
재진입 스핀 잠금 및 비재진입 스핀 잠금
기사 시작 부분의 코드를 주의 깊게 분석하면 재진입을 지원하지 않는다는 것을 알 수 있습니다. 즉, 스레드가 처음으로 잠금을 획득한 경우 잠금이 해제되기 전에 다시 잠금을 획득할 수 없습니다. 두 번째로 성공했습니다. CAS가 만족되지 않기 때문에 두 번째 획득은 while 루프에 들어가고 재진입 잠금인 경우 두 번째 획득에 성공해야 합니다.
게다가 두 번째 획득에 성공하더라도 처음 잠금이 해제되면 두 번째 획득한 잠금도 해제되는 것은 무리한 일이다.
재진입 잠금을 구현하려면 잠금을 획득한 스레드 수를 기록하는 카운터를 도입해야 합니다.
공개 클래스 ReentrantSpinLock {
private AtomicReference
비공개 정수 개수;
공개 무효 잠금() {
스레드 전류 = Thread.currentThread();
if (current == cas.get()) { // 현재 스레드가 잠금을 획득한 경우 스레드 수를 1만큼 늘린 후
를 반환합니다. 카운트++;
반품;
}
//락을 획득하지 못한 경우 CAS를 통해 스핀
while (!cas.compareAndSet(null, 현재)) {
// 아무것도 하지 마세요
}
}
공개 무효 잠금 해제() {
스레드 cur = Thread.currentThread();
if (cur == cas.get()) {
if (count > 0) {//0보다 큰 경우 현재 스레드가 잠금을 여러 번 획득했음을 의미하며 잠금 해제는 횟수를 1씩 감소시켜 시뮬레이션됩니다
백작--;
} else {// count==0이면 잠금이 해제될 수 있으므로 잠금을 획득한 횟수가 잠금이 해제된 횟수와 일치하도록 할 수 있습니다.
cas.compareAndSet(cur, null);
}
}
}
}
스핀 잠금 및 뮤텍스 잠금
스핀 잠금과 뮤텍스 잠금은 모두 리소스 공유를 보호하는 메커니즘입니다.
스핀 잠금이든 뮤텍스 잠금이든 언제든지 최대 하나의 홀더가 있을 수 있습니다.
뮤텍스 잠금을 획득한 스레드가 이미 점유된 경우 스레드는 절전 상태로 전환됩니다. 스핀 잠금을 획득한 스레드는 절전 상태가 아니지만 잠금이 해제될 때까지 루프에서 계속 대기합니다.
위 내용은 Java의 다양한 잠금은 무엇입니까?의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!