数日前、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メソッドが直接使用されており、Unsafeではjdk1.7ではそのような方法はありません。 (追記:原因を調べるためにUnsafeを逆コンパイルしたところ、CASのリトライ失敗がgetAndAddIntメソッドで行われていることが分かりました。リフレクションを利用してUnsafeインスタンスを取得し、getAndAddIntと同じコードを書きましたが、テスト結果はjdk1 .7 の getAndIncrement と同じくらい遅いです。Unsafe でどのような黒魔術が行われるのかわかりません。アドバイスをお願いします。) (追記: 記事の最後に推論があります)
AtomicInteger のソース コードを見ると、 getAndAdd 、 addAndGet およびその他のほとんどのメソッドも影響を受けていることがわかります。
今回の CAS の機能強化により、ノンブロッキング アルゴリズムを使用する理由がまた 1 つ増えました。
最後に、このテスト方法は単純で粗雑であることに注意してください。単純に同期の方が優れているとは言えません。実際の使用では業務処理もあり、このような激しい競争はあり得ません。この比較はあくまで参考として使用されます。大幅に改善されました。
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 ~ 5 回のみであり、高い方の値 (中央の値):
jdk1.7
AtomicInteger.getAndIncrement 12,653,757,034
synchronized 4,146,813,462
AtomicInteger.compareAndSet 12,952,821,234
jdk1.8
AtomicInteger.getAndIncrement 2,159,486,620
同期4,067,309,911
AtomicInteger。 CompareAndSet 12,893,188,541
補足: ネットユーザーのリクエストにより、Unsafe.getAndAddInt の関連ソース コードと私のテスト コードをここに示します。
jdk1.8のUnsafeをjadで逆コンパイルして得られたソースコード:
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のUnsafeソースコード:
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);
私のテストコード(ヒント: 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: パフォーマンスが向上した理由については、あえて 100 とは言いませんが、以下の推論があります。 % 正しいです (jvm のソース コードが引数として使用されていないため)。それでも、netizen @zhoukeren@liuxinglanyue のおかげで、かなり確信しています。
Unsafe は特別に処理され、通常の Java コードとしては理解できません。 違いは次のとおりです。
getAndAddInt を呼び出すとき、システムの最下層がフェッチアンド追加をサポートしている場合、ネイティブ メソッドが実行され、 fetch -and が使用されます。 -add;
サポートされていない場合は、上記の getAndAddInt メソッド本体に従い、compare-and-swap を使用して Java コードの形式で実行します。
これは、openjdk8 の Unsafe と同じです: コメント上記 :getAndAddInt が一致するもの:
// The following contain CAS-based Java implementations used on // platforms not supporting native instructions
Unsafe の特殊な処理は、上で述べた「黒魔術」です。
以上がJava8 の CAS の機能強化の詳細な紹介の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。