우리 가 말 한 CAS 자 회전 자물쇠 가 도대체 무엇 인지 깊이 있 게 설명해 주세요.

자 물 쇠 는 무엇 입 니까?
자 회전 잠 금 은 다 중 스 레 드 의 잠 금 메커니즘 에서 시작 해 야 합 니 다.다 중 프로세서 시스템 환경 에서 일부 자원 이 유한 하기 때문에 상호 배척 방문(mutual exclusion)이 필요 할 때 잠 금 체 제 를 도입 합 니 다.잠 금 프로 세 스 를 가 져 와 야 자원 방문 을 받 을 수 있 습 니 다.즉,매번 하나의 프로 세 스 만 자 물 쇠 를 가 져 올 수 있 고 자신의 임계 구역 에 들 어 갈 수 있 습 니 다.같은 시간 에 두 개 이상 의 프로 세 스 가 임계 구역 에 들 어 갈 수 없습니다.임계 구역 을 종료 할 때 자 물 쇠 를 풀 수 있 습 니 다.
상호 배척 알고리즘 을 설계 할 때 항상 하나의 상황 에 직면 하 게 됩 니 다.즉,자 물 쇠 를 얻 지 못 한 프로 세 스 는 어떻게 합 니까?
보통 두 가지 처리 방식 이 있다.
하 나 는 자 물 쇠 를 얻 지 못 한 호출 자가 자 물 쇠 를 돌려 야 하 는 지 아 닌 지 를 계속 순환 하 는 것 이다.이것 이 바로 본 고의 중점 인 자 물 쇠 를 돌려 야 하 는 것 이다.그 는 선성 을 막 을 필요 가 없다.
다른 하 나 는 자 물 쇠 를 얻 지 못 한 프로 세 스 가 자신 을 막 고 스 레 드 의 다른 임 무 를 계속 수행 하 는 것 이다.이것 이 바로 상호 배척 자물쇠(내 장 된 자물쇠 Synchronized 와 ReentrantLock 등 포함)이다.
머리말
CAS(Compare and swap),즉 비교 하고 교환 하 는 것 도 우리 가 평소에 말 하 는 자 회전 자물쇠 나 낙관적 인 자 물 쇠 를 실현 하 는 핵심 작업 이다.
그것 의 실현 은 매우 간단 하 다.바로 예상 한 값 과 메모리 값 을 비교 하 는 것 이다.만약 두 값 이 같다 면 예상 한 값 으로 메모리 값 을 교체 하고 true 로 돌아 가 는 것 이다.그렇지 않 으 면 false 로 돌아 갑 니 다.
원자 조작 을 보증 하 다
모든 기술 의 출현 은 특정한 문 제 를 해결 하기 위해 서 이 며,CAS 가 해결 해 야 할 문 제 는 원자 조작 을 보장 하 는 것 이다.원자 조작 은 무엇 입 니까?원 자 는 최소 로 분리 할 수 없 는 것 입 니 다.원자 조작 은 최소 로 분리 할 수 없 는 조작 입 니 다.즉,조작 이 시작 되면 중단 되 지 않 고 조작 이 완 료 된 것 을 알 고 있 습 니 다.다 중 스 레 드 환경 에서 원자 조작 은 스 레 드 안전 을 확보 하 는 중요 한 수단 이다.예 를 들 어 두 개의 스 레 드 가 작업 을 하고 있다 고 가정 하면 특정한 값 을 수정 하고 싶 으 면 자체 증가 작업 으로 말하자면 하나의 정수 i 에 대해 자체 증가 작업 을 하려 면 기본 적 인 세 가지 절차 가 필요 하 다.
1.i 의 현재 값 읽 기;
2.i 값 에 1 조작 을 추가 합 니 다.
3.i 값 을 메모리 에 다시 쓰기;
두 프로 세 스 가 i 의 현재 값 을 읽 었 다 고 가정 하면 0 이 라 고 가정 합 니 다.이때 A 스 레 드 는 i 에 1 을 추가 하고 B 스 레 드 도 1 을 추가 합 니 다.마지막 i 는 2 가 아 닌 1 입 니 다.자 증 조작 은 원자 조작 이 아니 라 세 단계 로 나 뉘 어 방 해 될 수 있 기 때문이다.아래 의 예 를 들 어 10 개의 스 레 드 는 모든 스 레 드 가 10000 번 i+작업 을 수행 합 니 다.우리 가 기대 하 는 값 은 100,000 이지 만 안 타 깝 게 도 결 과 는 항상 100,000 보다 작 습 니 다.

 static int i = 0;
 public static void add(){
 i++;
 }
 
 private static class Plus implements Runnable{
 @Override
 public void run(){
 for(int k = 0;k<10000;k++){
 add();
 }
 }
 }
 
 public static void main(String[] args) throws InterruptedException{
 Thread[] threads = new Thread[10];
 for(int i = 0;i<10;i++){
 threads[i] = new Thread(new Plus());
 threads[i].start();
 }
 for(int i = 0;i<10;i++){
 threads[i].join();
 }
 System.out.println(i);
 }
