잠금 장치의 개념은 [높은 동시성 Java 1]의 도입에서 언급되었다. JDK 소스 코드에는 많은 수의 잠금 응용 프로그램이 있으므로 여기에는 잠금이 소개됩니다.
1 Lockless 클래스의 원칙에 대한 자세한 설명
1.1 CAS
CAS 알고리즘의 프로세스는 다음과 같습니다. 3 개의 매개 변수 CAS (v, e, n)가 포함됩니다. v는 업데이트 될 변수를 나타내고, e는 예상 값을 나타내고, n은 새 값을 나타냅니다. v
값이 e 값과 같으면 v 값이 N으로 설정됩니다. v 값이 e 값과 다르면 다른 스레드가 이미 업데이트를 수행했으며 현재 스레드는 아무것도하지 않음을 의미합니다. 마지막으로, CAS는 현재 V. CAS 운영의 실제 가치를 반환합니다. CAS 운영은 낙관적 인 태도로 수행되며 항상 운영을 성공적으로 완료 할 수 있다고 생각합니다. 여러 스레드가 CAS를 사용하여 동시에 변수를 작동하면 하나만 승리하고 업데이트하고 나머지는 실패합니다. 실패한 스레드가 매달리지 않으며, 실패가 허용되고 다시 시도 할 수 있으며, 실패한 스레드는 작업을 포기할 수 있습니다. 이 원칙에 따라 Cas
작업은 즉시 잠겨 있으며 다른 스레드는 현재 스레드에 대한 간섭을 감지하고 적절하게 처리 할 수 있습니다.
CAS에는 너무 많은 단계가 있음을 알게 될 것입니다. v와 e가 동일하다고 판단 한 후에 값을 할당하려고 할 때 스레드를 전환하고 값을 변경할 수 있습니까? 데이터 불일치를 일으킨 원인은 무엇입니까?
실제로이 걱정은 중복됩니다. CAS의 전체 작동 프로세스는 원자 연산이며 CPU 명령에 의해 완료됩니다.
1.2 CPU 지침
CAS의 CPU 명령은 CMMPXCHG입니다
지침 코드는 다음과 같습니다.
/ * Accumulator = al, ax 또는 eax, 바이트, 단어 또는 더블 워드 비교가 수행되는지에 따라 */ if (accumulator == 대상) {zf = 1; 대상 = 소스; } else {zf = 0; 축합기 = 대상; } 대상 값이 레지스터의 값과 같으면 점프 플래그가 설정되고 원래 데이터가 대상으로 설정됩니다. 기다리지 않으면 점프 깃발을 설정하지 않습니다.
Java는 많은 자물쇠 수업을 제공하므로 아래에 자물쇠가없는 클래스를 소개하겠습니다.
2 쓸모없는
우리는 이미 자물쇠가 차단보다 훨씬 효율적이라는 것을 알고 있습니다. Java 가이 잠금 장치를 어떻게 구현하는지 살펴 보겠습니다.
2.1. Atomicinteger
Integer와 같은 Atomicinteger는 둘 다 숫자 클래스를 상속합니다
공개 클래스 atomicinteger는 숫자 구현 java.io.serializable을 구현합니다
Atomicinteger에는 많은 CAS 작업이 있습니다. 일반적으로 다음과 같습니다.
공개 최종 부울 비교 (int expect, int update) {
insafe.compareandswapint를 반환합니다 (valueOffset, expling, update);
}
여기서는 insafe.compareandswapint 메소드를 설명합니다. 이 클래스의 오프셋 인 변수의 값이 valueOffset이 예상 값과 동일하다면이 변수의 값을 업데이트하도록 설정한다는 것을 의미합니다.
실제로 오프셋 valueOffset의 변수는 값입니다.
static {try {valueOffset = unsafe.objectfieldoffset (atomicinteger.class.getDeclaredfield ( "value")); } catch (Exception Ex) {Throw New Error (예 :); }}우리는 CAS가 실패 할 수 있다고 말하지만 실패 비용은 매우 적으므로 일반적인 구현은 성공할 때까지 무한 루프에 있습니다.
public final int getAndincrement () {for (;;) {int current = get (); int next = current + 1; if (compareAndset (current, next))을 반환합니다. }}2.2 안전하지 않습니다
클래스 이름에서 안전하지 않은 운영은 다음과 같은 비 안전한 작업임을 알 수 있습니다.
오프셋에 따라 값을 설정합니다 (방금 소개 된 Atomicinteger 에서이 기능을 보았습니다).
park () (이 스레드 중지, 향후 블로그에서 언급 될 것입니다)
기본 CAS 작동 비 공공 API는 JDK 버전에서 크게 다를 수 있습니다.
2.3. 원자 회의
Atomicinteger는 앞에서 언급되었으며 물론 Atomicboolean, Atomiclong 등은 모두 비슷합니다.
우리가 여기서 소개하고 싶은 것은 원자 회의입니다.
Atomicreference는 템플릿 클래스입니다
공개 클래스 Atomicreference <v>는 java.io.serializable을 구현합니다
모든 유형의 데이터를 캡슐화하는 데 사용할 수 있습니다.
예를 들어, 문자열
패키지 테스트; import java.util.concurrent.atomic.atomicreference; public class test {public final static atomicreference <string> atomicstring = new atomicreference <string> ( "Hosee"); public static void main (String [] args) {for (int i = 0; i <10; i ++) {Final int num = i; new Thread () {public void Run () {try {thread.sleep (math.abs ((int) math.random ()*100)); } catch (예외 e) {e.printstacktrace (); } if (atomicstring.compareAndset ( "Hosee", "ztk")) {system.out.println (thread.currentThread (). getId () + "Change Value"); } else {system.out.println (thread.currentThread (). getId () + "실패"); }}; }.시작(); }}}결과:
10 피라스
13failed
9Change 값
11 피라스
12 피라스
15 피라스
17 피라스
14 피라스
16failed
18 피라스
하나의 스레드 만 값을 수정할 수 있으며 후속 스레드는 더 이상 수정할 수 없다는 것을 알 수 있습니다.
2.4. AtomicStampedReference
CAS 운영에는 여전히 문제가 있음을 알게 될 것입니다.
예를 들어, 이전 Atomicinteger 증분 및 GGET 메소드
public final int excrementandget () {for (;;) {int current = get (); int next = current + 1; if (compareAndset (current, next)) 다음에 반환; }} 스레드 int current = get ()가 실행되면 현재 값 = 1을 가정하고 다른 스레드로 전환 하고이 스레드가 1을 2로 바꾸고 다른 스레드가 2로 다시 1으로 바뀝니다. 현재 초기 스레드로 전환하십시오. 값은 여전히 1과 같기 때문에 CAS 작업은 여전히 수행 될 수 있습니다. 물론 추가에는 문제가 없습니다. 일부 사례가있는 경우 데이터 상태에 민감한 경우 이러한 프로세스는 허용되지 않습니다.
현재 AtomicStampedReference 클래스가 필요합니다.
값과 타임 스탬프를 캡슐화하기 위해 내부적으로 쌍 클래스를 구현합니다.
개인 정적 클래스 쌍 <t> {최종 T 참조; 최종 int 스탬프; 개인 쌍 (t 참조, int Stamp) {this.reference = 참조; this.stamp = 스탬프; } static <t> pair <t> of (t reference, int stamp) {return new Pair <t> (참조, 스탬프); }}이 클래스의 주요 아이디어는 각 변경 사항을 식별하기 위해 타임 스탬프를 추가하는 것입니다.
// 설정 매개 변수 비교 매개 변수는 다음과 같습니다. 예상 값은 새 값을 씁니다.
Public Boolean CompareAndset (v expectionreference, v newReference, int expectStamp, int newstamp) {pair <v> current = pair; return exportReference == current.reference && exportStamp == current.stamp && ((newReference == current.reference && newStamp == current.stamp) || caspair (current, pair.of (newReference, newStamp))); } 예상 값이 현재 값과 같고 예상 타임 스탬프가 현재 타임 스탬프와 같으면 새 값이 작성되고 새 타임 스탬프가 업데이트됩니다.
다음은 AtomicStampedReference를 사용하는 시나리오입니다. 적합하지는 않지만 좋은 시나리오를 상상할 수는 없습니다.
장면 배경은 회사가 균형이 낮은 사용자를 무료로 재충전하지만 각 사용자는 한 번만 재충전 할 수 있다는 것입니다.
패키지 테스트; import java.util.concurrent.atomic.atomicStampedReference; 공개 클래스 테스트 {정적 원자 AtomicStampedReference <integer> money = new AtomicStampedReference <integer> (19, 0); public static void main (String [] args) {for (int i = 0; i <3; i ++) {Final int timestamp = money.getStamp (); 새 스레드 () {public void run () {while (true) {while (true) {integer m = money.getReference (); if (m <20) {if (money.compareAndset (m, m + 20, timestamp, timestamp + 1)) {System.out.println ( "성공적으로 재충전, 균형 :" + money.getReference ()); 부서지다; }} else {break; }}}}}}; }.시작(); } new Thread () {public void run () {for (int i = 0; i <100; i ++) {while (true) {int timestamp = money.getstamp (); 정수 m = money.getReference (); if (m> 10) {if (money.compareAndset (m, m -10, timestamp, timestamp + 1)) {System.out.println ( "10 위안, 균형 소비 :" + money.getReference ()); 부서지다; }} else {break; }} try {thread.sleep (100); } catch (예외 e) {// todo : handle exception}}}}; }.시작(); }}코드를 설명하면 3 개의 스레드가 사용자에게 재충전됩니다. 사용자의 잔액이 20 미만인 경우 사용자 20 위안을 재충전하십시오. 100 개의 스레드가 소비되는데, 각 스레드는 10 위안을 소비합니다. 사용자는 처음에 9 위안을 가지고 있습니다. AtomicStampedReference를 사용하여 구현할 때 각 작업은 타임 스탬프 +1을 유발하기 때문에 사용자는 한 번만 재충전됩니다. 실행 결과 :
성공적으로 재충전, 균형 : 39
10 위안의 소비, 균형 : 29
10 위안의 소비, 균형 : 19
10 위안의 소비, 균형 : 9
Atomicreference <integer> 또는 Atomic Integer를 사용하여 구현하면 여러 개의 재충전이 발생합니다.
성공적으로 재충전, 균형 : 39
10 위안의 소비, 균형 : 29
10 위안의 소비, 균형 : 19
성공적으로 재충전, 균형 : 39
10 위안의 소비, 균형 : 29
10 위안의 소비, 균형 : 19
성공적으로 재충전, 균형 : 39
10 위안의 소비, 균형 : 29
2.5. Atomicintegerarray
Atomicinteger와 비교할 때 배열의 구현은 추가 위시 일뿐입니다.
공개 최종 부울 비교 (int i, int expect, int update) {
return CompareAndSetraw (checkedByTeOffset (i), 기대, 업데이트);
}
그 내부는 일반 어레이를 캡슐화합니다
개인 최종 int [] 배열;
여기서 흥미로운 점은 이진수의 주요 0이 배열의 오프셋을 계산하는 데 사용된다는 것입니다.
Shift = 31 -integer.numberfleadingzeros (스케일);
주요 0은 예를 들어 8 비트가 12,00001100을 나타내고 주요 0은 1 앞의 0, 이는 4입니다.
오프셋을 계산하는 방법은 여기에 소개되지 않습니다.
2.6. Atomicintegerfieldupdater
Atomicintegerfieldupdater 클래스의 주요 기능은 일반 변수가 원자 연산을 즐길 수 있도록하는 것입니다.
예를 들어, 원래 int 유형 인 변수가 있었고이 변수는 여러 곳에 적용되었습니다. 그러나 특정 시나리오에서 int 유형을 Atomicinteger로 바꾸려면 유형을 직접 변경하면 다른 장소에서 응용 프로그램을 변경해야합니다. Atomicintegerfieldupdater는 이러한 문제를 해결하도록 설계되었습니다.
패키지 테스트; import java.util.concurrent.atomic.atomicinteger; import java.util.concurrent.atomic.atomicintegerfieldupdater; public class test {public static class v {int id; 휘발성 int 점수; public int getscore () {반환 점수; } public void setScore (int score) {this.score = score; }} public final static atomicintegerfieldupdater <v> vv = atomicintegerfieldupdater.newupdater (v.class, "score"); public static atomicinteger allscore = 새로운 atomicinteger (0); public static void main (string [] args)은 중단 된 예시 {final v stu = new v (); 스레드 [] t = 새 스레드 [10000]; for (int i = 0; i <10000; i ++) {t [i] = new Thread () {@override public void run () {if (math.random ()> 0.4) {vv.incrementandget (stu); allscore.incrementandget (); }}}; t [i] .start (); } for (int i = 0; i <10000; i ++) {t [i] .join (); } system.out.println ( "score ="+stu.getScore ()); System.out.println ( "allscore ="+allscore); }} 위의 코드는 atomicintegerfieldupdater를 Atomicinteger로 사용하여 점수를 돌립니다. 스레드 안전을 보장하십시오.
AllScore는 여기에서 확인하는 데 사용됩니다. 점수와 올스 스코어 값이 동일하다면 스레드 안전임을 의미합니다.
메모: