자바 병행 프로그래밍 노트 (3 개) - 스레드 안전

자바 병행 프로그래밍 노트 (3 개) - 스레드 안전

스레드 안전 :

다중 스레드는 상관없이 클래스를 액세스 할 때 일정 런타임 환경의 어떤 종류를 사용하거나 이러한 프로세스가 번갈아 수행하는 방법, 및 호출 코드에 추가 동기화 또는 상승 작용을 필요로하지 않는 클래스는 올바른를 표시 할 수 있습니다 행동,이 클래스는 스레드 안전 호출합니다.

스레드 안전은 세 가지 측면에 반영됩니다 :

  • 원자 : 그것을 작동 한 번에 하나 개의 스레드를 독점 액세스를 제공합니다
  • 가시성 : 메인 메모리 스레드를 수정하는 다른 스레드 시간에 관찰 할 수있다
  • 주문 : 스레드가 다른 스레드 관찰 명령의 실행 순서, 명령의 재 배열, 일반적인 관측 무질서가 있기 때문에.

자성 : 원자 패킷

사용 AtomicInteger 변수의 자성 동작을 보장

public class CountExample2 {

    // 请求总数
    public static int clientTotal = 5000;

    // 同时并发执行的线程数
    public static int threadTotal = 200;

    public static AtomicInteger count = new AtomicInteger(0);

