먼저 낙관적 인 자물쇠와 비관적 자물쇠를 소개하겠습니다.
비관적 잠금 장치 : 항상 최악의 경우를 가정합니다. 내가 데이터를 얻을 때마다 다른 사람들이 데이터를 수정할 것이라고 생각하므로 데이터를 얻을 때마다 잠그므로 다른 사람들이 잠금을 얻을 때까지 차단할 수 있습니다. 기존의 관계형 데이터베이스는 행 잠금, 테이블 잠금 등과 같은 많은 잠금 메커니즘, 읽기 잠금, 잠금 장치 등과 같은 많은 잠금 장치를 사용합니다. 예를 들어, Java에서 동기화 된 키워드 동기화 키워드를 구현하는 것도 비관적 인 잠금 장치입니다.
낙관적 잠금 장치 : 이름에서 알 수 있듯이, 그것은 매우 낙관적입니다. 내가 데이터를 얻을 때마다 다른 사람들이 데이터를 수정하지 않을 것이라고 생각하므로 잠그지 않을 것입니다. 그러나 업데이트 할 때이 기간 동안 다른 사람들이 데이터를 업데이트했는지 여부를 판단하고 버전 번호 및 기타 메커니즘을 사용할 수 있습니다. 낙관적 잠금은 다중 읽기 애플리케이션 유형에 적합하여 처리량을 향상시킬 수 있습니다. 예를 들어, 데이터베이스는 Write_Condition 메커니즘과 유사한 낙관적 잠금을 제공하지만 실제로는 모두 낙관적 잠금으로 제공됩니다. Java에서는 java.util.concurrent의 원자 변수 클래스가 낙관적 잠금을 사용하여 CAS에 의해 구현됩니다.
낙관적 잠금 장치 (비교 및 스왑)의 구현 :
잠금 문제 :
JDK1.5 이전에 Java는 동기화 된 키워드에 의존하여 동기화를 보장했습니다. 이러한 방식으로, 일관된 잠금 프로토콜을 사용하여 공유 상태에 대한 액세스를 조정함으로써, 공유 변수의 잠금 장치를 보유하든 독점적 인 방법을 사용하여 이러한 변수에 액세스 할 수 있도록 할 수 있습니다. 이것은 일종의 독점적 인 잠금입니다. 독점적 인 잠금 장치는 실제로 비관적 인 잠금 장치이므로 동기화 된 것은 비관적 인 자물쇠라고 말할 수 있습니다.
비관적 잠금 장치에는 다음과 같은 문제가 있습니다.
1. 멀티 스레드 경쟁에서 잠금 장치를 추가하고 출시하면 더 많은 컨텍스트 전환 및 스케줄링 지연이 발생하여 성능 문제가 발생합니다.
2. 잠금을 고정하는 실로 인해이 잠금 장치가 걸려있는 다른 모든 스레드가 발생합니다.
3. 우선 순위가 높은 스레드가 우선 순위가 낮은 스레드가 잠금을 해제하기 위해 대기하면 우선 순위 역전을 일으켜 성능 위험이 발생합니다.
비관적 잠금의 이러한 문제와 비교하여 또 다른보다 효과적인 잠금 잠금 장치는 낙관적 잠금입니다. 실제로 낙관적 잠금은 다음과 같습니다. 잠금 장치를 추가하지 않을 때마다 동시 충돌이 없다고 가정하면 작업을 완료합니다. 동시 충돌이 실패하면 성공할 때까지 다시 시도하십시오.
낙관적 자물쇠 :
낙관적 잠금은 위에서 언급되었지만 실제로는 일종의 생각입니다. 비관적 잠금과 비교하여 낙관적 잠금은 데이터가 일반적으로 동시 충돌을 일으키지 않을 것이라고 가정하므로 데이터가 제출되고 업데이트되면 데이터가 동시 충돌이 있는지 공식적으로 감지합니다. 동시 충돌이 발견되면 사용자의 잘못된 정보가 반환되고 사용자는이를 수행하는 방법을 결정합니다.
위에서 언급 한 낙관적 잠금의 개념은 실제로 특정 구현 세부 사항을 설명했습니다. 주로 충돌 감지 및 데이터 업데이트의 두 단계가 포함됩니다. 일반적인 구현 방법 중 하나는 비교 및 스왑 (CAS)입니다.
CAS :
CAS는 낙관적 인 잠금 기술입니다. 여러 스레드가 CAS를 사용하여 동시에 동일한 변수를 업데이트하려고하면 스레드 중 하나만 변수 값을 업데이트 할 수 있고 다른 스레드는 실패 할 수 있습니다. 실패한 스레드는 정지되지 않지만이 경쟁이 실패했으며 다시 시도 할 수 있다고 들었습니다.
CAS 조작에는 3 개의 피연산자가 포함되어 있습니다 - 메모리 위치 (v)는 읽고 작성 해야하는 메모리 위치 (v), 비교를 위해 예상 원본 값 (a) 및 새 값 (b)을 작성합니다. 메모리 위치 v의 값이 예상 된 원래 값 a와 일치하면 프로세서는 위치 값을 새 값 B로 자동 업데이트합니다. 그렇지 않으면 프로세서가 아무것도하지 않습니다. 두 경우 모두 CAS 지침 전에 해당 위치의 값을 반환합니다. (CAS의 일부 특별한 경우, CAS가 성공적이든 아니든, 현재 값을 추출하지 않고도 CAS의 경우에만 가능합니다.) CAS는 "직위 v에 값 A가 포함되어야한다고 생각합니다.이 위치에 B를 포함하면 B를 넣지 말고 위치를 변경하지 말고이 위치의 현재 값을 알려주십시오." 이것은 실제로 낙관적 잠금의 갈등 확인 + 데이터 업데이트의 원칙과 동일합니다.
낙관적 잠금은 일종의 생각이라는 점을 강조하겠습니다. CAS는이 아이디어를 실현하는 방법입니다.
CAS에 대한 Java 지원 :
JDK1.5의 새로운 java.util.concurrent (JUC)는 CAS에 구축됩니다. Synchronized와 같은 차단 알고리즘과 비교하여 CAS는 비 차단 알고리즘의 일반적인 구현입니다. 따라서 JUC는 성능을 크게 향상 시켰습니다.
Java.util.concurrent의 Atomicinteger를 예로 들어 잠금 장치를 사용하지 않고 스레드 안전을 보장하는 방법을 확인하십시오. 우리는 주로 ++ i 작업과 동일 인 getAndincrement 방법을 이해합니다.
Public Class atomicinteger는 숫자를 구현합니다. 공개 최종 int get () {return value; } public final int getAndincrement () {for (;;) {int current = get (); int next = current + 1; if (compareAndset (current, next))을 반환합니다. }} public final boolean compareandset (int reposs, int update) {return unsafe.compareandswapint (valueOffset, expling, update); }}잠금 장치가없는 메커니즘에서 필드 값을 사용하여 스레드 간 데이터가 가시성인지 확인해야합니다. 이렇게하면 변수의 값을 얻을 때 직접 읽을 수 있습니다. 그런 다음 ++가 어떻게 끝나는지 봅시다.
GetAndIncrement는 CAS 작업을 사용하고 메모리에서 데이터를 읽을 때 마다이 데이터에서 CAS 작업과 +1의 결과를 수행합니다. 성공하면 결과가 반환됩니다. 그렇지 않으면 성공할 때까지 다시 시도하십시오.
CompareAndset은 JNI (Java Native Interface)를 사용하여 CPU 지침의 작동을 완료합니다.
공개 Final Boolean CompareAndset (int eppost, int update) {return unsafe.compareAndswapint (this, valueOffset, expling, update); }unsafe.compareAndswapint (this, valueOffset, expling, update); 다음 논리와 유사합니다.
if (this == expect) {this = update return true; } else {return false; }그렇다면이 == 기대,이 두 단계의 원자력을 달성하기 위해이 = 업데이트, 비교를 바꾸는 방법을 비교하는 방법은 무엇입니까? CAS의 원칙을 참조하십시오
CAS 원칙 :
CAS는 JNI 코드를 호출하여 구현됩니다. CompareAndswapint는 C를 사용하여 기본 CPU 명령어를 호출하여 구현됩니다.
다음은보다 일반적으로 사용되는 CPU (Intel X86)의 분석에서 CAS의 구현 원리를 설명합니다.
다음은 sun.misc.unsafe class의 compareandswapint () 메소드의 소스 코드입니다.
공개 최종 네이티브 부울 CompareAndswapint (Object O, Long Offset, int expect, int x);
이것이 로컬 메소드 호출임을 알 수 있습니다. 이 로컬 메소드가 JDK에서 호출하는 C ++ 코드는 다음과 같습니다.
#define lock_if_mp (mp) __asm cmmp mp, 0 / __ __asm je l0 / __asm _emit 0xf0 / __ amas l0 : inline jint atomic :: cmpxchg (jint exchange_value, volatile jint* dest, jint compare_value) {// interlock의 대안을위한 대안 : // __asm {Mov EDX, Dest Mov Ecx, Exchange_Value Mov EAX, Compare_Value Lock_IF_MP (MP) CMMPXCHG DWORD PTR [EDX], ECX}}위의 소스 코드에 표시된 바와 같이, 프로그램은 현재 프로세서 유형에 따라 CMMPXCHG 명령어에 잠금 접두사를 추가할지 여부를 결정합니다. 프로그램이 다중 프로세서에서 실행중인 경우 잠금 접두사를 CMMPXCHG 명령에 추가하십시오. 반대로, 프로그램이 단일 프로세서에서 실행되는 경우 잠금 접두사가 생략됩니다 (단일 프로세서 자체는 단일 프로세서 내에서 순차적 일관성을 유지하고 잠금 접두사가 제공하는 메모리 배리어 효과가 필요하지 않습니다).
CAS 단점 :
1. ABA 질문 :
예를 들어, 스레드 1이 메모리 위치 v에서 A를 꺼내면 다른 스레드 2는 메모리에서 A를 제거하고 2 개는 일부 작업을 수행하고 B가됩니다. 그런 다음 2 개는 V 위치 A에서 데이터를 회전시킵니다.이 시점에서 스레드 One은 CAS 작업을 수행하고 A가 여전히 메모리에 있고 하나는 성공적으로 작동한다는 것을 알게됩니다. Thread One의 CAS 운영이 성공적이지만 숨겨진 문제가있을 수 있습니다. 아래 그림과 같이 :
일방 통행 링크 목록이있는 스택이 있습니다. 스택의 상단은 A입니다. 현재 T1은 A.Next가 B임을 이미 알고 있으며 스택의 상단을 B로 CAS로 바꾸려고합니다.
head.compareAndset (a, b);
T1이 위의 명령을 실행하기 전에 스레드 T2가 개입하고 스택에서 a와 b를 넣은 다음 눌린 다음 c 및 A를 밀어 넣습니다.이 시점에서 스택 구조는 다음과 같습니다.
현재 CAS 작업을 수행하는 스레드 T1의 차례입니다. 감지에 따르면 스택의 상단은 여전히 A이므로 CAS가 성공하고 스택의 상단이 B가되지만 실제로는 B.next가 Null 이므로이 시점의 상황은 다음과 같습니다.
스택에는 하나의 요소 B 만 있으며, 링크 된 목록은 C와 D로 구성되어 더 이상 스택에 존재하지 않습니다. C와 D는 이유없이 버려집니다.
Java 1.5에서 시작하여 JDK의 원자 패키지는 ABA 문제를 해결하기 위해 AtomicStampedReference 클래스를 제공합니다. 이 클래스의 비교 세트 방법은 먼저 현재 참조가 예상 기준과 동일 여부와 현재 플래그가 예상 플래그와 같은지 확인하는 것입니다. 모두 동일하면, 기준과 플래그 값은 주어진 업데이트 된 값으로 설정됩니다.
public boolean compareandset (v explicereference, // 예상 참조 v newReference, // 업데이트 된 참조 int expectStamp, // 예상 플래그 int newstamp // 업데이트 된 플래그)
실제 응용 프로그램 코드 :
개인 정적 AtomicStampedReference <Integer> atomicStampedRef = New AtomicStampedReference <Integer> (100, 0); ......... atomicstampedref.compareAndset (100, 101, 스탬프, 스탬프 + 1);
2. 긴 사이클 시간과 높은 오버 헤드 :
스핀 카스 (실패하면 성공할 때까지 순환 될 때까지 실행됩니다) 오랫동안 실패하면 CPU에 큰 실행 오버 헤드를 가져옵니다. JVM이 프로세서가 제공 한 일시 정지 명령을 지원할 수있는 경우 효율은 어느 정도 향상됩니다. 일시 정지 지침에는 두 가지 기능이 있습니다. 먼저, CPU가 너무 많은 실행 자원을 소비하지 않도록 파이프 라인 실행 지침 (de-pipeline)을 지연시킬 수 있습니다. 지연 시간은 특정 구현 버전에 따라 다릅니다. 일부 프로세서에서 지연 시간은 0입니다. 둘째, 루프를 종료 할 때 메모리 순서 위반으로 인한 CPU 파이프 라인 플러시를 피할 수 있으므로 CPU 실행 효율이 향상됩니다.
3. 공유 변수의 원자 연산 만 보장 할 수 있습니다.
공유 변수에 대한 작업을 수행 할 때는 Cyclic CAS 방법을 사용하여 원자 운영을 보장 할 수 있습니다. 그러나 여러 공유 변수를 작동 할 때 순환 CAS는 작업의 원자력을 보장 할 수 없습니다. 현재 잠금 장치를 사용하거나 여러 공유 변수를 공유 변수로 병합하는 트릭이 있습니다. 예를 들어, 두 가지 공유 변수 i = 2, j = a, IJ = 2a를 병합 한 다음 CAS를 사용하여 IJ를 작동시킵니다. Java 1.5에서 시작하여 JDK는 참조 된 객체 사이의 원자력을 보장하기 위해 원자 평등 클래스를 제공합니다. CAS 조작을 위해 여러 변수를 하나의 객체에 넣을 수 있습니다.
CAS 및 동기화 된 사용 시나리오 :
1. 리소스 경쟁이 적은 상황 (라이트 스레드 충돌)이있는 상황에서는 스레드 차단에 동기화 된 동기화 잠금을 사용하고 사용자 상태 커널 상태 간의 웨이크 업 스위치 및 스위치 작업을 사용하는 것은 CPU 리소스의 추가 낭비입니다. CAS는 하드웨어를 기반으로 구현되지만 커널을 입력 할 필요가 없으며 스레드를 전환 할 필요가 없으며 스핀 작동 가능성이 적으므로 성능이 높아질 수 있습니다.
2. 자원 경쟁이 심각한 상황 (심각한 스레드 충돌)의 경우 CAS 스핀의 확률은 상대적으로 높아서 CPU 자원이 더 많고 동기화 된 것보다 덜 효율적입니다.
보충 : JDK1.6 후에 동기화 된 것이 개선되고 최적화되었습니다. 동기화 된 기본 구현은 주로 잠금 장치 대기열에 의존합니다. 기본 아이디어는 스핀 후 차단하고 경쟁 전환 후에도 잠금을 위해 계속 경쟁하여 공정성을 약간 희생하지만 높은 처리량을 얻는 것입니다. 스레드 충돌이 적을 때 유사한 성능을 얻을 수 있습니다. 심각한 스레드 충돌이 발생하면 성능이 CAS의 성능보다 훨씬 높습니다.
동시 패키지의 구현 :
Java의 CAS는 휘발성 읽기 및 휘발성 쓰기에 대한 메모리 의미를 모두 가지고 있기 때문에 이제 Java 스레드간에 의사 소통하는 4 가지 방법이 있습니다.
1. Thread a는 휘발성 변수를 씁니다. 그런 다음 스레드 B는 휘발성 변수를 읽습니다.
2. thread a는 휘발성 변수를 씁니다. 그런 다음 Thread B는 CAS를 사용하여 휘발성 변수를 업데이트합니다.
3. a. a. a를 사용하여 cas를 사용하여 휘발성 변수를 업데이트 한 다음 스레드 B는 CAS를 사용 하여이 휘발성 변수를 업데이트합니다.
4. a. a를 a를 사용하여 휘발성 변수를 업데이트 한 다음 스레드 B는이 휘발 변수를 읽습니다.
Java의 CAS는 멀티 프로세서 (본질적으로 원자가 읽기-크기-작품을 지원할 수있는 원자가의 동등한 기계를 지원할 수있는 원자체의 동기화를 달성하는 핵심 인 메모리에 대한 읽기 체계 작업을 수행하는 최신 프로세서에서 제공되는 효율적인 기계 수준 원자 지침을 사용합니다. 메모리에서 원자 읽기 체계 쓰기 작업을 수행 할 수있는 지침). 동시에 휘발성 변수의 읽기/쓰기 및 CAS는 스레드 간의 통신을 실현할 수 있습니다. 이러한 기능을 함께 통합하면 전체 동시 패키지 구현의 초석이 형성됩니다. 동시 패키지의 소스 코드 구현을 신중하게 분석하면 일반적인 구현 패턴을 찾을 수 있습니다.
1. 첫째, 공유 변수가 휘발성이라고 선언합니다.
2. 그런 다음 CAS의 원자 조건 업데이트를 사용하여 스레드 간의 동기화를 달성합니다.
3. 동시에, 휘발성의 읽기/쓰기 및 CAS에서 휘발성 읽기 및 쓰기의 메모리 의미를 사용하여 스레드 간의 통신이 달성됩니다.
AQS, 비 차단 데이터 구조 및 원자 변수 클래스 (java.util.concurrent.atomic 패키지의 클래스),이 동시 패키지의 기본 클래스는이 패턴을 사용하여 구현되며 동시 패키지의 고급 클래스는 이러한 기본 클래스에 의존합니다. 일반적인 관점에서 동시 패키지의 구현 다이어그램은 다음과 같습니다.
CAS (힙에 물체의 할당) :
Java는 new object() 를 호출하여 객체를 만들고 JVM 힙에 할당됩니다. 그렇다면이 물체는 힙에 어떻게 저장됩니까?
우선, new object() 실행되면 Java의 다양한 데이터 유형과 얼마나 많은 공간이 고정되어 있기 때문에이 객체가 필요한 공간이 실제로 결정되는 공간이 실제로 결정됩니다 (원칙에 대해 명확하지 않은 경우 직접 Google을 Google하십시오). 다음 작업은이 개체를 저장하기 위해 힙에 공간을 찾는 것입니다.
단일 스레드의 경우 일반적으로 두 가지 할당 전략이 있습니다.
1. 포인터 충돌 : 이것은 일반적으로 절대적으로 규칙적인 메모리에 적용 할 수 있습니다 (메모리가 규칙적이든 메모리 재활용 전략에 따라 다름). 공간을 할당하는 작업은 자유 메모리 측면에서 객체 크기의 거리와 같이 포인터를 움직이는 것입니다.
2. 무료 목록 : 이것은 비 규산 메모리에 적합합니다. 이 경우 JVM은 메모리 영역이 어떤 메모리 영역과 크기인지 기록하기 위해 메모리 목록을 유지합니다. 객체에 공간을 할당 할 때는 무료 목록으로 이동하여 적절한 영역을 쿼리 한 다음 할당하십시오.
그러나 JVM이 항상 단일 스레드 상태로 실행되는 것은 불가능하므로 효율이 너무 열악합니다. 메모리를 다른 객체에 할당 할 때 원자 작동이 아니기 때문에 최소한 다음 단계가 필요합니다. 무료 목록 찾기, 메모리 할당, 무료 목록 수정 등은 안전하지 않습니다. 동시성 동안 보안 문제를 해결하기위한 두 가지 전략도 있습니다.
1. CAS : 실제로, 가상 머신은 CAS를 사용하여 재 시도에 실패하여 업데이트 작업의 원자력을 보장하며, 원리는 위에서 언급 한 것과 동일합니다.
2. TLAB : CAS를 사용하는 경우 실제로 성능에 영향을 미치기 때문에 JVM은보다 진보 된 최적화 전략을 제안했습니다. 각 스레드는 로컬 스레드 할당 버퍼 (TLAB)라고하는 Java 힙에 작은 메모리를 사전에 사전 할당합니다. 스레드가 그 안에 메모리를 할당 해야하는 경우, 스레드 충돌을 피하면서 TLAB에 직접 메모리를 할당하는 것으로 충분합니다. 버퍼 메모리가 사용되고 메모리를 재 할당 해야하는 경우에만 더 큰 메모리 공간을 할당하기 위해 CAS 작동이 수행됩니다.
가상 머신을 사용하는지 여부는 -XX:+/-UseTLAB 매개 변수를 통해 구성 할 수 있습니다 (JDK5 이상 버전은 기본적으로 활성화 됨).
위는이 기사의 모든 내용입니다. 모든 사람의 학습에 도움이되기를 바랍니다. 모든 사람이 wulin.com을 더 지원하기를 바랍니다.