11

Map値を持つを作成し、intそれらを複数のスレッドで増やしようとしています。2 つ以上のスレッドが同じキーを増やす可能性があります。

ConcurrentHashMap次のように書かれているため、ドキュメンテーションは私には非常に不明確でした。

Retrieval operations (including get) generally do not block, so may overlap with update operations (including put and remove)

次のコードを使用して正しく動作ConcurrentHashMapするかどうか疑問に思います:

myMap.put(X, myMap.get(X) + 1);

そうでない場合、どうすればそのようなことを管理できますか?

4

5 に答える 5

12

並行マップは、コードのスレッド セーフには役立ちません。あなたはまだ競合状態を得ることができます:

Thread-1: x = 1, get(x)
Thread-2: x = 1, get(x)
Thread-1: put(x + 1) => 2
Thread-2: put(x + 1) => 2

2 回の増分が発生しましたが、まだ +1 しか得られません。コンテンツではなくマップ自体を変更する場合にのみ、並行マップが必要です。マップがもはや変更されていない場合、最も単純なHashMapでさえ、同時読み取りに対してスレッドセーフです。

したがって、プリミティブ型のスレッドセーフ マップの代わりに、型のスレッドセーフ ラッパーが必要です。任意のタイプが必要な場合は、独自のロックされたコンテナーから何かを取得するかjava.util.concurrent.atomic、ロールします。

于 2012-08-27T12:09:15.573 に答える
3

1 つのアイデアは、ConcurrentMap とインクリメント メソッドを持つ AtomicInteger を組み合わせることです。

 AtomicInteger current = map.putIfAbsent(key, new AtomicInteger(1));
 int newValue = current == null ? 1 :current.incrementAndGet();

または(より効率的に、@Keppilに感謝します)余分なコードガードを使用して、不要なオブジェクトの作成を回避します:

 AtomicInteger current = map.get(key);
 if (current == null){
     current = map.putIfAbsent(key, new AtomicInteger(1));
 }
 int newValue = current == null ? 1 : current.incrementAndGet();
于 2012-08-27T11:48:19.287 に答える
3

ベストプラクティス。HashMap と AtomicInteger を使用できます。テストコード:

public class HashMapAtomicIntegerTest {
    public static final int KEY = 10;

    public static void main(String[] args) {
        HashMap<Integer, AtomicInteger> concurrentHashMap = new HashMap<Integer, AtomicInteger>();
        concurrentHashMap.put(HashMapAtomicIntegerTest.KEY, new AtomicInteger());
        List<HashMapAtomicCountThread> threadList = new ArrayList<HashMapAtomicCountThread>();
        for (int i = 0; i < 500; i++) {
            HashMapAtomicCountThread testThread = new HashMapAtomicCountThread(
                    concurrentHashMap);
            testThread.start();
            threadList.add(testThread);
        }
        int index = 0;
        while (true) {
            for (int i = index; i < 500; i++) {
                HashMapAtomicCountThread testThread = threadList.get(i);
                if (testThread.isAlive()) {
                    break;
                } else {
                    index++;
                }
            }
            if (index == 500) {
                break;
            }
        }
        System.out.println("The result value should be " + 5000000
                + ",actually is"
                + concurrentHashMap.get(HashMapAtomicIntegerTest.KEY));
    }
}

class HashMapAtomicCountThread extends Thread {
    HashMap<Integer, AtomicInteger> concurrentHashMap = null;

    public HashMapAtomicCountThread(
            HashMap<Integer, AtomicInteger> concurrentHashMap) {
        this.concurrentHashMap = concurrentHashMap;
    }

    @Override
    public void run() {
        for (int i = 0; i < 10000; i++) {
            concurrentHashMap.get(HashMapAtomicIntegerTest.KEY)
                    .getAndIncrement();
        }
    }
}

結果:

結果値は 5000000 のはずですが、実際には 5000000 です

または HashMap と同期しますが、前者よりもはるかに遅くなります

public class HashMapSynchronizeTest {

