JUC 패키지 의 분할 치료 전략-성능 향상 을 위 한 생 성
이번 공 유 는 JUC 가방 의 재 미 있 는 종 류 를 함께 연구 하고 자 합 니 다.AtomicLong&LongAdder,ThreadLocalRandom 원 리 를 포함 합 니 다.
2.AtomicLong&LongAdder
2.1 AtomicLong 류
AtomicLong 은 JUC 패키지 가 제공 하 는 원자 성 조작 류 로 그 내 부 는 CAS 를 통 해 계수 에 대한 원자 성 업데이트 작업 을 보장 한다.
원본 코드 를 뒤 져 보면 내 부 는 UnSafe(rt.jar)와 같은 CAs 작업 을 통 해 내부 의 카운터 변수 인 long value 를 원자 적 으로 업데이트 할 수 있 습 니 다.예 를 들 어 JDK 8 에서:
public final long incrementAndGet() {
return unsafe.getAndAddLong(this, valueOffset, 1L) + 1L;
}
그 중에서 unsafe.getAndAddLong 의 코드 는 다음 과 같다.
public final long getAndAddLong(Object paramObject, long paramLong1, long paramLong2)
{
long l;
do
{
l = getLongVolatile(paramObject, paramLong1);//(1)
} while (!compareAndSwapLong(paramObject, paramLong1, l, l + paramLong2));//(2)
return l;
}
최종 호출 은 native 방법 compare AndSwapLong 원자 조작 임 을 알 수 있 습 니 다.
여러 스 레 드 가 같은 AtomicLong 인 스 턴 스 의 increment AndGet 방법 을 호출 하면 여러 스 레 드 가 unsafe.getAndAddLong 방법 으로 실 행 됩 니 다.그리고 여러 스 레 드 가 코드(1)에 실 행 됩 니 다.계수기 의 값 을 가 져 온 다음 에 코드(2)를 실행 합 니 다.여러 스 레 드 가 동시에 코드(2)를 실행 하면 CAS 가 원자 성 을 가지 기 때문에그래서 하나의 스 레 드 만 업데이트 에 성공 하고 트 루 로 돌아 가 순환 을 종료 합 니 다.전체 업데이트 작업 은 OK 입 니 다.다른 스 레 드 는 CAS 가 false 로 돌아 가지 못 하면(1)에서 현재 계수기 의 값 을 한 번 반복 해서 가 져 온 다음(2)실행 을 시도 합 니 다.이것 은 CAS 의 자전 작업 이 라 고 합 니 다.본질은 Cpu 자원 을 사용 하여 자물쇠 로 가 져 온 컨 텍스트 전환 등 비용 으로 바 꾸 는 것 입 니 다.
2.2 LongAdder 류
AtomicLong 류 는 개발 자 에 게 스 레 드 안전 한 계수 기 를 사용 하 는 데 편 의 를 제공 합 니 다.그러나 AtomicLong 은 높 고 보 내 는 데 문제 가 있 습 니 다.상기 와 같이 대량의 스 레 드 가 같은 AtomicLong 의 인 스 턴 스 를 호출 하 는 방법 을 사용 할 때 하나의 스 레 드 만 CAS 카운터 의 값 이 성공 하고 실패 한 스 레 드 는 제자리 에서 cpu 를 차지 하여 자전 재 시도 합 니 다.이번 에는 대량의 스 레 드 가 cpu 를 헛되이 낭비 하여 제자리 에서 자전 하 게 만 들 었 다.
JDK 8 에 LongAdder 류 가 추가 되 었 습 니 다.이 는 분 리 된 전략 으로 같은 변수의 합병 경쟁 도 를 줄 이 는 것 입 니 다.LongAdder 의 핵심 사상 은 하나의 원자 변 수 를 여러 변수 로 분해 하여 같은 여러 개의 스 레 드 로 여러 개의 자원 을 경쟁 하 게 하 는 것 입 니 다.그러면 모든 자원 의 스 레 드 수 를 경쟁 하 는 것 입 니 다.다음은 도형 을 통 해 두 디자인 의 차이 점 을 이해한다.
위의 그림 과 같이 AtomicLong 은 여러 스 레 드 가 같은 원자 변 수 를 동시에 경쟁 합 니 다.
위의 그림 에서 LongAdder 내부 에서 여러 개의 Cell 변 수 를 유지 하고 똑 같은 병발 량 의 상황 에서 하나의 변수 업데이트 작업 을 쟁탈 하 는 스 레 드 량 이 줄 어 들 것 이다.이것 은 공유 자원 을 쟁탈 하 는 병발 량 을 변형 적 으로 감소 시 킨 것 이다.
다음은 셀 의 구 조 를 살 펴 보 겠 습 니 다.
@sun.misc.Contended static final class Cell {
volatile long value;
Cell(long x) { value = x; }
final boolean cas(long cmp, long val) {
return UNSAFE.compareAndSwapLong(this, valueOffset, cmp, val);
}
// Unsafe
private static final sun.misc.Unsafe UNSAFE;
private static final long valueOffset;
static {
try {
UNSAFE = sun.misc.Unsafe.getUnsafe();
Class> ak = Cell.class;
valueOffset = UNSAFE.objectFieldOffset
(ak.getDeclaredField("value"));
} catch (Exception e) {
throw new Error(e);
}
}
}
LongAdder 는 초기 화 지연 원자 업데이트 배열(기본 적 인 상황 에서 Cell 배열 은 null)과 기본 값 변수 base 를 유지 합 니 다.Cells 가 메모 리 를 사용 하 는 것 이 상대 적 으로 크기 때문에 처음에는 만 들 지 않 고 필요 할 때 만 듭 니 다.즉,타성 생 성 입 니 다.
처음에 cell 배열 이 null 이 고 동시 다발 스 레 드 가 적 을 때 모든 누적 작업 은 base 변 수 를 진행 하 는 것 이 라 고 판 단 했 을 때 AtomicLong 으로 퇴화 되 었 다.cell 배열 의 크기 는 2 의 N 제곱 크기 를 유지 합 니 다.초기 화 할 때 Cell 배열 의 중 Cell 요소 개 수 는 2 이 고 배열 안의 변수 실 체 는 Cell 형식 입 니 다.
여러 스 레 드 가 같은 Cell 원자 변 수 를 쟁탈 할 때 실패 하면 현재 cell 변수 에서 CAS 재 시도 가 아니 라 다른 Cell 변수 에서 CAS 시 도 를 시도 합 니 다.이 변 화 는 현재 스 레 드 재 시도 시 CAS 가 성공 할 가능성 을 증가 시 킵 니 다.마지막 으로 LongAdder 현재 값 을 가 져 올 때 모든 Cell 변수의 value 값 을 누적 한 후 base 를 추가 하여 되 돌려 줍 니 다.다음 코드:
public long sum() {
Cell[] as = cells; Cell a;
long sum = base;
if (as != null) {
for (int i = 0; i < as.length; ++i) {
if ((a = as[i]) != null)
sum += a.value;
}
}
return sum;
}
위의 코드 에서 알 수 있 듯 이 먼저 base 의 값 을 sum 변수 에 할당 한 다음 에 순환 을 통 해 모든 cell 요소 의 value 값 을 sum 변수 에 누적 한 다음 에 sum 으로 되 돌려 줍 니 다.
사실은 이것 은 분 리 된 전략 이다.먼저 병발 량 을 여러 개의 원자 변수 에 분담 하고 여러 개의 스 레 드 가 동시에 서로 다른 원자 변 수 를 조작 한 다음 에 계 수 를 얻 을 때 모든 원자 변수의 계수 와 누적 을 얻는다.
사고 문제:
성능 대비 http://blog.palominolabs.com/2014/02/10/java-8-performance-improvements-longadder-vs-atomiclong/
3.랜 덤&ThreadLocalRandom
3.1 랜 덤 류 의 원리 와 한계 성
JDK 7 이전에 현재 자바 util.Random 을 포함 하여 비교적 광범 위 한 난수 생 성 도구 류 를 사용 해 야 합 니 다.다음은 간단 한 코드 를 통 해 자바 util.Random 이 어떻게 사용 하 는 지 보 겠 습 니 다.
public class RandomTest {
public static void main(String[] args) {
//(1)
Random random = new Random();
//(2) 10 0-5( 0, 5)
for (int i = 0; i < 10; ++i) {
System.out.println(random.nextInt(5));
}
}
}
public int nextInt(int bound) {
//(3)
if (bound <= 0)
throw new IllegalArgumentException(BadBound);
//(4)
int r = next(31);
//(5)
...
return r;
}
위 코드 에서 알 수 있 듯 이 새로운 난수 생 성 은 두 단계 가 필요 합 니 다.
다음은 next()코드 를 보 겠 습 니 다.
protected int next(int bits) {
long oldseed, nextseed;
AtomicLong seed = this.seed;
do {
//(6)
oldseed = seed.get();
//(7)
nextseed = (oldseed * multiplier + addend) & mask;
//(8)
} while (!seed.compareAndSet(oldseed, nextseed));
//(9)
return (int)(nextseed >>> (48 - bits));
}
3.2 ThreadLocalRandom
Random 클래스 생 성 난수 원리 및 부족:모든 Random 인 스 턴 스 에는 현재 피 드 의 값 을 기록 하 는 원자 변 수 를 가지 고 있 습 니 다.새로운 랜 덤 수 를 생 성 하려 면 현재 피 드 에 따라 새로운 피 드 를 계산 하고 원자 변 수 를 업데이트 해 야 합 니 다.
다 중 스 레 드 에서 하나의 Random 인 스 턴 스 를 사용 하여 랜 덤 수 를 생 성 할 때 여러 스 레 드 가 동시에 랜 덤 수 를 계산 하여 새로운 피 드 를 계산 할 때 여러 스 레 드 는 같은 원자 변수의 업데이트 작업 을 경쟁 합 니 다.원자 변수의 업 데 이 트 는 CAS 작업 이 고 하나의 스 레 드 만 성공 하기 때문에 CAS 작업 에 실패 한 대량의 스 레 드 는 자전 재 시도 합 니 다.한편,대량의 스 레 드 의 자전 재 시 도 는 병행 성능 을 낮 추고 CPU 자원 을 소모 할 수 있 습 니 다.이 문 제 를 해결 하기 위해 ThreadLocal Random 류 가 생 겨 났 습 니 다.
public class RandomTest {
public static void main(String[] args) {
//(10)
ThreadLocalRandom random = ThreadLocalRandom.current();
//(11) 10 0-5( 0, 5)
for (int i = 0; i < 10; ++i) {
System.out.println(random.nextInt(5));
}
}
}
위의 코드(10)와 같이 ThreadLocalRandom.current()를 호출 하여 현재 스 레 드 의 난수 생 성 기 를 가 져 옵 니 다.ThreadLocal Random 의 실현 원 리 를 분석 해 보 겠 습 니 다.
이름 으로 보면 ThreadLocal 류 를 연상 시 킬 수 있 습 니 다.ThreadLocal 은 모든 스 레 드 에 변 수 를 복사 하도록 합 니 다.모든 스 레 드 가 변 수 를 조작 할 때 실제 적 으로 자신의 로 컬 메모리 에 있 는 복사 본 을 조작 하여 공유 변 수 를 동기 화 하 는 것 을 피 합 니 다.실제로 스 레 드 로 컬 랜 덤 의 실현 도 이 원리 다.Random 의 단점 은 여러 스 레 드 가 원자 피 드 변 수 를 사용 하여 원자 변 수 를 업데이트 하 는 경쟁 을 초래 할 수 있다 는 것 이다.이 원 리 는 아래 그림 을 통 해 표현 할 수 있다.
그러면 모든 스 레 드 가 자신의 피 드 변 수 를 유지 하고 모든 스 레 드 가 랜 덤 수 를 생 성 할 때 자신의 로 컬 메모리 에 있 는 오래된 피 드 에 따라 새로운 피 드 를 계산 하고 새로운 피 드 를 사용 하여 오래된 피 드 를 업데이트 한 다음 에 새로운 피 드 에 따라 랜 덤 수 를 계산 하면 경쟁 문제 가 존재 하지 않 습 니 다.이것 은 병행 성능 을 크게 향상 시 킬 것 입 니 다.아래 그림 과 같이 ThreadLocalRandom 원 리 는 다음 그림 으로 표현 할 수 있 습 니 다.
Thread 클래스 에는 몇 개의 변수 가 있 습 니 다.
/** The current seed for a ThreadLocalRandom */
@sun.misc.Contended("tlr")
long threadLocalRandomSeed;
/** Probe hash value; nonzero if threadLocalRandomSeed initialized */
@sun.misc.Contended("tlr")
int threadLocalRandomProbe;
사고 문제:
졸작 자바 병렬 프로 그래 밍 의 아름다움 한 책의 장절 추출이번 공 유 는 먼저 AtomicLong 의 내부 실현 과 존재 하 는 단점 을 설명 한 다음 에 LongAdder 가 분 리 된 전략 으로 여러 개의 원자 변 수 를 사용 하여 하나의 원자 변수 경쟁의 병발 도 를 줄 이 는 것 을 설명 했다.그 다음 에 Random 과 그 단점 을 간단하게 소개 했다.마지막 으로 ThreadLocal Random 은 ThreadLocal 의 사상 을 빌려 다 중 스 레 드 가 같은 원자 변수 경쟁 자물쇠 에 가 져 온 성능 손실 을 해결 했다.사실 JUC 가방 에는 포크-join 프레임 워 크 등 다른 전형 적 인 구성 요소 도 있다.
저자:더하기
원문 을 읽다
본 고 는 운 서 지역사회 의 오리지널 내용 으로 허락 없 이 전재 할 수 없다.
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
Python에서 random 모듈을 사용하는 방법randrange (start,end,step) 는 range (start,end,step) 가 생성한 목록의 무작위 값을 되돌려줍니다.여기는 왼쪽으로 닫고 오른쪽으로 여는 구간입니다. choice () 는 교체 가...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.