2012-07-09 2 views
3

경우에 따라 이상한 동작을 나타내는 ConcurrentHashMap가 있습니다.Java ConcurrentHashMap 타락한 값

내 응용 프로그램이 처음 시작되면 파일 시스템에서 디렉토리를 읽고 파일 이름을 키로 사용하여 각 파일의 내용을 ConcurrentHashMap에로드합니다. 일부 파일은 비어있을 수 있습니다.이 경우 값을 "비어 있음"으로 설정합니다.

일단 모든 파일이로드되면 작업자 스레드 풀은 외부 요청을 기다립니다. 요청이 들어 오면 ConcurrentHashMap에 키가 들어 있는지 확인하는 getData() 함수를 호출합니다. 키가 있으면 값을 가져 와서 값이 "비어 있는지"확인하십시오. value.contains ("empty") 인 경우, "file not found"를 반환합니다. 그렇지 않으면 파일 내용이 리턴됩니다. 키가 존재하지 않으면 파일 시스템에서 파일을로드하려고합니다.

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

반환 FALSE :

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 문을 두 부분으로 나누었습니다 : if (reply != null)if (!reply.contains("empty")). IF 문의 첫 번째 부분은 TRUE를 반환합니다. 두 번째 부분은 FALSE를 반환합니다. 그래서 문자열의 내용에 실제로 "empty"가 포함되어 있는지 확인하기 위해 변수 "reply"를 출력하기로 결정했습니다. 이는 사실이 아니 었습니다. 즉, 내용에 문자열 "empty"가 포함되지 않았습니다. 나는 그것을 밖으로 인쇄 할 때 또한, 내가 돌아 indexOf를 기다리고 있었다 "빈"문자열을 포함하지 않은 변수 응답 이후 라인

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

을 추가 -1. 그러나이 함수는 문자열 길이의 대략적인 값, 즉 if reply.length == 15100을 반환하고 reply.indexOf("empty")은 15099를 반환했습니다.

주간 단위로 약 2-3 회이 문제가 발생합니다. 이 프로세스는 매일 다시 시작되므로 ConcurrentHashMap은 정기적으로 다시 생성됩니다.

Java ConcurrentHashMap을 사용할 때 이러한 동작을 본 사람이 있습니까? 당신이 순서대로 여러 스레드에서 메서드를 호출하는 경우 당신을 보호하지 않습니다 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; 
} 
+3

간단히 말해서 foo.indexOf ("empty")'는 foo.length() - 1'을 비어 있지 않은 문자열로 반환 할 것이라고 생각하지 않습니다. 그것은'String.indexOf'가 매우 망가져 있음을 암시합니다. 'ConcurrentHashMap' 또는'String' 중 하나가 망가 졌다고 생각하지 않습니다 - 코드가 어딘가에서 깨졌습니다. –

+0

'getDataFromFileSystem (name);'의 코드를 보여줄 수 있습니까? – assylias

+1

은 _actual_ getData() 메소드이거나 여기에 게시하기 위해 재 작업 했습니까? – jtahlborn

답변

3

당신의 문제는 당신의 작업 중 일부는 원자 적이어야하고 그렇지 않다는 것입니다.

