Java 하이 병렬 4: 자물쇠 없음 상세 정보
1 무쇄류의 원리 상세
1.1 CAS
CAS 알고리즘의 프로세스는 다음과 같습니다. 이 알고리즘은 3개의 매개 변수 CAS(V, E, N)를 포함합니다.V는 업데이트할 변수를 나타내고 E는 예상치를 나타내며 N은 새 값을 나타냅니다.V로만
값이 E 값과 같아야 V의 값이 N으로 설정됩니다. V 값과 E 값이 다르면 다른 스레드가 업데이트되었음을 나타냅니다. 현재 스레드가 무엇입니까?
안 해.마지막으로 CAS는 현재 V의 실제 값을 반환합니다.CAS 작업은 항상 성공적이라고 생각하는 낙관적인 태도로 진행됩니다.
조작하다.여러 스레드가 하나의 변수를 CAS로 동시에 조작할 때 하나만 이기고 업데이트에 성공하면 나머지는 모두 실패합니다.실패한 스레드
끊기지 않습니다. 실패를 알리고 다시 시도할 수 있습니다. 물론 실패한 라인이 작업을 포기하는 것도 허용합니다.이러한 원리를 바탕으로 CAS
조작이 즉시 잠기지 않고 다른 라인이 현재 라인에 대한 방해를 발견하고 적절한 처리를 할 수 있다.
CAS의 절차가 너무 많아서 V와 E가 같다고 판단한 후에 값을 부여하려고 할 때 라인을 전환하고 값을 변경할 수 있는지 알 수 있습니다.데이터가 일치하지 않는데요?
사실 이 걱정은 쓸데없는 것이다.CAS의 전체 작업 과정은 하나의 CPU 명령으로 이루어진 원자 작업입니다.
1.2 CPU 명령
CAS의 CPU 명령은 cmpxchg입니다.
명령 코드는 다음과 같습니다.
 /*
 accumulator = AL, AX, or EAX, depending on whether
 a byte, word, or doubleword comparison is being performed
 */
 if(accumulator == Destination) {
 ZF = 1;
 Destination = Source;
 }
 else {
 ZF = 0;
 accumulator = Destination;
 }
자바에는 자물쇠 없는 클래스가 많이 제공되고 있습니다. 다음은 자물쇠 없는 클래스를 소개합니다.
2 무분별한 사용
우리는 이미 자물쇠가 막히는 것보다 효율이 훨씬 높다는 것을 안다.자바가 어떻게 이런 자물쇠 없는 종류를 실현하는지 봅시다.
2.1. AtomicInteger
AtomicInteger는 Integer와 마찬가지로 Number 클래스를 상속합니다.
public class AtomicInteger extends Number implements java.io.Serializable
AtomicInteger에는 다음과 같은 CAS 작업이 있습니다.
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
여기서 unsafe를 설명해 드리겠습니다.compare And Swap Int 방법은this 클래스의 편이량이value Offset인 변수 값이 기대치expect와 같으면 이 변수의 값을 업데이트로 설정하는 것입니다.
사실 오프셋이 value Offset인 변수가 value예요.
static {
 try {
 valueOffset = unsafe.objectFieldOffset
  (AtomicInteger.class.getDeclaredField("value"));
 } catch (Exception ex) { throw new Error(ex); }
}
public final int getAndIncrement() {
 for (;;) {
  int current = get();
  int next = current + 1;
  if (compareAndSet(current, next))
  return current;
 }
 }
클래스 이름에서 알 수 있듯이 Unsafe 작업은 비안전한 작업입니다. 예를 들어
오프셋 값을 기준으로 설정합니다. (방금 설명한 Atomic Integer에서 이 기능을 보았습니다.)
park () (이 라인을 멈추면 나중에 블로그에서 언급할 것)
기본 CAS 작업
비공개 API, JDK 버전에 따라 차이가 클 수 있음
2.3. AtomicReference
앞에서 이미 Atomic Integer를 언급했는데 물론 Atomic Boolean, Atomic Long 등도 대동소이하다.
여기서 소개할 것은 Atomic Reference입니다.
AtomicReference는 템플릿 클래스입니다.
public class AtomicReference
그것은 임의의 유형의 데이터를 봉인할 수 있다.
예를 들어 String
package test;
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 (Exception e)
 {
 e.printStackTrace();
 }
 if (atomicString.compareAndSet("hosee", "ztk"))
 {
 System.out.println(Thread.currentThread().getId() + "Change value");
 }else {
 System.out.println(Thread.currentThread().getId() + "Failed");
 }
 };
 }.start();
 }
 }
}
10Failed
13Failed
9Change value
11Failed
12Failed
15Failed
17Failed
14Failed
16Failed
18Failed
값을 수정할 수 있는 라인이 하나밖에 없고 뒤의 라인은 더 이상 수정할 수 없습니다.
2.4.AtomicStampedReference
저희가 CAS 조작에 문제가 있다는 걸 알게 될 거예요.
예를 들어 이전의 Atomic Integer의 increment And Get 방법.
public final int incrementAndGet() {
 for (;;) {
  int current = get();
  int next = current + 1;
  if (compareAndSet(current, next))
  return next;
 }
 }