이왕 이렇게 된 거 어 떡 하지?그래,자 물 쇠 를 추가 하거나 synchronized 를 이용 하여 실현 할 수 있다 는 것 을 이미 생각 했 을 지도 모른다.예 를 들 어 add()방법 을 다음 과 같이 수정 할 수 있다.

public synchronized static void add(){
 i++;
 }
또는 잠 금 동작 을 추가 합 니 다.예 를 들 어 아래 에 ReentrantLock(잠 금 을 다시 넣 을 수 있 습 니 다)을 사용 하여 이 루어 집 니 다.

private static Lock lock = new ReentrantLock();
 public static void add(){
 lock.lock();
 i++;
 lock.unlock();
 }
CAS 자동 잠 금 실현
자물쇠 나 synchronized 키 워드 를 사용 하면 원자 조작 을 할 수 있 는데 왜 CAS 를 사용 해 야 합 니까?자 물 쇠 를 추가 하거나 synchronized 키 워드 를 사용 하면 성능 손실 이 비교적 크 고 CAS 를 사용 하면 낙관적 인 자 물 쇠 를 실현 할 수 있 기 때문에 실제 적 으로 CPU 차원 의 명령 을 직접 이용 하여 성능 이 매우 높 습 니 다.
위 에서 도 말 했 듯 이 CAS 는 자 회전 자 물 쇠 를 실현 하 는 기초 이다.CAS 는 CPU 명령 을 이용 하여 조작 하 는 원자 성 을 확보 하여 자물쇠 의 효 과 를 얻 었 다.자 회전 은 글자 의 뜻 도 잘 알 고 있다.자신 이 회전 하고 성인 말 을 번역 하면 순환 이 며 보통 무한 순환 으로 이 루어 진다.이렇게 되면 하나의 무한 순환 에서 CAS 작업 을 실행 하고 작업 이 성공 하면 true 로 돌아 갈 때 순환 이 끝 납 니 다.false 로 돌아 갈 때,이어서 순환 을 실행 하고,true 로 돌아 갈 때 까지 CAS 작업 을 계속 시도 합 니 다.
사실 JDK 에는 CAS 를 사용 하 는 곳 이 많 습 니 다.특히 자바 util.concurrent 가방 에 있 습 니 다.예 를 들 어 Countdown Latch,Semaphore,ReentrantLock 에 있 습 니 다.예 를 들 어 자바 util.concurrent.atomic 가방 에 모두 Atomic*,예 를 들 어 Atomic Boolean,Atomic Integer 등 을 사용 한 적 이 있 을 것 이 라 고 믿 습 니 다.
이것 은 아주 간단 하기 때문에 Atomic Boolean 을 예 로 들 자.

public class AtomicBoolean implements java.io.Serializable {
 private static final long serialVersionUID = 4654671469794556979L;
 // setup to use Unsafe.compareAndSwapInt for updates
 private static final Unsafe unsafe = Unsafe.getUnsafe();
 private static final long valueOffset;
 static {
 try {
 valueOffset = unsafe.objectFieldOffset
 (AtomicBoolean.class.getDeclaredField("value"));
 } catch (Exception ex) { throw new Error(ex); }
 }
 private volatile int value;
 
