Java 多线程(ExecutorService)方法意外工作

Java 多线程(ExecutorService)方法意外工作,java,multithreading,parallel-processing,thread-safety,executorservice,Java,Multithreading,Parallel Processing,Thread Safety,Executorservice,我不明白哪里出了错。 我正在尝试制作一个多线程程序,它将在数组中找到具有相同符号的字符串。然后,这些字符串应该打印它们的索引 Example input: {"qwe","wqe","qwee","a","a"}; Output: a = 3, 4 eqw = 0, 1 但是当我试图运行程序时,它们的索引发生了一些变化。 我尝试通过以下方式同步我的方法:使用(此)进行同步,在方法级别进行同步,

我不明白哪里出了错。 我正在尝试制作一个多线程程序,它将在数组中找到具有相同符号的字符串。然后,这些字符串应该打印它们的索引

Example input: {"qwe","wqe","qwee","a","a"};
Output:
a = 3, 4
eqw = 0, 1 
但是当我试图运行程序时,它们的索引发生了一些变化。 我尝试通过以下方式同步我的方法:使用(此)进行同步,在方法级别进行同步, 使用为每个对象创建的锁进行同步。也许我错过了什么,有什么东西可以让它发挥作用

 public class Main {

  private volatile Map<String, Integer> countByString = new ConcurrentHashMap<>();
  private volatile Map<String, String> indexesByString = new ConcurrentHashMap<>();

  public static void main(String[] args) {
    String[] arr = {"qwe", "wqe", "qwee", "a", "a"};
    Main main = new Main();
    List<Callable<Void>> tasks = new ArrayList<>();
    AtomicInteger i = new AtomicInteger(0);
    Arrays.stream(arr).forEach(str -> {
      tasks.add(() -> {
        char[] charArr = str.toCharArray();
        Arrays.sort(charArr);
        String sortedStr = new String(charArr);
        main.calculateCount(sortedStr);
        main.calculateIndex(sortedStr, i.getAndIncrement());
        return null;
      });
    });

    try {
      ExecutorService executorService = Executors.newFixedThreadPool(4);
      executorService.invokeAll(tasks);
      executorService.shutdown();
    } catch (Exception e) {
      e.printStackTrace();
    }
    main.printResult();
  }

  void calculateCount(String str) {
    synchronized (countByString) {
      int count = countByString.get(str) == null ? 0 : countByString.get(str);
      countByString.put(str, ++count);
    }
  }

  void calculateIndex(String str, int index) {
    synchronized (indexesByString) {
      System.out.println(Thread.currentThread().getName() + " " + str + " " + index);
      String indexes = indexesByString.get(str);
      if (indexes == null) {
        indexes = "";
      }
      indexes += (index + ";");
      indexesByString.put(str, indexes);
    }
  }

  private void printResult() {
    for (Map.Entry<String, Integer> entry : countByString.entrySet()) {
      String str = entry.getKey();
//      Integer count = entry.getValue();
//      if (count >= 2) {
      String indexes = indexesByString.get(str);
      System.out.println(str + " = " + indexes);
//      }
    }
  }
}
公共类主{
private volatile Map countByString=new ConcurrentHashMap();
private volatile Map indexysting=new ConcurrentHashMap();
公共静态void main(字符串[]args){
字符串[]arr={“qwe”、“wqe”、“qwee”、“a”、“a”};
Main Main=新Main();
列表任务=新建ArrayList();
AtomicInteger i=新的AtomicInteger(0);
Arrays.stream(arr.forEach)(str->{
任务。添加(()->{
char[]charArr=str.toCharArray();
数组。排序(charArr);
字符串排序器=新字符串(charArr);
主计算计数(分拣机);
main.calculateIndex(sortedStr,i.getAndIncrement());
返回null;
});
});
试一试{
ExecutorService ExecutorService=Executors.newFixedThreadPool(4);
executorService.invokeAll(任务);
executorService.shutdown();
}捕获(例外e){
e、 printStackTrace();
}
main.printResult();
}
void calculateCount(字符串str){
已同步(countByString){
int count=countByString.get(str)==null?0:countByString.get(str);
countByString.put(str,++count);
}
}
void calculateIndex(字符串str,int索引){
已同步(IndexeBystring){
System.out.println(Thread.currentThread().getName()+“”+str+“”+index);
字符串索引=indexesByString.get(str);
如果(索引==null){
索引=”;
}
索引+=(索引+“;”);
INDEXESBSYSTRING.put(str,索引);
}
}
私有void printResult(){
对于(Map.Entry:countByString.entrySet()){
String str=entry.getKey();
//整数计数=entry.getValue();
//如果(计数>=2){
字符串索引=indexesByString.get(str);
System.out.println(str+“=”+索引);
//      }
}
}
}

代码的问题在于这一部分:

Arrays.stream(arr).forEach(str -> {
  tasks.add(() -> {
    char[] charArr = str.toCharArray();
    Arrays.sort(charArr);
    String sortedStr = new String(charArr);
    main.calculateCount(sortedStr);
    main.calculateIndex(sortedStr, i.getAndIncrement());
    return null;
  });
});
您正在将相同的工作(搜索整个字符串数组)分配给所有线程。因此,线程正在重写彼此的索引。您应该做的是在线程之间分配搜索字符串的工作。类似于:

tasks.add(() -> {
for(int threadIndex = i.getAndIncrement(); threadIndex < arr.length; threadIndex = i.getAndIncrement()){
        char[] charArr = arr[threadIndex].toCharArray();
        Arrays.sort(charArr);
        String sortedStr = new String(charArr);
        main.calculateCount(sortedStr);
        main.calculateIndex(sortedStr, threadIndex);
}
return null;
});
在此上下文中,您既不需要
volatile
也不需要
ConcurrentHashMap
,但是,您可以将这些字段设置为final。由于在并行区域之前创建了对象
main
,因此可以删除
volatile
子句,而
ConcurrentHashMap
可以更改为
HashMap
,因为您已经同步了这些字段

在关注代码的正确性之后,应该尽量减少同步开销。例如,您可以尝试在每个线程之间复制
countByString
indexesByString
,并在并行工作完成后按顺序减少它们的值

当然,考虑到当前输入的大小,很难注意到性能优化之间有意义的差异

private volatile Map<String, Integer> countByString = new ConcurrentHashMap<>();
private volatile Map<String, String> indexesByString = new ConcurrentHashMap<>();