답변
원자 기준은 모니터 기반 동기화가 적합하지 않은 기준에 대해 간단한 원자 (즉, 스레드 안전 , 사소하지 않은) 작업 을 수행해야하는 설정에서 사용해야합니다 . 마지막으로 확인한대로 객체의 상태가 유지되는 경우에만 특정 필드를 확인하려고한다고 가정합니다.
AtomicReference<Object> cache = new AtomicReference<Object>();
Object cachedValue = new Object();
cache.set(cachedValue);
//... time passes ...
Object cachedValueToUpdate = cache.get();
//... do some work to transform cachedValueToUpdate into a new version
Object newValue = someFunctionOfOld(cachedValueToUpdate);
boolean success = cache.compareAndSet(cachedValue,cachedValueToUpdate);
원자 참조 시맨틱 때문에을 cache
사용하지 않고 오브젝트가 스레드간에 공유되는 경우에도이를 수행 할 수 있습니다 synchronized
. 일반적으로 수행중인 작업을 알지 못하면 동기화 기 또는 java.util.concurrent
프레임 워크를 사용하는 것이 좋습니다 Atomic*
.
이 주제를 소개하는 두 가지 훌륭한 데드 트리 참조 :
(이것이 항상 참인지는 모르겠습니다) 참조 할당 (즉, =
)은 자체가 원자 적입니다 ( 원시적 64 비트 유형을 업데이트 long
하거나 double
원자 적이 지 않을 수도 있음).하지만 64 비트라도 참조를 업데이트하는 것은 항상 원자 적입니다 )를 명시 적으로 사용하지 않고 Atomic*
. Java 언어 사양 3, 섹션 17.7을
참조하십시오 .
답변
원자 참조는 여러 스레드간에 불변 개체의 상태를 공유하고 변경해야 할 때 사용하기에 이상적입니다. 그것은 매우 조밀 한 진술이므로 조금 세분화 할 것입니다.
첫째, 불변 개체는 시공 후 효과적으로 변경되지 않는 개체입니다. 불변 객체의 메서드는 종종 같은 클래스의 새 인스턴스를 반환합니다. 예를 들어 Long과 Double의 래퍼 클래스뿐만 아니라 String도 포함됩니다. ( JVM 불변 객체 에서 프로그래밍 동시성에 따르면 현대 동시성의 중요한 부분입니다).
다음으로, AtomicReference가 공유 값을 공유하는 휘발성 개체보다 나은 이유는 무엇입니까? 간단한 코드 예제는 차이점을 보여줍니다.
volatile String sharedValue;
static final Object lock=new Object();
void modifyString(){
synchronized(lock){
sharedValue=sharedValue+"something to add";
}
}
현재 값을 기준으로 해당 휘발성 필드에서 참조하는 문자열을 수정할 때마다 먼저 해당 객체에 대한 잠금을 얻어야합니다. 이렇게하면 다른 스레드가 들어오는 동안 새 문자열 연결 중에 값이 변경되는 것을 방지 할 수 있습니다. 그런 다음 스레드가 다시 시작되면 다른 스레드의 작업을 방해합니다. 그러나 솔직히 그 코드가 작동하고 깨끗해 보이며 대부분의 사람들을 행복하게 할 것입니다.
약간의 문제가 있습니다. 느립니다. 특히 해당 잠금 개체에 대해 많은 경합이있는 경우. 대부분의 잠금에는 OS 시스템 호출이 필요하기 때문에 스레드는 다른 프로세스를 처리하기 위해 CPU에서 컨텍스트를 차단하고 차단합니다.
다른 옵션은 AtomicRefrence를 사용하는 것입니다.
public static AtomicReference<String> shared = new AtomicReference<>();
String init="Inital Value";
shared.set(init);
//now we will modify that value
boolean success=false;
while(!success){
String prevValue=shared.get();
// do all the work you need to
String newValue=shared.get()+"lets add something";
// Compare and set
success=shared.compareAndSet(prevValue,newValue);
}
왜 이것이 더 좋은가? 솔직히 그 코드는 이전보다 조금 덜 깨끗합니다. 그러나 AtomicRefrence의 후드에서 발생하는 중요한 사항이 있습니다. 그것은 비교와 스왑입니다. OS 호출이 아닌 단일 CPU 명령으로 전환이 발생합니다. 그것은 CPU에 대한 단일 명령입니다. 또한 잠금 장치가 없기 때문에 잠금 장치가 작동되는 경우 상황 전환이 없으므로 시간이 훨씬 절약됩니다!
catch는 AtomicReferences의 경우 .equals () 호출을 사용하지 않고 대신 예상 값에 대한 == 비교입니다. 따라서 루프에서 가져 오기에서 리턴 된 실제 오브젝트가 예상인지 확인하십시오.
답변
AtomicReference의 사용 사례는 다음과 같습니다.
숫자 범위로 작동하고 개별 AtmomicInteger 변수를 사용하여 하한 및 상한을 유지하는이 클래스를 고려하십시오.
public class NumberRange {
// INVARIANT: lower <= upper
private final AtomicInteger lower = new AtomicInteger(0);
private final AtomicInteger upper = new AtomicInteger(0);
public void setLower(int i) {
// Warning -- unsafe check-then-act
if (i > upper.get())
throw new IllegalArgumentException(
"can't set lower to " + i + " > upper");
lower.set(i);
}
public void setUpper(int i) {
// Warning -- unsafe check-then-act
if (i < lower.get())
throw new IllegalArgumentException(
"can't set upper to " + i + " < lower");
upper.set(i);
}
public boolean isInRange(int i) {
return (i >= lower.get() && i <= upper.get());
}
}
setLower 및 setUpper는 모두 확인 후 동작 시퀀스이지만 원자를 만들기에 충분한 잠금을 사용하지 않습니다. 숫자 범위가 (0, 10)을 유지하고 하나의 스레드가 setLower (5)를 호출하고 다른 스레드가 setUpper (4)를 호출하는 경우, 운이 좋지 않은 타이밍이 있으면 둘 다 setter의 검사를 통과하고 두 수정이 모두 적용됩니다. 결과적으로 범위는 이제 유효하지 않은 상태 (5, 4)를 유지합니다. 따라서 기본 AtomicInteger는 스레드로부터 안전하지만 복합 클래스는 그렇지 않습니다. 상한과 하한에 개별 AtomicInteger를 사용하는 대신 AtomicReference를 사용하여이 문제를 해결할 수 있습니다.
public class CasNumberRange {
// Immutable
private static class IntPair {
final int lower; // Invariant: lower <= upper
final int upper;
private IntPair(int lower, int upper) {
this.lower = lower;
this.upper = upper;
}
}
private final AtomicReference<IntPair> values =
new AtomicReference<IntPair>(new IntPair(0, 0));
public int getLower() {
return values.get().lower;
}
public void setLower(int lower) {
while (true) {
IntPair oldv = values.get();
if (lower > oldv.upper)
throw new IllegalArgumentException(
"Can't set lower to " + lower + " > upper");
IntPair newv = new IntPair(lower, oldv.upper);
if (values.compareAndSet(oldv, newv))
return;
}
}
public int getUpper() {
return values.get().upper;
}
public void setUpper(int upper) {
while (true) {
IntPair oldv = values.get();
if (upper < oldv.lower)
throw new IllegalArgumentException(
"Can't set upper to " + upper + " < lower");
IntPair newv = new IntPair(oldv.lower, upper);
if (values.compareAndSet(oldv, newv))
return;
}
}
}
답변
낙관적 잠금을 적용 할 때 AtomicReference를 사용할 수 있습니다. 공유 객체가 있고 둘 이상의 스레드에서 변경하려고합니다.
- 공유 객체의 사본을 만들 수 있습니다
- 공유 객체 수정
- 공유 객체가 여전히 이전과 동일한 지 확인해야합니다. 그렇다면, 수정 된 사본을 참조하여 업데이트하십시오.
다른 스레드가 수정 했으므로이 두 단계 사이에서 수정할 수 있습니다. 원자 작업에서 수행해야합니다. 이것이 AtomicReference가 도울 수있는 곳입니다
답변
다음은 매우 간단한 사용 사례이며 스레드 안전과 관련이 없습니다.
람다 호출간에 객체를 공유하려면AtomicReference
다음 옵션이 있습니다 .
public void doSomethingUsingLambdas() {
AtomicReference<YourObject> yourObjectRef = new AtomicReference<>();
soSomethingThatTakesALambda(() -> {
yourObjectRef.set(youObject);
});
soSomethingElseThatTakesALambda(() -> {
YourObject yourObject = yourObjectRef.get();
});
}
나는 이것이 좋은 디자인이나 아무것도 아니라고 말하지는 않지만 (단순한 예일뿐입니다) 람다 호출간에 객체를 공유 해야하는 경우가 있습니다 AtomicReference
.
실제로 참조를 보유한 모든 객체를 사용할 수 있으며, 항목이 하나만있는 Collection도 있습니다. 그러나 AtomicReference는 완벽하게 맞습니다.
답변
많이 말하지 않겠습니다. 이미 존경받는 동료 친구들이 귀중한 의견을 제시했습니다. 이 블로그 마지막에 본격적인 실행 코드가 있으면 혼동을 제거해야합니다. 멀티 스레드 시나리오에서 영화 좌석 예약 소규모 프로그램에 관한 것입니다.
몇 가지 중요한 기본 사실은 다음과 같습니다. 1> 다른 스레드는 힙 공간에서 예를 들어 정적 멤버 변수에만 경합 할 수 있습니다. 2> 휘발성 읽기 또는 쓰기는 완전히 원자화되고 직렬화 / 합판되어 메모리에서만 수행됩니다. 이것을 말하는 것은 모든 읽기가 메모리의 이전 쓰기를 따른다는 것을 의미합니다. 모든 쓰기는 메모리에서 이전 읽기를 따릅니다. 따라서 휘발성으로 작업하는 스레드는 항상 최신 값을 볼 수 있습니다.
AtomicReference는이 휘발성 속성을 사용합니다.
다음은 AtomicReference의 소스 코드 중 일부입니다. AtomicReference는 객체 참조를 나타냅니다. 이 참조는 다음과 같이 AtomicReference 인스턴스의 휘발성 멤버 변수입니다.
private volatile V value;
get ()은 단순히 변수의 최신 값을 반환합니다 (휘발성은 “이전에 발생”).
public final V get()
다음은 AtomicReference의 가장 중요한 방법입니다.
public final boolean compareAndSet(V expect, V update) {
return unsafe.compareAndSwapObject(this, valueOffset, expect, update);
}
compareAndSet (expect, update) 메소드는 안전하지 않은 Java 클래스의 compareAndSwapObject () 메소드를 호출합니다. 안전하지 않은이 메소드 호출은 기본 호출을 호출하여 프로세서에 대한 단일 명령을 호출합니다. “예상”및 “업데이트”는 각각 개체를 참조합니다.
AtomicReference 인스턴스 멤버 변수 “value”가 동일한 오브젝트를 “expect”로 참조하는 경우에만 “update”가이 인스턴스 변수에 지정되고 “true”가 리턴됩니다. 그렇지 않으면 false가 반환됩니다. 모든 것은 원자 적으로 이루어집니다. 다른 스레드는 사이를 가로 챌 수 없습니다. 이것은 단일 프로세서 작업 (현대 컴퓨터 아키텍처의 마술)이므로 동기화 된 블록을 사용하는 것보다 빠릅니다. 그러나 여러 변수를 원자 적으로 업데이트해야하는 경우 AtomicReference가 도움이되지 않습니다.
이클립스에서 실행할 수있는 본격적인 실행 코드를 추가하고 싶습니다. 많은 혼란이 사라질 것입니다. 여기 22 명의 사용자 (MyTh 스레드)가 20 개의 좌석을 예약하려고합니다. 다음은 코드 스 니펫과 전체 코드입니다.
22 명의 사용자가 20 개의 좌석을 예약하려고하는 코드 스 니펫.
for (int i = 0; i < 20; i++) {// 20 seats
seats.add(new AtomicReference<Integer>());
}
Thread[] ths = new Thread[22];// 22 users
for (int i = 0; i < ths.length; i++) {
ths[i] = new MyTh(seats, i);
ths[i].start();
}
다음은 전체 실행 코드입니다.
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
public class Solution {
static List<AtomicReference<Integer>> seats;// Movie seats numbered as per
// list index
public static void main(String[] args) throws InterruptedException {
// TODO Auto-generated method stub
seats = new ArrayList<>();
for (int i = 0; i < 20; i++) {// 20 seats
seats.add(new AtomicReference<Integer>());
}
Thread[] ths = new Thread[22];// 22 users
for (int i = 0; i < ths.length; i++) {
ths[i] = new MyTh(seats, i);
ths[i].start();
}
for (Thread t : ths) {
t.join();
}
for (AtomicReference<Integer> seat : seats) {
System.out.print(" " + seat.get());
}
}
/**
* id is the id of the user
*
* @author sankbane
*
*/
static class MyTh extends Thread {// each thread is a user
static AtomicInteger full = new AtomicInteger(0);
List<AtomicReference<Integer>> l;//seats
int id;//id of the users
int seats;
public MyTh(List<AtomicReference<Integer>> list, int userId) {
l = list;
this.id = userId;
seats = list.size();
}
@Override
public void run() {
boolean reserved = false;
try {
while (!reserved && full.get() < seats) {
Thread.sleep(50);
int r = ThreadLocalRandom.current().nextInt(0, seats);// excludes
// seats
//
AtomicReference<Integer> el = l.get(r);
reserved = el.compareAndSet(null, id);// null means no user
// has reserved this
// seat
if (reserved)
full.getAndIncrement();
}
if (!reserved && full.get() == seats)
System.out.println("user " + id + " did not get a seat");
} catch (InterruptedException ie) {
// log it
}
}
}
}
답변
언제 AtomicReference를 사용합니까?
AtomicReference 는 동기화를 사용하지 않고 변수 값을 원자 적으로 업데이트하는 유연한 방법입니다.
AtomicReference
단일 변수에서 잠금없는 스레드 안전 프로그래밍을 지원합니다.
높은 수준의 동시 API로 스레드 안전성을 달성하는 방법에는 여러 가지가 있습니다 . 원자 변수는 여러 옵션 중 하나입니다.
Lock
객체는 많은 동시 응용 프로그램을 단순화하는 잠금 관용구를 지원합니다.
Executors
스레드 시작 및 관리를위한 고급 API를 정의합니다. java.util.concurrent가 제공하는 실행기 구현은 대규모 애플리케이션에 적합한 스레드 풀 관리를 제공합니다.
동시 수집을 통해 대규모 데이터 수집을보다 쉽게 관리 할 수 있으며 동기화 필요성을 크게 줄일 수 있습니다.
원자 변수 에는 동기화를 최소화하고 메모리 일관성 오류를 방지하는 기능이 있습니다.
AtomicReference를 사용해야하는 간단한 예를 제공하십시오.
샘플 코드 AtomicReference
:
String initialReference = "value 1";
AtomicReference<String> someRef =
new AtomicReference<String>(initialReference);
String newReference = "value 2";
boolean exchanged = someRef.compareAndSet(initialReference, newReference);
System.out.println("exchanged: " + exchanged);
모든 멀티 스레드 프로그램에서 객체를 생성해야합니까?
AtomicReference
모든 멀티 스레드 프로그램에서 사용할 필요는 없습니다 .
단일 변수를 보호하려면을 사용하십시오 AtomicReference
. 코드 블록을 보호하려면 Lock
/ synchronized
etc 와 같은 다른 구문을 사용하십시오 .