    public static final int KEY = 10;

    public static void main(String[] args) {

        HashMap<Integer, Integer> hashMap = new HashMap<Integer, Integer>();
        hashMap.put(KEY, 0);
        List<HashMapSynchronizeThread> threadList = new ArrayList<HashMapSynchronizeThread>();
        for (int i = 0; i < 500; i++) {
            HashMapSynchronizeThread testThread = new HashMapSynchronizeThread(
                    hashMap);
            testThread.start();
            threadList.add(testThread);
        }
        int index = 0;
        while (true) {
            for (int i = index; i < 500; i++) {
                HashMapSynchronizeThread testThread = threadList.get(i);
                if (testThread.isAlive()) {
                    break;
                } else {
                    index++;
                }
            }
            if (index == 500) {
                break;
            }
        }
        System.out.println("The result value should be " + 5000000
                + ",actually is" + hashMap.get(KEY));
    }
}

class HashMapSynchronizeThread extends Thread {
    HashMap<Integer, Integer> hashMap = null;

    public HashMapSynchronizeThread(
            HashMap<Integer, Integer> hashMap) {
        this.hashMap = hashMap;
    }

    @Override
    public void run() {
        for (int i = 0; i < 10000; i++) {
            synchronized (hashMap) {
                hashMap.put(HashMapSynchronizeTest.KEY,
                        hashMap
                                .get(HashMapSynchronizeTest.KEY) + 1);
            }
        }
    }
}

結果:

結果値は 5000000 のはずですが、実際には 5000000 です

ConcurrentHashMap を使用すると、間違った結果が得られます。

public class ConcurrentHashMapTest {

    public static final int KEY = 10;

    public static void main(String[] args) {
        ConcurrentHashMap<Integer, Integer> concurrentHashMap = new ConcurrentHashMap<Integer, Integer>();
        concurrentHashMap.put(KEY, 0);
        List<CountThread> threadList = new ArrayList<CountThread>();
        for (int i = 0; i < 500; i++) {
            CountThread testThread = new CountThread(concurrentHashMap);
            testThread.start();
            threadList.add(testThread);
        }
        int index = 0;
        while (true) {
            for (int i = index; i < 500; i++) {
                CountThread testThread = threadList.get(i);
                if (testThread.isAlive()) {
                    break;
                } else {
                    index++;
                }
            }
            if (index == 500) {
                break;
            }
        }
        System.out.println("The result value should be " + 5000000
                + ",actually is" + concurrentHashMap.get(KEY));
    }
}

class CountThread extends Thread {
    ConcurrentHashMap<Integer, Integer> concurrentHashMap = null;

    public CountThread(ConcurrentHashMap<Integer, Integer> concurrentHashMap) {
        this.concurrentHashMap = concurrentHashMap;
    }

    @Override
    public void run() {
        for (int i = 0; i < 10000; i++) {
            concurrentHashMap.put(ConcurrentHashMapTest.KEY,
                    concurrentHashMap.get(ConcurrentHashMapTest.KEY) + 1);
        }
    }
}

結果:

結果値は 5000000 のはずですが、実際には 11759 です

于 2012-08-27T12:49:15.587 に答える
1

synchronized (myMap) {...}操作をブロックに入れるだけです。

于 2012-08-27T11:48:02.650 に答える
0

現在のコードはマップの値を同時に変更するため、これは機能しません。

複数のスレッドがputマップに値を入れることができる場合、 ConcurrentHashMap のような同時実行マップを、 のようなスレッドセーフでない値とともに使用する必要がありますIntegerConcurrentMap.replaceは、必要なことを行います (またはAtomicInteger、コードを簡単にするために使用します)。

スレッドがマップの値のみを変更する (キーを追加/変更しない) 場合は、AtomicIntegerなどのスレッドセーフな値を格納する標準マップを使用できます。次に、スレッドが次のように呼び出します。たとえば。map.get(key).incrementAndGet()

于 2012-08-27T11:48:31.003 に答える