31

別のスレッドで前を見ることが保証されていますか?私の期待はそうです、そしてJavaDocsを読むことはそう示しているようです、しかし私は99%が現実が違うと確信しています。私の本番サーバーでは、以下のことが起こっているようです。(私はロギングでそれを捕らえました。)ConcurrentHashMap.get() ConcurrentHashMap.put()

擬似コードの例:

static final ConcurrentHashMap map = new ConcurrentHashMap();
//sharedLock is key specific.  One map, many keys.  There is a 1:1 
//      relationship between key and Foo instance.
void doSomething(Semaphore sharedLock) {
    boolean haveLock = sharedLock.tryAcquire(3000, MILLISECONDS);

    if (haveLock) {
        log("Have lock: " + threadId);
        Foo foo = map.get("key");
        log("foo=" + foo);

        if (foo == null) {
            log("New foo time! " + threadId);
            foo = new Foo(); //foo is expensive to instance
            map.put("key", foo);

        } else
            log("Found foo:" + threadId);

        log("foo=" + foo);
        sharedLock.release();

    } else
        log("No lock acquired");
} 

起こっているように見えるのはこれです:

Thread 1                          Thread 2
 - request lock                    - request lock
 - have lock                       - blocked waiting for lock
 - get from map, nothing there
 - create new foo
 - place new foo in map
 - logs foo.toString()
 - release lock
 - exit method                     - have lock
                                   - get from map, NOTHING THERE!!! (Why not?)
                                   - create new foo
                                   - place new foo in map
                                   - logs foo.toString()
                                   - release lock
                                   - exit method

したがって、私の出力は次のようになります。

Have lock: 1    
foo=null
New foo time! 1
foo=foo@cafebabe420
Have lock: 2    
foo=null
New foo time! 2
foo=foo@boof00boo    

2番目のスレッドはすぐにプットを見ません!なんで?私の本番システムでは、より多くのスレッドがあり、スレッド1の直後に続く最初のスレッドである1つのスレッドだけに問題があります。

ConcurrentHashMapの同時実行レベルを1に縮小しようとしたこともありますが、それは問題ではありません。例えば:

static ConcurrentHashMap map = new ConcurrentHashMap(32, 1);

どこが間違っているのですか?私の期待?または、これを引き起こしている私のコード(上記ではなく実際のソフトウェア)にいくつかのバグがありますか?私はそれを繰り返し調べましたが、99%はロックを正しく処理していると確信しています。ConcurrentHashMapまたはJVM のバグを推測することすらできません。私を自分から救ってください。

