ConcurrentHashMapのバグ

2565 ワード

java 1.8のconcurrentHashMapが最近発見され、computeIfAbsentを使用する場合、mapの変更に関連する場合、バグが発生します.サンプルコードは次のとおりです.
        System.out.println("start.");
        map.computeIfAbsent("t",
                (String t) -> map.computeIfAbsent("t", (String i) -> "i")); //halt   
        System.out.println("fin.");

このコードを実行すると、コードがコメントに止まり、結果が出ないことがわかります.最初は再帰的実現の問題と考えられていたが,通俗的には関数を構築する際に自己再帰に陥った.Aを構築したいが、Aの構造はAが構築を完了したいくつかの属性に依存している.この原因であるかどうかを検証するために,コードをいくつか調整し,再帰呼び出しを除去した.
        ConcurrentHashMap map = new ConcurrentHashMap<>();
        System.out.println("start.");
        map.computeIfAbsent("t",
                (String t) -> {
                    map.put("t", "t");
                    return "t";
                });
        System.out.println("fin.");

コードがどこに止まっているのか、「fin.」を出力できないことがわかります.
そしてデッドロックと疑い、concurrentHashMapが非再入ロックを使用している疑いがあります.しかしconrrentHashMapの実装を見ると,cas+synchronizedに基づいて実装されていることが分かったが,synchronized自体は再入可能であるため,ここではデッドロックの条件を満たさない.concurrentHashMapの注釈を続けてみると、次のような言葉があります.
/*
  must not attempt to update any other mappings of this map.
*/

この言葉はこの問題が既知であるべきであることを確定した.したがって、computeIfAbsentに再帰的またはmapを変更する操作は絶対に避けなければならない.
理由を明らかにするために、debug concurrentHashMapのソースコードを続け、computeIfAbsentでmapを修正しようとすると、コードは
        for (Node[] tab = table;;) {  //    
            Node f; int n, i, fh;
            if (tab == null || (n = tab.length) == 0)
                tab = initTable();
            else if ((f = tabAt(tab, i = (n - 1) & h)) == null) {
                Node r = new ReservationNode();
                synchronized (r) {
                    if (casTabAt(tab, i, null, r)) { //cas
....

で繰り返します.注意:正確とは限らないが、concurrentHashMapではcas操作が使用されているため、casネストが発生した場合、「デッドロック」が形成されることを個人的に理解している.例えば、1つの値はもともと1で、私はそれを2に修正したいと思っています.正常なcas操作は、修正した瞬間に、値がまだ1であるかどうかを比較します.この比較はcasが1層しかない場合には問題ありません.ただし、2層のcasがある場合、この値は元は1であり、第1層は1->2を、casがまだ有効でない場合は、第2層cas操作に進み、2->3を、最終的にコミットすると、第2層casは現在の値が2であるかどうかを比較するが、現在は1を指すため、修正は無効である.最終的に繰り返しサイクルに入り、デッドロックを形成する.
computeIfAbsentのコード注釈ではこのようなmapを修正する行為に強いヒントを与えたが,実際には,この行為は依然としてconcurrentHashMapの実装バグであると考えられる.
https://bugs.openjdk.java.net/browse/JDK-8172951幸いこの問題はjava 1.9でほぼ修復された.
This is fixed in JDK 9 with JDK-8071667 . When the test case is run in JDK 9-ea, it gives a ConcurrentModification Exception. java 9