얼마 전 AtomicInteger의 자동 증가 성능을 테스트하기 위해 이전에 작성한 코드를 실수로 실행하여 동기화했는데, 예기치 않게 AtomicInteger의 성능이 동기화된 것보다 낫다는 것을 발견했습니다. 다음이 발견됩니다:
jdk1.7에서 AtomicInteger의 getAndIncrement는 다음과 같습니다:
public final int getAndIncrement() { for (;;) { int current = get(); int next = current + 1; if (compareAndSet(current, next)) return current; } } public final boolean compareAndSet(int expect, int update) { return unsafe.compareAndSwapInt(this, valueOffset, expect, update); }
그리고 jdk1.8에서는 다음과 같습니다:
public final int getAndIncrement() { return unsafe.getAndAddInt(this, valueOffset, 1); }
jdk1.8에서는 Unsafe의 getAndAddInt 메소드를 직접 사용하지만, jdk1.7의 Unsafe에서는 그러한 메소드가 없음을 알 수 있다. (PS: 이유를 알아보기 위해 Unsafe를 디컴파일한 결과 CAS의 실패한 재시도가 getAndAddInt 메소드에서 수행된 것을 발견했습니다. Reflection을 사용하여 Unsafe 인스턴스를 가져오고 getAndAddInt와 동일한 코드를 작성했지만 테스트 결과는 다음과 같습니다. jdk1 .7의 getAndIncrement도 마찬가지로 느립니다. Unsafe에서 어떤 종류의 흑마술이 실행되는지 모르겠습니다. 전문가의 조언을 부탁드립니다.) (추가: 기사 끝에 추론이 있습니다.)
AtomicInteger의 소스 코드를 살펴보면 getAndAdd, addAndGet 등의 메소드도 대부분 영향을 받는 것을 알 수 있습니다.
이번 CAS 개선으로 논블로킹 알고리즘을 사용해야 하는 또 다른 이유가 생겼습니다.
마지막으로 테스트 코드가 제공됩니다. 이 테스트 방법은 간단하고 조악합니다. 단순히 동기화가 더 좋다고 말할 수는 없습니다. 게다가 실제 사용 시에는 이렇게 높은 경쟁 강도를 가질 수 없습니다. 이 비교는 단지 참고용으로만 사용된다는 것입니다. AtomicInteger.getAndIncrement의 성능이 크게 향상되었습니다.
package performance; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.locks.LockSupport; public class AtomicTest { //测试规模,调用一次getAndIncreaseX视作提供一次业务服务,记录提供TEST_SIZE次服务的耗时 private static final int TEST_SIZE = 100000000; //客户线程数 private static final int THREAD_COUNT = 10; //使用CountDownLatch让各线程同时开始 private CountDownLatch cdl = new CountDownLatch(THREAD_COUNT + 1); private int n = 0; private AtomicInteger ai = new AtomicInteger(0); private long startTime; public void init() { startTime = System.nanoTime(); } /** * 使用AtomicInteger.getAndIncrement,测试结果为1.8比1.7有明显性能提升 * @return */ private final int getAndIncreaseA() { int result = ai.getAndIncrement(); if (result == TEST_SIZE) { System.out.println(System.nanoTime() - startTime); System.exit(0); } return result; } /** * 使用synchronized来完成同步,测试结果为1.7和1.8几乎无性能差别 * @return */ private final int getAndIncreaseB() { int result; synchronized (this) { result = n++; } if (result == TEST_SIZE) { System.out.println(System.nanoTime() - startTime); System.exit(0); } return result; } /** * 使用AtomicInteger.compareAndSet在java代码层面做失败重试(与1.7的AtomicInteger.getAndIncrement的实现类似), * 测试结果为1.7和1.8几乎无性能差别 * @return */ private final int getAndIncreaseC() { int result; do { result = ai.get(); } while (!ai.compareAndSet(result, result + 1)); if (result == TEST_SIZE) { System.out.println(System.nanoTime() - startTime); System.exit(0); } return result; } public class MyTask implements Runnable { @Override public void run() { cdl.countDown(); try { cdl.await(); } catch (InterruptedException e) { e.printStackTrace(); } while (true) getAndIncreaseA();// getAndIncreaseB(); } } public static void main(String[] args) throws InterruptedException { AtomicTest at = new AtomicTest(); for (int n = 0; n < THREAD_COUNT; n++) new Thread(at.new MyTask()).start(); System.out.println("start"); at.init(); at.cdl.countDown(); } }
다음은 Intel(R) Core(TM) i7-4710HQ CPU @2.50GHz(4코어 8스레드)에서의 테스트 결과입니다(변동이 적으므로 각 항목은 4개 또는 4개 스레드만 테스트했습니다. 다섯 번 더 중간 값 중 하나를 선택합니다.):
jdk1.7
AtomicInteger.getAndIncrement 12,653,757,034
synchronized 4,146,813,462
AtomicInteger.compareAndSet 1 2,952,821,234
jdk1.8
AtomicInteger.getAndIncrement 2,159,486,620
동기화 4,067,309,911
AtomicInteger.compareAndSet 12,893,188, 541
보충 : 네티즌 요청에 따라 , Unsafe는 getAndAddInt의 관련 소스 코드와 내 테스트 코드를 제공합니다.
jad를 사용하여 jdk1.8에서 안전하지 않은 소스 코드를 디컴파일합니다:
public final int getAndAddInt(Object obj, long l, int i) { int j; do j = getIntVolatile(obj, l); while(!compareAndSwapInt(obj, l, j, j + i)); return j; } public native int getIntVolatile(Object obj, long l); public final native boolean compareAndSwapInt(Object obj, long l, int i, int j);
openjdk8의 안전하지 않은 소스 코드:
public final int getAndAddInt(Object o, long offset, int delta) { int v; do { v = getIntVolatile(o, offset); } while (!compareAndSwapInt(o, offset, v, v + delta)); return v; } public native int getIntVolatile(Object o, long offset); public final native boolean compareAndSwapInt(Object o, long offset, int expected, int x);
내 테스트 코드(팁: if eclipse IDE가 오류를 보고하기를 기다리는 것은 제한된 Unsafe를 사용하기 때문입니다. 경고 수준을 오류에서 경고로 줄일 수 있습니다. Baidu를 참조하세요.):
... import sun.misc.Unsafe; public class AtomicTest { .... private Unsafe unsafe; private long valueOffset; public AtomicTest(){ Field f; try { f = Unsafe.class.getDeclaredField("theUnsafe"); f.setAccessible(true); unsafe = (Unsafe)f.get(null); valueOffset = unsafe.objectFieldOffset(AtomicInteger.class.getDeclaredField("value")); }catch(NoSuchFieldException e){ ... } } private final int getAndIncreaseD(){ int result; do{ result = unsafe.getIntVolatile(ai, valueOffset); }while(!unsafe.compareAndSwapInt(ai, valueOffset, result, result+1)); if(result == MAX){ System.out.println(System.nanoTime()-startTime); System.exit(0); } return result; } ... }
보조 2: For 성능 향상의 이유는 다음과 같습니다. (jvm의 소스 코드가 인수로 사용되지 않기 때문에) 다음 추론이 100% 정확하다고 말할 수는 없지만 여전히 매우 확신합니다. 네티즌 @zhoukeren@liuxinglanyue에게 감사드립니다!
Unsafe는 특수하게 처리되어 일반 Java 코드로 이해할 수 없습니다. 차이점은 다음과 같습니다.
getAndAddInt를 호출할 때 시스템의 하위 계층에서 가져오기 및 추가를 지원하면 실행됩니다. 가져오기 및 추가를 사용하는 기본 메서드입니다.
지원되지 않는 경우 위에 표시된 getAndAddInt 메서드 본문을 따르고 비교 및 교환을 사용하여 Java 코드 형식으로 실행합니다.
이는 openjdk8의 Unsafe::getAndAddInt: // The following contain CAS-based Java implementations used on
// platforms not supporting native instructions
위의
과도 일치합니다. Unsafe의 특수 처리는 위에서 언급한 "흑마법"입니다.
위 내용은 Java8의 CAS 개선 사항에 대한 자세한 소개의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!