    public static void main(String[] args) throws Exception {
        ExecutorService executorService = Executors.newCachedThreadPool();
        final Semaphore semaphore = new Semaphore(threadTotal);
        final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
        for (int i = 0; i < clientTotal ; i++) {
            executorService.execute(() -> {
                try {
                    semaphore.acquire();
                    add();
                    semaphore.release();
                } catch (Exception e) {
                    log.error("exception", e);
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        executorService.shutdown();
        log.info("count:{}", count.get());
    }

    private static void add() {
        count.incrementAndGet();  //相当于++x;
        // count.getAndIncrement();    //相当于x++
    }
}

원리 : incrementAndGet의 AtomicInteger () 메소드 불안전 클래스 내에 사용

public final int incrementAndGet() {
        return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
    }

실현 getAndAddInt의 점에 깊이있는 모습을 계속 :

//
public final int getAndAddInt(Object var1, long var2, int var4) {
        int var5;
        do {
            var5 = this.getIntVolatile(var1, var2);
        } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

        return var5;
    }

가장 중요한 방법은 여기에 있습니다 : 방법 기본 자바입니다 compareAndSwapInt (), 그것은 자바로 구현되지 않습니다 :

public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);

매개 변수 설명 :

개체 VAR1 : 이와 같은 경우에 오브젝트 조작은이 Obect는 AtomicInteger 카운트이고;

긴 VAR2 : 오브젝트의 전류 값;

INT의 var4는 다음 +1 동작 후 var4이 1 할 이와 마찬가지로, 현재 오브젝트의 값을 증가하는 단계;

INT의 VAR5 : 하단 호출 한 값이 동작을 통해, 다른 스레드가이 값 VAR2 동일 없어야한다면

getAndAddInt () 메소드 compareAndSwapInt () 메소드를 설명 수행하는 경우, 오브젝트 VAR1 들면 VAR2 바닥 VAR5 의한 값이 동일하고 구현 VAR5 + var4 경우;

추가 설명 : 카운트의 현재 값은 현재 스레드의 값이 상기 작업 메모리의 스레드 값 속해 있으며, 하부 획득의 값은 작업 메모리의 값 및 주 메모리의 값이 단지 주 메모리의 중간 인 동시에, 그것은 수정할 수 있습니다.

AtomicLong, LongAdder

상기 예에서, AtomicInteger가 AtomicLong를 대체하는 전체 방법은 아직 스레드 안전하다.

두 번째 방법은 사용하는 LongAdder입니다 :

public class AtomicExample3 {

    // 请求总数
    public static int clientTotal = 5000;

    // 同时并发执行的线程数
    public static int threadTotal = 200;

    public static LongAdder count = new LongAdder();

    public static void main(String[] args) throws Exception {
        ExecutorService executorService = Executors.newCachedThreadPool();
        final Semaphore semaphore = new Semaphore(threadTotal);
        final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
        for (int i = 0; i < clientTotal ; i++) {
            executorService.execute(() -> {
                try {
                    semaphore.acquire();
                    add();
                    semaphore.release();
                } catch (Exception e) {
                    log.error("exception", e);
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        executorService.shutdown();
        log.info("count:{}", count);
    }

    private static void add() {
        count.increment();
    }
}

AtomicLong 및 LongAdder 비교 :

AtomicLong :이 클래스는, 지속적으로 수정, 성공의 확률이 크게 수정 경쟁 상황에서 실패의 더 큰 가능성을 개정 경쟁력이 떨어지는 상황에서 성공할 때까지 목표 값을 수정하는 무한 루프의 기본이되는 구현하려고한다, 그것은이 경우 성능에 해가 될 것이다.

LongAdder는 롱으로 인해 이중형 값 JVM은이를 읽고 쓰기 작업 LongAdder가 다음 결과 배열에 추가 조건이 동작 원리에 따른 값의 배열로 분할하는 판독 된 64 비트 32 비트를 분할하여 기록 동작을 할 수 있도록 그리고, 스핀 압력 등화의 조작에 의해 그 성능이 상대적으로 좋다

사용 시나리오 선택 : 높은 동시 카운트 시나리오에서 사용 LongAdder 우선적으로 다른 상황이 AtomicLong를 사용

AtomicBoolean

방법 기본 구현은 다음과 같습니다

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);
    }

이 방법은 논리 값을 수행하는 코드의 블록을 지칭한다.

케이스 (이 경우는 한 번만 수행 다중 스레드 코드의 특정 부분을 보여줍니다)를 사용합니다 :

public class AtomicExample6 {

    private static AtomicBoolean isHappened = new AtomicBoolean(false);

    // 请求总数
    public static int clientTotal = 5000;

    // 同时并发执行的线程数
    public static int threadTotal = 200;

    public static void main(String[] args) throws Exception {
        ExecutorService executorService = Executors.newCachedThreadPool();
        final Semaphore semaphore = new Semaphore(threadTotal);
        final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
        for (int i = 0; i < clientTotal ; i++) {
            executorService.execute(() -> {
                try {
                    semaphore.acquire();
                    test();
                    semaphore.release();
                } catch (Exception e) {
                    log.error("exception", e);
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        executorService.shutdown();
        log.info("isHappened:{}", isHappened.get());
    }

    private static void test() {
        if (isHappened.compareAndSet(false, true)) {
            log.info("execute");
        }
    }
}

AtomicReference, AtomicReferenceFieldUpdater

AtomicReference 사용 예 :

public class AtomicExample4 {

    private static AtomicReference<Integer> count = new AtomicReference<>(0);

    public static void main(String[] args) {
        count.compareAndSet(0, 2); // 2
        count.compareAndSet(0, 1); // no
        count.compareAndSet(1, 3); // no
        count.compareAndSet(2, 4); // 4
        count.compareAndSet(3, 5); // no
        log.info("count:{}", count.get());   //4
    }
}

사용 예 AtomicReferenceFieldUpdater :

public class AtomicExample5 {

    private static AtomicIntegerFieldUpdater<AtomicExample5> updater =
            AtomicIntegerFieldUpdater.newUpdater(AtomicExample5.class, "count");

    @Getter
    public volatile int count = 100;

    public static void main(String[] args) {

        AtomicExample5 example5 = new AtomicExample5();

        if (updater.compareAndSet(example5, 100, 120)) {
            log.info("update success 1, {}", example5.getCount());
        }

        if (updater.compareAndSet(example5, 100, 120)) {
            log.info("update success 2, {}", example5.getCount());
        } else {
            log.info("update failed, {}", example5.getCount());
        }
    }
}

AtomicStampedReference는 : CAS는 ABA의 문제를 해결

ABA의 문제점 : CAS 동작 B에 대한 값을 가변 다른 스레드하지만이 다시 변경 될 때, 사용이 때 현재의 변수와 비교하여 원하는 값 스레드 변수 A가 발견되지는 CAS 후, 변경 가치 교환 작업.

해결 방법 : 모든 변수 업데이트 버전 번호 +1

코어 클래스 :

AtomicStampedReference

코어에있어서의 compareAndSet ()

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)));
    }

AtomicLongArray

이 클래스는 유지 보수의 배열입니다

AtomicLong에 비해이 클래스는, 또 하나의 방법은 우리가 인덱스 값을 지정할 수 있습니다.

원 자성 - 잠금

  • 동기화 : 의존 JVM에 잠금 장치를 달성하기 위해
  • 잠금 : CPU가 특별 지시는, 코드에 의존 달성하기 위해, ReenteantLock

동기화 :

  • 수정 된 블록 : 코드 중괄호, 객체 호출에 작용
  • 수정 방법 : 객체 호출에 작용의 전 과정

  • 수정 된 정적 방법 : 전역 정적 메서드 모든 개체에 작용
  • 모든 물체에 작용하는 개질 형 브라켓 부

원 자성 - 비교

동기화 : 잠금은 경쟁이 덜 좋은 가독성을 중단 할 수 없다

잠금 : 인터럽트 잠금, 매니 폴드, 때 치열한 경쟁 정상을 유지하기 위해

원자는 : 잠금 성능보다 더 일반적인 경우 치열한 경쟁을 유지하는 경우에만 값을 동기화

시계

스레드 간의 보이지 공유 변수 원인

  • 크로스 스레드 실행
  • 바인딩 크로스 스레드는 재정렬을 실행
  • 공유 변수 업데이트의 값은 작업 메모리와 메인 메모리 사이에서 최신 없습니다

가시성 - 동기화

JMM 약 2 동기화 조항 :

  • 스레드가 공유 변수의 값을 잠금을 해제하기 전에 메인 메모리의 최신 갱신해야합니다
  • (: 잠금 및 잠금 해제 같은 잠금입니다 주) 나사 잠금, 공유 변수를 사용할 때 메인 메모리에서 최신 값을 다시 읽을 필요하므로 값 변수의 공유 작업 메모리를 취소 한 것

가시성 - 휘발성

메모리 장벽 재정렬 및 ​​최적화 금지 의해 달성 된 경우

  • 휘발성 가변 기입 동작은, 상기 배리어는 기록 동작의 저장 명령을 추가 할 때, 로컬 메모리는 메인 리프레시 값 내에 공유 변수
  • volatileb 변수가 판독 동작을 할 때, 부하가 판독 동작 이전에 배리어 명령을 메인 메모리로부터 상기 공유 변수의 읽기를 첨가한다

어떤 원자가없는 휘발성 키워드

적절한 장면 :

  • 작업 변수 및 현재 값에 의존하지 않는 쓰기;
  • 이 변수는 다른 변수와 불변에 포함되지 않습니다.

휘발성 상태 플래그 수량 따라서 적합

온화

자바 메모리 모델, 에디터 및 프로세서 지침을 다시 정렬 할 수있게하지만, 재정렬 프로세스는 단일 스레드 프로그램의 이행에 영향을 미치지 않습니다,하지만 여러 스레드의 동시 실행의 정확성에 영향을 미칠 것입니다.

정상적인 상황에서는 다음과 같은 세 가지 키워드를 주문에 의해 보장 될 수있다 :

  • 휘발성 물질
  • 동기화
  • 자물쇠

발생-전에 원칙

두 작업의 실행 순서는 원리를 추론 할 수없는 경우 발생-전에, 우리는 그들의 질서, 가상 머신들을 임의의 순서가 될 수 보장 할 수 없습니다.

즉, 이러한 규칙을 다음과 같은 장면을 다른 장면의 규정에 추가됩니다, 가상 머신은 다시 정렬 할 수 있습니다.

  • 프로그램 순서 규칙 : 스레드, 작업을 위의 코드 책 사설의 순서에 따라 나중에 쓰기 작업에서 발생
  • 규칙 차단하십시오 잠금 해제 동작이 잠금 동작과 제 1면에 고정 후 발생한
  • 휘발성 가변 규칙 : 가변 사전 판독 동작 이후에,이 변수의 얼굴에서 발생 오퍼레이션 쓰기
  • 규칙 합격 : 조작 앞서 절차 B를 발생하고, B는 제 1 동작이 발생 C에서 동작하는 경우, 동작은 A가 이전 동작을 얻을 수있다 C 발생할

  • 스레드 시작 규칙 : Thread 객체를 (시작) 메소드는 우선 모든 행동이 스레드에서 발생
  • 스레드 인터럽트 원리 : 호출 인터럽트 () 메소드는 첫 번째 이벤트가 발생하는 인터럽트 인터럽트 스레드 코드 검출의 발생을 스레드.
  • 규칙의 끝을 스레드 : 모든 작업에 스레드가 종료 감지 스레드에 앞서 개최, 우리는 () 메소드는, Thread.isAlive () 반환 값은 의미가 스레드가 종료되었음을 감지를 Thread.join으로 종료 할 수 있습니다.
  • 룰 객체의 끝 : 오브젝트 초기화 파이널 라이즈에서 처음 발생을 완료 () 메소드를 시작할

추천

출처www.cnblogs.com/xujie09/p/11694133.html