3

時々奇妙な動作を示す ConcurrentHashMap があります。

アプリの初回起動時に、ファイル システムからディレクトリを読み取り、ファイル名をキーとして使用して各ファイルの内容を ConcurrentHashMap に読み込みます。一部のファイルは空である可能性があり、その場合は値を「空」に設定します。

すべてのファイルがロードされると、ワーカー スレッドのプールが外部要求を待ちます。リクエストが来ると、getData() 関数を呼び出して、ConcurrentHashMap にキーが含まれているかどうかを確認します。キーが存在する場合は、値を取得し、値が「空」かどうかを確認します。value.contains("empty") の場合、"ファイルが見つかりません" を返します。それ以外の場合は、ファイルの内容が返されます。キーが存在しない場合は、ファイル システムからファイルをロードしようとします。

private String getData(String name) {
    String reply = null;
    if (map.containsKey(name)) {
        reply = map.get(name);
    } else {
        reply = getDataFromFileSystem(name);
    }

    if (reply != null && !reply.contains("empty")) {
        return reply;
    }

    return "file not found";
}

場合によっては、ConcurrentHashMap は空でないファイル (つまりvalue.contains("empty") == false) の内容を返しますが、次の行:

if (reply != null && !reply.contains("empty")) 

FALSE を返します。IF ステートメントを と の 2 つの部分に分けましif (reply != null)if (!reply.contains("empty"))。IF ステートメントの最初の部分は TRUE を返します。2 番目の部分は FALSE を返します。そこで、文字列の内容に実際に「空」が含まれているかどうかを判断するために、変数「reply」を出力することにしました。これは事実ではありませんでした。つまり、内容に文字列「empty」が含まれていませんでした。さらに、行を追加しました

int indexOf = reply.indexOf("empty");

印刷したときに変数の応答に文字列「empty」が含まれていなかったので、indexOf-1 が返されることを期待していました。しかし、関数は文字列の長さ、つまり に近い値を返しif reply.length == 15100、その後reply.indexOf("empty")15099 を返していました。

この問題は毎週、週に約 2 ~ 3 回発生します。このプロセスは毎日再開されるため、ConcurrentHashMap は定期的に再生成されます。

Java の ConcurrentHashMap を使用しているときに、そのような動作を見た人はいますか?

編集

private String getDataFromFileSystem(String name) {
    String contents = "empty";
    try {
        File folder = new File(dir);

        File[] fileList = folder.listFiles();
        for (int i = 0; i < fileList.length; i++) {
            if (fileList[i].isFile() && fileList[i].getName().contains(name)) {
                String fileName = fileList[i].getAbsolutePath();

                FileReader fr = null;
                BufferedReader br = null;

                try {
                    fr = new FileReader(fileName);
                    br = new BufferedReader(fr);
                    String sCurrentLine;
                    while ((sCurrentLine = br.readLine()) != null) {
                        contents += sCurrentLine.trim();
                    }
                    if (contents.equals("")) {
                        contents = "empty";
                    }

                    return contents;
                } catch (Exception e) {
                    e.printStackTrace();

                    if (contents.equals("")) {
                        contents = "empty";
                    }
                    return contents;
                } finally {
                    if (fr != null) {
                        try {
                            fr.close();
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }

                    if (br != null) {
                        try {
                            br.close();
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }

                    if (map.containsKey(name)) {
                        map.remove(name);
                    }

                    map.put(name, contents);
                }
            }
        }
    } catch (Exception e) {
        e.printStackTrace();

        if (contents.equals("")) {
            contents = "empty";
        }
        return contents;
    }
    return contents;
}
4

3 に答える 3

4

あなたの問題は、操作の一部がアトミックであるべきであり、そうでないことだと思います。

たとえば、考えられるスレッド インターリーブのシナリオの 1 つを次に示します。

  • スレッド 1 は、getDataメソッドで次の行を読み取ります。

    if (map.containsKey(name)) // (1)
    
  • 結果は false で、スレッド 1 は

    reply = getDataFromFileSystem(name); // (2)
    
  • getDataFromFileSystemは、次のコードがあります。

    if (map.containsKey(name)) { // (3)
        map.remove(name);  // (4)
    }
    map.put(name, contents); // (5)
    
  • 別のスレッド (スレッド 2) が に到着し(1)、スレッド 1 が(4)(5): name の間にあると想像してください。そのため、スレッド 2 は(2)再び

これは、観察している特定の問題を説明するものではありませんが、同期せずにコードのセクションで多くのスレッドを同時に実行させると、奇妙なことが起こる可能性があるという事実を示しています。

現状では、テストで複数回呼び出しない限り、説明したシナリオの説明を見つけることができません。reply = map.get(name)その場合、2 つの呼び出しが同じ結果を返さない可能性が非常に高くなります。

于 2012-07-10T09:53:27.367 に答える
0

まず、ConcurrentHashMap複数のスレッドから順番にそのメソッドを呼び出すと、使用しても保護されません。containsKeyとその後を呼び出し、その間getに別のスレッドが呼び出すとremove、null の結果が得られます。containsKey/get の代わりに、必ず get のみを呼び出して null をチェックしてください。どちらの方法もコストがほぼ同じであるため、パフォーマンスに関しても優れています。

第 2 に、奇妙な indexOf 呼び出しの結果は、プログラミング エラーが原因であるか、メモリの破損を示しています。アプリケーションに含まれるネイティブ コードはありますか? で何をしているのgetDataFromFileSystemFileChannel複数のスレッドからオブジェクトを使用すると、メモリの破損が見られました。

于 2012-07-09T19:15:13.930 に答える