Atomic

7309 단어
Atomic: 원자로 번역된 것으로, 그 용도는 원자 문제를 해결하는 데 쓰인다. 그렇다면 원자는 무엇이며, 중단될 수 없는 일련의 작업으로 해석된다.
예를 들면 다음과 같습니다.
다중 스레드 상황에서 같은 Integer 대상obj 스레드 A를 조작하려면 업무 논리 획득 대상obj를 실행해야 하고,obj=1이면obj=2스레드 B를 설정하면 업무 논리 획득 대상obj를 실행해야 하고,obj==1이면obj=10을 설정해야 한다.
이런 상황에서 스레드 A와 스레드 B는 모두obj의 값을 1로 얻는다. 이때 스레드 A는obj를 2로 설정한다. 그러나 이때 스레드 B는obj의 값이 이미 변화한 것을 모르고 여전히obj를 10으로 설정한다. 분명히 이것은 문제가 있는 것이다.
 
그러면 이런 경우obj 대상을 잠그고 라인 A가 실행된 다음에 라인 B가 코드를 읽고 실행하도록 하겠습니다.
public static void main(String[] args) throws InterruptedException {
    Test test = new Test();
    // A test  
    new Thread(()->{
       synchronized (test){
           if (test.obj ==1){
               test.obj = 2;
           }
       }
    }).start();
    // A test  
    new Thread(()->{
       synchronized (test){
           if (test.obj ==1){
               test.obj = 10;
           }
       }
    }).start();
}
public static class Test{
    public Integer obj = 1;
}

 
그러나 분명히 대상을 잠그는 효율이 매우 낮다(잠금의 입자가 너무 크고 전체 업무 논리 코드가 잠겼다). 그러면 자연히 비교와 할당의 두 가지 조작이 원자가 되기를 바란다. 값을 설정하는 동시에 이 값이 기대치와 같은지 비교하면 Atomic 패키지의 상용 클래스가 있다. 다음은 Atomic Integer 코드로 예를 들자.
public static void main(String[] args) throws InterruptedException {
    AtomicInteger atomicInteger = new AtomicInteger(1);
    // A 
    new Thread(()->{
        boolean result = atomicInteger.compareAndSet(1,2);
        System.out.println("A->"+result);
    }).start();
    // B 
    new Thread(()->{
        boolean result = atomicInteger.compareAndSet(1,10);
        System.out.println("B->"+result);
    }).start();

    Thread.sleep(100);
    System.out.println(atomicInteger);
}

 :
A->true
B->false
2

코드 결과에서 알 수 있듯이 설정과 대비는 한 번의 원자 조작으로 코드에서 사용하는 것이 더욱 간편하고 효율이 높으며 잠긴 코드가 적고 잠긴 코드가 적으면 잠긴 입자가 적으며 전체 코드에 있어 효율이 높고 원자 조작류가 많으므로 JUC 패키지에 직접 들어가서 볼 수 있다.
AtomicReference AtomicReference Array
위의 Atomic 일반 클래스는 현재 개체 원자 조작을 해결했지만 일부 업무는 데이터 자체의 값에 주목할 뿐만 아니라 업무의 흐름에도 주목한다
장면:
다음과 같은 데이터 구조
public class Test{
    public Integer obj = 1;
}

obj의 값은 1이고 라인 A 업무 수요는 obj==1이면obj=2를 설정하고 라인 B의 수요는obj=2가obj=1을 설정하고 라인 C의 수요는obj==1이면obj=10을 설정한다. 공교롭게도 라인 A B C가 순서대로 집행된다면 라인 A B C는 모두 성공할 수 있다.
데이터 값으로 볼 때 아무런 문제가 없지만 C의 수요는 스레드 A->스레드 C 사이에 다른 업무 프로세스가 존재하지 않기를 바라는 것이다. 만약에 존재하면 실패로 돌아가고 이때 흔히 볼 수 있는 ABA 장면이 나타난다.
솔루션:
데이터 구조를 변경하는 것은 다음과 같다.
 
public class Test{
    public Integer obj = 1;
    public Long v = 0L;
}

 
이때 대비값을 제외한 다른 보조값을 가지고 업무 절차의 정확성을 확정하기 위해 버전 번호의 개념을 제시했고(락관쇄로 이해할 수 있다) obj의 모든 조작에 대해 버전 번호 v가 있는데 기대하는 버전 번호와 사용 시 버전 번호가 같아야만 조작에 성공할 수 있다. 다음과 같다.
원시 Test(obj=1, v=0)-> 스레드 A는obj를 2로 설정합니다. 이때 Test(obj=2, v=1)-> 스레드 A는obj를 2로 설정합니다. 이때 Test(obj=1, v=2)-> 스레드 C가 획득할 때test(obj=1, v=0) 기대if(obj==1 & v=0)를obj=10로 설정합니다.
분명히 obj의 값은 조건에 부합되지만 v는 획득할 때의 v=0이 아니라 이미 v=2가 되었다. 이때 라인 C는 조작할 수 없다. 그러면 ABA의 문제는 해결된다.
다행히도 JDK에서 ABA 문제를 해결하는 도구 종류를 제공했다:AtomicStampedReference
일반적인 방법:
public static void main(String[] args) throws InterruptedException {
    Test test = new Test();
    AtomicStampedReference reference = new AtomicStampedReference(test, 0);
    // 
    test = reference.getReference();
    //     
    reference.set(new Test(), reference.getStamp());
    //   
    reference.compareAndSet(test, new Test(), reference.getStamp(), 1);
    // compareAndSet  1.9   
    reference.weakCompareAndSet(test, new Test(), reference.getStamp(), 1)
}
public static class Test{
    public Integer obj = 1;
}

 

좋은 웹페이지 즐겨찾기