0

オブジェクトを作成し、作成されたオブジェクトの数を増やすコードがあります。オブジェクトを作成してカウントを増やすコードは、カウントがオブジェクトの MAX 数に達したかどうかをチェックする if 条件によって保護されています。コードをマルチスレッドで実行すると、行の位置によってカウントがインクリメントされると判断された if 条件に違反する (つまり、影響がない) 可能性があります。これは、Java コンパイラーの最適化が原因でしょうか? コード スニペットは次のとおりです。

private BlockingQueue<WeakReference<ItemType>> items;
private final ReferenceQueue<ItemType> referenceQueue = new ReferenceQueue<ItemType>();
private final int maxSize = 10;
private final AtomicInteger numberOfCreatedObj = new AtomicInteger(0);

protected ItemType create(boolean addToCache) {
ItemType item = null;
try {           
    if (!hasCreatedMaxObjects()) {
        // we have not reached maxSize yet.
        item = getFactory().create();
        // this position makes the MAX objects checking working
                    // Position A:
        increaseCreatedObjectCount();
        LOG.debug("Created new item [" + item.getId() + "]");
        if (addToCache) {
            LOG.debug("Add to cache the new item [" + item.getId() + "]");
            addCreated(item);
        }
        // This position makes the MAX objects checking failed
                    // Position B;      
        //increaseCreatedObjectCount();             
    } else {
        LOG.warn("Already reached MAX created objects " + numberOfCreatedObj.get());
    }
} catch (Exception e) {
    LOG.error("Error in creating a new object", e);
}
return item;
} 

protected boolean hasCreatedMaxObjects() {      
return getNumberOfCreatedObj().compareAndSet(getMaxSize(), getMaxSize());
}

protected void increaseCreatedObjectCount() {
getNumberOfCreatedObj().incrementAndGet();
}

protected void addCreated(ItemType item) {
    items.offer(new WeakReference<ItemType>(item, referenceQueue));
}

30 スレッドでテストを実行しました。各スレッドは、作成されたオブジェクトを取得した後、100 ミリ秒間スリープします。位置 A で increaseCreatedObjectCount() が呼び出されると、コードは正常に機能し、10 (MAX) 個のオブジェクトが作成されました。位置 B で increaseCreatedObjectCount() が呼び出されると、30 個のオブジェクトが作成されました。これは、実行中のスレッドの数と同じです。

Java コンパイラーに最適化されたコードを表示するにはどうすればよいですか?

ありがとうございました。

4

1 に答える 1

3

問題を完全に理解しているかどうかはわかりませんが、コードは基本的に競合状態であるため、コードに競合状態があることは確かです

if (count < max) {
    count++;

2 つのスレッドが最初に最大数に達したかどうかを並行してチェックし、次に並行してカウントをインクリメントすると、スロットが 1 つしか残っていなくても、もちろん最大値は 2 回実装されます。

  • スレッド A、値をチェック、値 = 9、if ブロックに入る
  • スレッド B、値を確認、値 = 9、ブロックの場合に入る
  • スレッド A、インクリメント、値 = 10
  • スレッド B、インクリメント、値 = 11

そして、インクリメントが位置 A ではなく位置 B にあるときに発生することは驚くことではありません。これは、カウントがまだインクリメントされていない時間枠がはるかに長く、別のスレッドが if ブロックに入る可能性があるためです。より明確にするために、位置 B でのインクリメントを含むコードは、

if (count < max) {
    sleep(ENOUGH_TIME_FOR_ANOTHER_THREAD_TO_STILL_SEE_THE_NON_INCREMENTED_VALUE)
    count++;

したがって、コンパイラの最適化は問題とは何の関係もありません。問題は、同期の欠如、または AtomicInteger の正しい使用法の欠如です。ifブロックは

if (numberOfCreatedObj.getAndIncrement() < maxSize) {
    ...
}

また、インクリメントが A の位置にあるときに期待どおりに動作することがテストで示されていても、それは単なる偶然であることに注意してください。位置 A のインクリメントでも同じバグが発生する可能性があります。それが発生しないのは幸運でした。

于 2013-07-14T18:12:08.993 に答える