2015-05-07 60 views
3

該類的方法getFirst()getSecond()被同時調用。它是一個網絡應用程序的一部分。Java併發性,流行爲

內部映射也填充,沒有併發。

public class MyClass { 
    private Map<String, List<List<String>>> first; 

    private Map<String, List<List<String>>> second; 

    public MyClass() { 
     first = new ConcurrentHashMap<>(); 
     second = new ConcurrentHashMap<>(); 
    } 

    public Set<String> getFirst(String key, String token, int a, int b) { 
     return get(first, key, token, a, b); 
    } 

    public Set<String> getSecond(String key, String token, int a, int b) { 
     return get(second, key, token, a, b); 
    } 

    private Set<String> get(final Map<String, List<List<String>>> map, final String key, final String token, 
     final int a, final int b) { 
     Set<String> result = new TreeSet<>(); 
     map.get(key).stream().filter(i -> i.size() <= b && i.size() >= a).forEach(
      s -> result 
       .addAll(s.stream().filter(p -> StringUtils.containsIgnoreCase(p, token)).collect(Collectors.toList()))); 
     return result; 
    } 
} 

我用類似ab -n 10000 -c 100(Apache的實用工具)測試了它。我登錄它。我所有的時間都一樣。但是,如果我將map.get(key).stream()更改爲map.get(key).parallelStream(),並執行相同的步驟,則有時會得到不同的結果大小(始終較小)。

這是什麼?

+1

嘗試使用線程安全集合來收集結果。 – OldCurmudgeon

+0

Set的線程安全版本? – SDmitry

+0

@SDmitry:爲什麼不呢? 'Collections.newSetFromMap(new ConcurrentSkipListMap ())' –

回答

5

您在並行流的forEach中使用TreeSet.addAll()。對於不同的元素,forEach正文可以在不同線程中同時執行多次,並且TreeSet不是線程安全的。要快速解決問題,您可以同步修改result或使用forEachOrdered。然而,它會更清潔,更高效地運行到flatMap您的流,並立即收集它,而無需forEach。試試這個版本:

return map.get(key).stream() 
     .filter(i -> i.size() <= b && i.size() >= a) 
     .flatMap(List::stream).filter(p -> StringUtils.containsIgnoreCase(p, token)) 
     .collect(Collectors.toCollection(TreeSet::new));