자바 에서 CAS 가 나타 난 원인 및 원리 분석

목차
프롤로그
해결 방안
AtomicInteger 소스 코드 분석
  • Unsafe
  • valueOffset
  • compareAndSwapInt
  • incrementAndGet

  • 총화
    머리말
    CAS:즉,compare and swap(비교 및 교체)입 니 다.CAS 는 다 중 스 레 드 병발 시 스 레 드 간 전환 시간 편 으로 인 한 원자 문 제 를 해결 해 야 합 니 다.
    코드 부터 볼 게 요.
    public class CasDemo1 {
    
        static int k = 0;
    
        public static void main(String[] args) {
    
            for(int i = 0;i<10;i++) {
                Thread t = new Thread(() -> {
                    //     count  100 
                    try {
                        //          
                        Thread.sleep(200);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    for (int j = 0; j < 100; j++) {
                        k++;
                    }
                });
                t.start();
            }
    
    
            try {
                //   2 ,        
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            System.out.println(k);
        }
    
    }
    

    10 개의 스 레 드 를 동시에 k 의 값+100 으로 만 들 지만 결과 출력 값 은 1000 보다 작 습 니 다.왜 일 까요?이 유 는 k++이 조작 이 CPU 차원 에서 원자 성 을 보장 하지 않 고 분 리 될 수 있 기 때문이다.
    1.조회 k 의 값 2,k=k+1
    이 때 스 레 드 간 에 시간 편 전환 이 존재 하기 때문에 만약 에 스 레 드 A 가 첫 번 째 단계 까지 실행 하면 k 의 값=0 을 조회 합 니 다.이때 CPU 가 전환 되 었 고 스 레 드 B 는 1 과 2 를 성공 적 으로 실 행 했 습 니 다.이때 스 레 드 B 에 게 K=1 은 시간 편 이 A 스 레 드 로 전환 되 었 습 니 다.A 스 레 드 의 k=0 은 이때 2,k+1=1 을 실 행 했 습 니 다.그러면 우리 의 기대 와 다 르 고 병발 문제 가 발생 했 습 니 다.
    해결 방안
    상기 문 제 를 해결 하기 위해 서 우 리 는 자 물 쇠 를 추가 하여 병발 을 피 할 수 있 고 synchronized 를 사용 하여 자 물 쇠 를 추가 할 수 있 지만 synchronized 는 비관 적 인 자물쇠 사상 으로 성능 이 비교적 떨 어 질 것 이다.
    CAS 가 채택 한 것 은 바로 낙관적 인 자물쇠 사상 이다.즉,집행 할 때 병발 문제 가 없다 고 가정 하고 먼저 비교 한 결과 변경 되 지 않 으 면 교체 되 고 변경 되면 상기 1 과 2 절 차 를 성공 할 때 까지 다시 집행 하 는 것 이다.
    자바 는 이미 여러 가지 지원 과 병행 하 는 카 스 관련 종 류 를 제공 하 였 으 며,주로 자바.util.concurrent.atomic 패키지 에서,예 를 들 어 AtomicInteger,AtomicBoolean,AtomicLong 등 입 니 다.
    AtomicInteger 소스 코드 분석
    우 리 는 AtomicInteger 소스 코드 의 실현 을 보 았 다.
    public class AtomicInteger extends Number implements java.io.Serializable {
        private static final long serialVersionUID = 6214790243416807050L;
    
        // setup to use Unsafe.compareAndSwapInt for updates
        private static final Unsafe unsafe = Unsafe.getUnsafe();
        private static final long valueOffset;
    
        static {
            try {
                valueOffset = unsafe.objectFieldOffset
                    (AtomicInteger.class.getDeclaredField("value"));
            } catch (Exception ex) { throw new Error(ex); }
        }
    
        private volatile int value;
    
        /**
         * Creates a new AtomicInteger with the given initial value.
         *
         * @param initialValue the initial value
         */
        public AtomicInteger(int initialValue) {
            value = initialValue;
        }
    
        /**
         * Creates a new AtomicInteger with initial value {@code 0}.
         */
        public AtomicInteger() {
        }
        /**
         * Gets the current value.
         *
         * @return the current value
         */
        public final int get() {
            return value;
        }
        
        public final int incrementAndGet() {
            return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
        }
        
        public final boolean compareAndSet(int expect, int update) {
            return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
        }
     
       ......
    
        }
    

    Unsafe
    Unsafe 클래스,전체 이름 은 sun.misc.Unsafe 입 니 다.이름 에서 우 리 는 이 클래스 가 일반 프로그래머 에 게'위험'이라는 것 을 알 수 있 습 니 다.일반 응용 개발 자 들 은 이런 종 류 를 사용 하지 않 습 니 다.만약 에 우리 가 Unsafe 류 를 기반 으로 하 는 기능 을 실현 해 야 한다 면 반사 로 Unsafe 류 를 가 져 올 수 밖 에 없습니다.Unsafe 는 생 성 할 때 제한 을 가 했 기 때문에 rt.jar 가방 에 있 는 클래스 만 접근 할 수 있 습 니 다.Unsafe 의 getUnsafe 에서 알 수 있 습 니 다.
    Unsafe 는 자바 에서 메모 리 를 직접 조작 하 는 능력 을 제공 합 니 다.
    valueOffset
    value Offset=unsafe.object FieldOffset(AtomicInteger.class.getDeclared Field("value"))이 코드 를 보면 value Offset 의 값 은 AtomicInteger 의 value 값 이 메모리 에 있 는 주소,즉 오프셋 임 을 알 수 있 습 니 다.
    compareAndSwapInt
    unsafe.compareAndSwapInt(this, valueOffset, expect, update)
    카 스 의 핵심 작업,즉 조회 매개 변수 1 의 메모리 주 소 는 value Offset 의 값 입 니 다.매개 변수 3 과 같 을 때 매개 변수 4 로 수정 하고 성공 을 되 돌려 줍 니 다.같 지 않 을 때 false 매개 변수 1 을 되 돌려 줍 니 다.작업 의 인 스 턴 스 매개 변수 2:작업 인 스 턴 스 중 메모리 주소 매개 변수 3:예상 값 매개 변수 4:변경 되 기 를 원 하 는 값
    incrementAndGet
    늘다
    getAndAddInt 방법 보기
     public final int getAndAddInt(Object var1, long var2, int var4) {
            int var5;
            do {
            	//              valueOffset  
                var5 = this.getIntVolatile(var1, var2);
                //     cas      , var5     var5    var5+var4
            } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
    
            return var5;
        }
    

    즉,최종 적 으로 AtomicInteger 의 value+1 을+1 후의 값 으로 되 돌려 줍 니 다.
    총결산
    AtomicInteger 의 소스 코드 를 보고 우 리 는 기본적으로 카 스 의 실현 을 알 게 되 었 습 니 다.즉,Unsafe 류 를 통 해 메모리 에 있 는 값 을 직접 비교 하고 같 으 면 교체 하 며 실 패 는 성공 할 때 까지 자전 합 니 다.메모리 에 있 는 값 을 직접 비교 하여 상기 시간 대 전환 으로 인 한 원자 문 제 를 해결 합 니 다.

    좋은 웹페이지 즐겨찾기