 public final boolean get() {
 return value != 0;
 }
 public final boolean compareAndSet(boolean expect, boolean update) {
 int e = expect ? 1 : 0;
 int u = update ? 1 : 0;
 return unsafe.compareAndSwapInt(this, valueOffset, e, u);
 }
}
이것 은 Atomic Boolean 의 일부 코드 입 니 다.우 리 는 이 안에 또 몇 가지 관건 적 인 방법 과 속성 을 보 았 습 니 다.
1.sun.misc.Unsafe 대상 을 사 용 했 습 니 다.이 종 류 는 메모리 대상 을 직접 조작 하 는 일련의 방법 을 제공 합 니 다.jdk 내부 에서 만 사용 하고 개발 자 에 게 사용 하 는 것 을 권장 하지 않 습 니 다.
2.value 는 실제 값 을 표시 합 니 다.get 방법 은 실제 value 가 0 인지 에 따라 불 값 을 판단 하 는 것 을 볼 수 있 습 니 다.여기 서 value 는 volatile 로 정의 합 니 다.volatile 은 메모리 의 가시 성 을 확보 할 수 있 기 때 문 입 니 다.즉,value 값 이 변화 하면 다른 스 레 드 는 바로 변화 후의 값 을 볼 수 있 습 니 다.다음 편 에 서 는 말씀 드 리 겠 습 니 다volatile 가시 적 문제.주목 해 주세요.
3.value Offset 은 value 값 의 메모리 오프셋 입 니 다.unsafe.object FieldOffset 방법 으로 얻 을 수 있 으 며 뒤의 copare Andset 방법 으로 사용 합 니 다.
4.compare AndSet 방법,이것 이 바로 CAS 를 실현 하 는 핵심 방법 입 니 다.Atomic Boolean 의 이 방법 을 사용 할 때 기대 치 와 업데이트 할 값 만 전달 하면 됩 니 다.그 안에 unsafe.compare AndSwapInt(this,value Offset,e,u)방법 을 호출 했 습 니 다.native 방법 입 니 다.c+로 실현 하면 구체 적 인 코드 는 붙 이지 않 습 니 다.한 마디 로 CPU 의 cmpxchg 명령 을 이용 하여 비교 하고 교체 한 것 입 니 다.물론 구체 적 인 시스템 버 전에 따라 실현 되 는 것 도 다 릅 니 다.관심 이 있 는 사람 은 직접 관련 글 을 찾 아 보 세 요.
필드 사용
  • CAS 는 간단 한 대상 의 조작 에 적합 하 다.예 를 들 어 불 값,정형 값 등 이다.
  • CAS 는 충돌 이 비교적 적은 경우 에 적합 하 다.만약 에 너무 많은 스 레 드 가 동시에 자전 하면 장시간 순환 하면 CPU 비용 이 많이 든다.
  • 예 를 들 어 AtomicBoolean 은 이러한 장면 에서 사용 할 수 있 습 니 다.시스템 은 하나의 불 변수의 상태 속성 에 따라 초기 화 작업 을 수행 해 야 하 는 지 여 부 를 판단 해 야 합 니 다.만약 에 다 중 스 레 드 환경 에서 여러 번 반복 되 지 않도록 AtomicBoolean 을 사용 하여 실현 할 수 있 습 니 다.의사 코드 는 다음 과 같 습 니 다.
    
    private final static AtomicBoolean flag = new AtomicBoolean();
     if(flag.compareAndSet(false,true)){
     init();
     }
    예 를 들 어 AtomicInteger 는 카운터 에서 다 중 스 레 드 환경 에서 계산 이 정확 하도록 할 수 있다.
    ABA 문제
    CAS 는 하나의 값 이 A 에서 B 로 바 뀌 었 다가 B 에서 A 로 바 뀌 었 다 는 문제 가 존재 한다.이 경우 CAS 는 값 이 변 한 적 이 없다 고 생각 하지만 실제로는 변화 가 있다.이에 대해 서 는 아 토 믹 Stamped Reference 가 버 전 번호 에 따라 판단 하 는 실현 을 제공 하여 일부 문 제 를 해결 할 수 있 습 니 다.
    총결산
    이상 은 이 글 의 전체 내용 입 니 다.본 논문 의 내용 이 여러분 의 학습 이나 업무 에 어느 정도 참고 학습 가치 가 있 기 를 바 랍 니 다.궁금 한 점 이 있 으 시 면 댓 글 을 남 겨 주 셔 서 저희 에 대한 지지 에 감 사 드 립 니 다.

    좋은 웹페이지 즐겨찾기