AtomicStampedReference 클래스가 필요합니다.
그 내부에는 값과 시간 스탬프를 봉인하기 위한 Pair 클래스가 있습니다.
private static class Pair<T> {
 final T reference;
 final int stamp;
 private Pair(T reference, int stamp) {
  this.reference = reference;
  this.stamp = stamp;
 }
 static <T> Pair<T> of(T reference, int stamp) {
  return new Pair<T>(reference, stamp);
 }
 }
//비교 설정 매개 변수 순서: 기대치 쓰기 새 시간 스탬프 새 시간 스탬프
public boolean compareAndSet(V expectedReference,
     V newReference,
     int expectedStamp,
     int newStamp) {
 Pair<V> current = pair;
 return
  expectedReference == current.reference &&
  expectedStamp == current.stamp &&
  ((newReference == current.reference &&
  newStamp == current.stamp) ||
  casPair(current, Pair.of(newReference, newStamp)));
 }
여기에 Atomic Stamped Reference를 사용한 장면을 들어보면 잘 어울리지 않을 것 같지만 좋은 장면은 생각지도 못했다.
장면 배경은 모 회사가 잔액이 적은 사용자에게 무료로 충전을 해주지만 사용자마다 한 번만 충전할 수 있다는 것이다.
package test;
import java.util.concurrent.atomic.AtomicStampedReference;
public class Test
{
 static 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();
 new Thread()
 {
 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());
  break;
 }
 }
 else
 {
 break;
 }
 }
 }
 };
 }.start();
 }
 new Thread()
 {
 public void run()
 {
 for (int i = 0; i < 100; i++)
 {
 while (true)
 {
 int timestamp = money.getStamp();
 Integer m = money.getReference();
 if (m > 10)
 {
 if (money.compareAndSet(m, m - 10, timestamp,
  timestamp + 1))
 {
 System.out.println(" 10 , :"
  + money.getReference());
 break;
 }
 }else {
 break;
 }
 }
 try
 {
 Thread.sleep(100);
 }
 catch (Exception e)
 {
 // TODO: handle exception
 }
 }
 };
 }.start();
 }
}
충전 성공, 잔액: 39
소비 10원, 잔액: 29
소비 10원, 잔액: 19
소비 10원, 잔액: 9
Atomic Reference
충전 성공, 잔액: 39
소비 10원, 잔액: 29
소비 10원, 잔액: 19
충전 성공, 잔액: 39
소비 10원, 잔액: 29
소비 10원, 잔액: 19
충전 성공, 잔액: 39
소비 10원, 잔액: 29
2.5. AtomicIntegerArray
Atomic Integer와 비교했을 때, 수조의 실현은 단지 하표가 하나 더 많은 것에 불과하다.
public final boolean compareAndSet(int i, int expect, int update) {
return compareAndSetRaw(checkedByteOffset(i), expect, update);
}
그것의 내부는 단지 일반적인array를 봉인했을 뿐이다
private final int[] array;
재미있는 것은 2진수의 전도 0을 이용하여 수조의 편이량을 계산한 것이다.
shift = 31 - Integer.numberOfLeadingZeros(scale);
전도 0은 예를 들어 8위가 1200001100을 나타내면 전도 0은 1 앞의 0의 개수로 4이다.
구체적인 편이량을 어떻게 계산하는지 여기에서는 더 이상 소개하지 않겠다.
2.6. AtomicIntegerFieldUpdater
Atomic Integer Field Updater 클래스의 주요 역할은 일반 변수도 원자 조작을 즐길 수 있도록 하는 것이다.
예를 들어 원래 하나의 변수가 int형이고 많은 곳에서 이 변수를 응용했지만 어떤 장면에서 int형을 Atomic Integer로 바꾸고 싶지만 직접 유형을 바꾸면 다른 지역의 응용을 고쳐야 한다.Atomic Integer Field Updater는 이러한 문제를 해결하기 위해 만들어진 것입니다.
package test;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
public class Test
{
 public static class V{
 int id;
 volatile int score;
 public int getScore()
 {
 return score;
 }
 public void setScore(int score)
 {
 this.score = score;
 }
 
 }
 public final static AtomicIntegerFieldUpdater<V> vv = AtomicIntegerFieldUpdater.newUpdater(V.class, "score");
 
 public static AtomicInteger allscore = new AtomicInteger(0);
 
 public static void main(String[] args) throws InterruptedException
 {
 final V stu = new V();
 Thread[] t = new Thread[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);
 }
}
여기에서allscore를 사용하여 검증합니다. 만약score와allscore 수치가 같다면 라인이 안전하다는 것을 설명합니다.
설명:
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
JPA + QueryDSL 계층형 댓글, 대댓글 구현(2)이번엔 전편에 이어서 계층형 댓글, 대댓글을 다시 리팩토링해볼 예정이다. 이전 게시글에서는 계층형 댓글, 대댓글을 구현은 되었지만 N+1 문제가 있었다. 이번에는 그 N+1 문제를 해결해 볼 것이다. 위의 로직은 이...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.