if (map.containsKey(name)) // (1) 
  • 결과가 거짓이고, 스레드 1로 진행 :

    • 스레드 1은 getData 방법이 라인을 읽어

      번 가능한 스레드 인터리빙 시나리오는 다음과 같다

      reply = getDataFromFileSystem(name); // (2) 
      
    • 스레드 2 다시 (2)에 간다, 그래서 이름이지도에없는 : 스레드 1 (4)(5) 사이에있는 동안

      if (map.containsKey(name)) { // (3) 
          map.remove(name); // (4) 
      } 
      map.put(name, contents); // (5) 
      
    • 다른 스레드 (스레드 2) (1)에 도착 상상 : 24,553,, 다음과 같은 코드가 있습니다

    는 이제 당신이 관찰하는 특정 문제를 설명하지 않습니다하지만 당신은 이상한 일을하고 일 할 수있는, 많은 스레드 동기화없이 코드 섹션에서 동시에 실행할 수 있도록한다는 사실을 보여줍니다.

    reply = map.get(name) 번을 두 번 이상 호출하지 않는 한 설명하는 시나리오에 대한 설명을 찾을 수 없습니다.이 경우 두 번의 호출이 동일한 결과를 반환하지 않을 가능성이 매우 높습니다 .

  • +0

    도움말 assylias 주셔서 감사. 스레드 기능을 보장하고 동작을 모니터링하기 위해 함수를 업데이트합니다. –

    0

    . 나중에 containsKeyget을 호출하고 다른 스레드가 remove을 호출하면 null 결과가 표시됩니다. containsKey/get 대신에 get만을 호출하고 null을 확인하십시오. 두 가지 방법 모두 거의 동일한 비용을 가지기 때문에 성능면에서 더 좋습니다.

    두 번째로 이상한 indexOf 호출 결과는 프로그래밍 오류 또는 메모리 손상을 가리 킵니다. 애플리케이션에 네이티브 코드가 포함되어 있습니까? getDataFromFileSystem에서 뭐하고 있니? 여러 스레드에서 FileChannel 개체를 사용할 때 메모리 손상을 관찰했습니다.

    +0

    내 응용 프로그램에 기본 전화가 없습니다. ** getDataFromFileSystem **은 이제 원래 게시물에 정의됩니다. 이 함수는 단순히 BufferedFileReader를 사용하여 파일을 읽습니다. –

    +0

    또한지도에 액세스하는 방식을 변경했습니다. containsKey를 호출하고 get을 호출하는 대신 get을 호출하고 null을 확인합니다. 팁 주셔서 감사 : –

    2

    먼저 ConcurrentHashMap에 버그가 있다고 생각하지 마십시오. JDK 단점은 매우 드물며 아이디어를 재미있게 만들어도 코드를 올바르게 디버깅 할 수 없게됩니다.

    나는 다음과 같은 버그가 있다고 생각합니다. contains("empty")을 사용하고 있기 때문에 파일의 행에 "empty"이라는 단어가 있으면 어떻게됩니까? 그것들이 엉망이 될 것 아닌가요?

    contains("empty") 대신에 ==을 사용합니다. "비어있는 것"을 private static final String으로 만들고 평등을 사용할 수 있습니다.

    private final static String EMPTY_STRING_REFERENCE = "empty"; 
    ... 
    if (reply != null && reply != EMPTY_STRING_REFERENCE) { 
        return reply; 
    } 
    ... 
    String contents = EMPTY_STRING_REFERENCE; 
    ... 
    // really this should be if (contents.isEmpty()) 
    if (contents.equals("")) { 
        contents = EMPTY_STRING_REFERENCE; 
    } 
    

    는 유일한 시간은 당신이 문자열을 비교하는 ==를 사용한다 , BTW입니다. 이 경우 파일의 행에 실제로 마법 문자열이 포함될 수 있으므로 이 아닌으로 내용을 테스트하여 테스트하고 싶습니다. 여기

    는 다른 점이다 : 당신이 당신의 프로그램에서 여러 위치에 동일한 String를 사용 할 때마다 일반적으로

    • , 그것은 static final 필드로 끌어 올려해야합니다. 어쨌든 Java가이 작업을 수행하지만 코드를 훨씬 더 명확하게 만듭니다.
    • @assylias는 ConcurrentHashMap을 2 번 호출 할 때 경합 조건에 관한 부분입니다. 예를 들어 다음을 수행하는 대신 :

      if (map.containsKey(name)) { 
          reply = map.get(name); 
      } else { 
      

      하나만 수행하면됩니다.코드에서

      reply = map.get(name); 
      if (reply == null) { 
      
    • 이 작업을 수행 : 다음과 같이 다시 작성해야

      if (map.containsKey(name)) { 
          map.remove(name); 
      } 
      map.put(name, contents); 
      

      . 언급 된 @assylias와 같은 경쟁 조건을 도입하는 put 전에 제거 할 필요는 없습니다.

      map.put(name, contents); 
      
    • 당신은 말했다 : reply.length == 15100은 다음 reply.indexOf ("빈")는 15099.

      를 반환했다

      경우는 불가능 같은 reply 문자열. 다른 스레드를보고 있거나 출력을 잘못 해석 한 것으로 의심됩니다. 다시 말하지만, java.lang.String에 버그가 있다고 생각하도록 속지 마십시오.

    +1

    EMPTY_STRING에 ==를 사용하지 않는 것이 좋습니다. 코드 분석 도구는 오류가있어 다음 유지 관리 개발자가 오류를 "수정"할 수 있다고보고합니다. OP 구현물을 살펴보면 아무 것도 읽지 않은 경우 빈 문자열 만 반환하지 않는 명백한 이유는 없습니다. – Arne

    +0

    코드 분석 도구가 그 사실을보고 할 지 확신하지 못합니다. 그러나 다음 개발자 포인트는 좋은 것입니다. _REFERENCE로 이름을 변경했습니다. – Gray

    +0

    참조 ID가 더 좋긴하지만 빈 문자열의 측면에서 좋은 아이디어입니다. – Gray