関連する可能性のあるGoreyの詳細:

  • クアッドコア64ビットXeon(DL380 G5)
  • RHEL4(Linux mysvr 2.6.9-78.0.5.ELsmp #1 SMP... x86_64 GNU/Linux
  • Java 6(build 1.6.0_07-b0664-Bit Server VM (build 10.0-b23, mixed mode)
4

8 に答える 8

10

キャッシュ内でオブジェクトを見つけられなかったことに基づいて、作成に費用のかかるオブジェクトをキャッシュ内に作成するというこの問題は、既知の問題です。そして幸いなことに、これはすでに実装されていました。

GoogleCollecitonsのMapMakerを使用できます。オブジェクトを作成するコールバックを指定するだけで、クライアントコードがマップを調べ、マップが空の場合、コールバックが呼び出され、結果がマップに配置されます。

MapMakerjavadocsを参照してください...

 ConcurrentMap<Key, Graph> graphs = new MapMaker()
       .concurrencyLevel(32)
       .softKeys()
       .weakValues()
       .expiration(30, TimeUnit.MINUTES)
       .makeComputingMap(
           new Function<Key, Graph>() {
             public Graph apply(Key key) {
               return createExpensiveGraph(key);
             }
           });

ところで、元の例では、各アクセスをロックしているため、ConcurrentHashMapを使用する利点はありません。ロックされたセクション内で通常のHashMapを使用しないのはなぜですか?

于 2009-11-20T14:20:20.250 に答える
9

ここにいくつかの良い答えがありますが、私が知る限り、「ConcurrentHashMap.get()は別のスレッドで以前のConcurrentHashMap.put()を見ることが保証されていますか?」という質問に対する正規の答えを実際に提供した人は誰もいません。はいと言った人は情報源を提供していません。

だから:はい、それは保証されています。ソース(「メモリ整合性プロパティ」のセクションを参照):

オブジェクトを同時コレクションに配置する前のスレッド内のアクションが発生します-別のスレッド内のコレクションからその要素にアクセスまたは削除した後のアクションの前に発生します。

于 2011-05-24T01:21:30.950 に答える
4

スレッドが同時ハッシュマップに値を配置した場合、マップの値を取得する他のスレッドは、前のスレッドによって挿入された値を確認することが保証されます。

この問題は、JoshuaBlochによる「JavaConcurrencyinPractice」で明らかにされています。

テキストからの引用:-

スレッドセーフなライブラリコレクションは、javadocの主題が明確でない場合でも、次の安全な公開保証を提供します。

  • キーまたは値をに配置するHashtablesynchronizedMapConcurrent-Mapマップから取得する他のスレッドに安全に公開します(直接またはイテレーターを介して)。
于 2013-10-06T19:24:48.890 に答える
3

考慮すべきことの1つは、「get」呼び出しの両方の時点で、キーが等しく、同じハッシュコードを持っているかどうかです。彼らがただStringの場合はそうです、ここで問題は発生しません。ただし、キーの汎用タイプを指定しておらず、擬似コードで「重要でない」詳細を省略しているため、別のクラスをキーとして使用しているのではないかと思います。

いずれの場合も、スレッド1と2のgets / putsに使用されるキーのハッシュコードを追加でログに記録することをお勧めします。これらが異なる場合は、問題があります。また、それkey1.equals(key2)は真実でなければならないことに注意してください。これは明確にログに記録できるものではありませんが、キーが最終クラスでない場合は、完全修飾クラス名をログに記録する価値があります。次に、そのクラスのequals()メソッドを調べて、 2番目のキーは最初のキーと等しくないと見なすことができます。

そして、あなたのタイトルに答えるために-はい、ConcurrentHashMap.get()は以前のput()を見ることが保証されています。ここで、「previous」は、Javaメモリモデルで指定された2つの間に発生前の関係があることを意味します。(特にConcurrentHashMapの場合、これは基本的に期待どおりですが、両方のスレッドが異なるコアで「まったく同時に」実行された場合、どちらが最初に発生するかがわからない場合があることに注意してください。 、スレッド2)のput()の結果を確実に確認する必要があります。

于 2009-11-20T14:46:23.240 に答える
2

問題は「ConcurrentHashMap」にあるのではなく、コードのどこかにあるか、コードの理由にあると思います。上記のコードでエラーを見つけることができません(おそらく、悪い部分が表示されないだけですか?)。

しかし、あなたの質問に答えるために、「ConcurrentHashMap.get()は、別のスレッドによって以前のConcurrentHashMap.put()を見ることが保証されていますか?」私は小さなテストプログラムを一緒にハックしました。

要するに:いいえ、ConcurrentHashMapはOKです!

地図がひどく書かれている場合、次のプログラムは「悪いアクセス!」と印刷します。少なくとも時々。上で概説したメソッドへの100000回の呼び出しで100個のスレッドをスローします。しかし、それは「Allok!」と出力します。

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

public class Test {
    private final static ConcurrentHashMap<String, Test> map = new ConcurrentHashMap<String, Test>();
    private final static Semaphore lock = new Semaphore(1);
    private static int counter = 0;

    public static void main(String[] args) throws InterruptedException {
        ExecutorService pool = Executors.newFixedThreadPool(100);
        List<Callable<Boolean>> testCalls = new ArrayList<Callable<Boolean>>();
        for (int n = 0; n < 100000; n++)
            testCalls.add(new Callable<Boolean>() {
                @Override
                public Boolean call() throws Exception {
                    doSomething(lock);
                    return true;
                }
            });
        pool.invokeAll(testCalls);
        pool.shutdown();
        pool.awaitTermination(5, TimeUnit.SECONDS);
        System.out.println("All ok!");
    }

    static void doSomething(Semaphore lock) throws InterruptedException {
        boolean haveLock = lock.tryAcquire(3000, TimeUnit.MILLISECONDS);

        if (haveLock) {
            Test foo = map.get("key");
            if (foo == null) {
                foo = new Test();
                map.put("key", new Test());
                if (counter > 0)
                    System.err.println("Bad access!");
                counter++;
            }
            lock.release();
        } else {
            System.err.println("Fail to lock!");
        }
    }
}
于 2009-11-20T13:06:38.140 に答える
1

更新: putIfAbsent()ここでは論理的に正しいですが、キーが存在しない場合にのみFooを作成するという問題を回避することはできません。マップに配置されなくても、常にFooが作成されます。アプリでGoogleコレクションの依存関係を受け入れることができると仮定すると、DavidRousselの答えは適切です。


明らかな何かが欠けているかもしれませんが、なぜセマフォでマップを守っているのですか? ConcurrentHashMap(CHM)はスレッドセーフです(安全に公開されていると仮定すると、ここにあります)。アトミックな「まだそこにない場合は置く」を取得しようとしている場合は、chmを使用します。putIfAbsent()。マップの内容を変更できない、より複雑な不変条件が必要な場合は、通常のHashMapを使用して、通常どおりに同期する必要があります。

質問に直接答えるには:putが返されると、マップに入力した値は、それを探す次のスレッドで確認されることが保証されます。

ちなみに、セマフォのリリースを最終的に入れることについての他のコメントへの+1です。

if (sem.tryAcquire(3000, TimeUnit.MILLISECONDS)) {
    try {
        // do stuff while holding permit    
    } finally {
        sem.release();
    }
}
于 2009-11-20T14:12:01.763 に答える
0

Javaメモリモデルの興味深い兆候が見られますか?レジスタはどのような条件下でメインメモリにフラッシュされますか?2つのスレッドが同じオブジェクトで同期する場合、一貫したメモリビューが表示されることが保証されていると思います。

Semphoreが内部で何をしているのかわかりません。ほとんどの場合、同期を行う必要がありますが、それはわかっていますか?

するとどうなりますか

synchronize(dedicatedLockObject)

セマフォを取得する代わりに?

于 2009-11-20T12:52:20.213 に答える
0

なぜ同時ハッシュマップをロックするのですか?defによる。そのスレッドセーフ。問題がある場合は、ロックコードにあります。これが、Javaにスレッドセーフパッケージがある理由です。これをデバッグする最良の方法は、バリア同期を使用することです。

于 2011-05-23T22:21:48.777 に答える