>  기사  >  Java  >  CAS 명령어의 잠금 없는 프로그래밍을 구현하는 Java의 예

CAS 명령어의 잠금 없는 프로그래밍을 구현하는 Java의 예

黄舟
黄舟원래의
2017-09-15 11:07:211520검색

이 기사에서는 주로 Java 언어의 CAS 명령에 대한 잠금 없는 프로그래밍 구현 예를 소개합니다. 이는 특정 참조 가치가 있으며 이에 대해 배울 수 있습니다.

관련 콘텐츠를 처음 접할 때는 휘발성 키워드를 사용해야 변수의 가시성을 보장할 수 있고 읽기 및 쓰기의 원자적 연산을 구현하는 데 사용할 수 있다는 것을 알고 계실 것입니다. . . 그러나 일부 복합 연산을 구현하는 데 휘발성은 무력합니다. . . 가장 대표적인 것은 증가 및 감소 연산이다. . . .

동시 환경에서 데이터 일관성을 달성하는 가장 간단한 방법은 하나의 스레드만 동시에 데이터에 대해 작업할 수 있도록 잠그는 것임을 알고 있습니다. . . . 예를 들어, 카운터는 다음과 같은 방식으로 구현될 수 있습니다:


public class Counter {
  private volatile int a = 0;
  public synchronized int incrAndGet(int number) {
    this.a += number;
    return a;
  } 
  public synchronized int get() {
    return a;
  }
}

속성 a에 대한 동기 액세스를 보장하기 위해 동기화된 키워드를 사용하여 모든 작업을 수정합니다. . . 이는 실제로 동시 환경에서 일관성을 보장할 수 있지만 잠금, 잠금 오버헤드, 스레드 스케줄링 등의 사용으로 인해 프로그램의 확장성이 제한되므로 잠금 없는 구현이 많이 있습니다. . . .

사실 이러한 잠금 없는 방법은 모두 프로세서에서 제공하는 일부 CAS(비교 및 전환) 명령을 사용합니다. CAS가 수행하는 의미는 다음과 같습니다.


public synchronized int compareAndSwap(int expect, int newValue) {
    int old = this.a;
    if (old == expect) {
      this.a = newValue;
    }
    return old;
  }

좋아요, 코드를 통해 CAS 의미를 매우 명확하게 이해해야 합니다. 이제 대부분의 프로세서는 원자 CAS 명령어를 구현하는 것 같습니다. .
자, CAS가 Java에서 어디에 사용되는지 살펴보겠습니다. 먼저 AtomicInteger 유형을 살펴보겠습니다. 이는 동시성 라이브러리에서 제공되는 유형입니다.


private volatile int value;

이것은 내부 정의입니다. 휘발성이므로 스레드 간의 가시성과 읽기 및 쓰기의 원자성을 보장할 수 있습니다. . .
그럼 더 일반적으로 사용되는 몇 가지 방법을 살펴보겠습니다.


public final int addAndGet(int delta) {
  for (;;) {
    int current = get();
    int next = current + delta;
    if (compareAndSet(current, next))
      return next;
  }
}

이 방법의 기능은 현재 값에 델타를 추가하는 것입니다. 여기서 전체 방법에 잠금이 없음을 알 수 있습니다. code 실제로 Java에서 lock-free 카운터를 구현하는 메소드라고 해도 여기의 CompareAndSet 메소드는 다음과 같이 정의됩니다.


public final boolean compareAndSet(int expect, int update) {
  return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}

unsafe 메소드가 호출되므로 이에 대해 할 수 있는 일은 없습니다. 사실, JVM이 원자적 연산을 구현하기 위해 프로세서 자체의 CAS를 호출한다고 추측할 수 있어야 합니다. . .

기본적으로 AtomicInteger 유형의 중요한 메소드는 잠금 없는 방식으로 구현됩니다. . 따라서 동시 환경에서는 이 유형을 사용하면 더 나은 성능을 얻을 수 있습니다. . .
이상으로 java에서 lock-free 카운터 구현이 완료되었습니다. 다음으로, lock-free 스택을 구현하고 코드를 직접 붙여넣는 방법을 살펴보겠습니다. 코드는 "JAVA Concurrent 프로그래밍 실습"에서 따왔습니다.


package concurrenttest;
import java.util.concurrent.atomic.AtomicReference;
public class ConcurrentStack<e> {
  AtomicReference<node<e>> top = new AtomicReference<node<e>>();
  public void push(E item) {
    Node<e> newHead = new Node<e>(item);
    Node<e> oldHead;
    while (true) {
      oldHead = top.get();
      newHead.next = oldHead;
      if (top.compareAndSet(oldHead, newHead)) {
        return;
      }
    }
  }
  public E pop() {
    while (true) {
      Node<e> oldHead = top.get();
      if (oldHead == null) {
        return null;
      }
      Node<e> newHead = oldHead.next;
      if (top.compareAndSet(oldHead, newHead)) {
        return oldHead.item;
      }
    }
  }
  private static class Node<e> {
    public final E item;
    public Node<e> next;
     
    public Node(E item) {
      this.item = item;
    }
  }
}

좋습니다. 위 코드는 잠금 없는 스택을 구현합니다. 간단합니다. . . 동시 환경에서는 잠금 없는 데이터 구조가 잠금보다 훨씬 더 잘 확장될 수 있습니다. . .

잠금 없는 프로그래밍에 대해 이야기할 때 잠금 없는 대기열을 언급해야 합니다. 실제로 잠금 없는 대기열의 구현은 동시 라이브러리인 ConcurrentLinkedQueue에서 제공됩니다. 중요한 메서드 구현을 살펴보겠습니다.

public boolean offer(E e) {
  checkNotNull(e);
  final Node<e> newNode = new Node<e>(e);
  for (Node<e> t = tail, p = t;;) {
    Node<e> q = p.next;
    if (q == null) {
      // p is last node
      if (p.casNext(null, newNode)) {
        // Successful CAS is the linearization point
        // for e to become an element of this queue,
        // and for newNode to become "live".
        if (p != t) // hop two nodes at a time
          casTail(t, newNode); // Failure is OK.
        return true;
      }
      // Lost CAS race to another thread; re-read next
    }
    else if (p == q)
      // We have fallen off list. If tail is unchanged, it
      // will also be off-list, in which case we need to
      // jump to head, from which all live nodes are always
      // reachable. Else the new tail is a better bet.
      p = (t != (t = tail)) ? t : head;
    else
      // Check for tail updates after two hops.
      p = (p != t && t != (t = tail)) ? t : q;
  }
}

이 방법은 대기열 끝에 요소를 추가하는 데 사용됩니다. 여기서는 특정 잠금 없는 알고리즘의 경우 Michael-Scott이 제안한 비차단 연결 목록 링크 알고리즘이 있음을 알 수 있습니다. 사용된. . . 구체적으로 어떻게 작동하는지 보려면 "JAVA Concurrent 프로그래밍 실습"으로 이동하여 더 자세한 소개를 볼 수 있습니다.

또한 다른 메서드는 실제로 잠금 없는 방식으로 구현됩니다.

마지막으로 실제 프로그래밍에서는 동시 환경에서 이러한 잠금 없는 구현을 사용하는 것이 더 낫습니다. 결국 확장성이 더 좋습니다.

요약

위 내용은 CAS 명령어의 잠금 없는 프로그래밍을 구현하는 Java의 예의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.