이 기사에서는 주로 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 중국어 웹사이트의 기타 관련 기사를 참조하세요!