ConcurrentModificationException在anyMatch调用期间流式传输HashSet的情况

时间:2019-05-22 19:41:17

标签: java multithreading

    private Set<Job> myJobs = new HashSet<>();

    public shouldDoWork(Work work)
        return !myJobs.stream()
                  .map(job -> job.doWork(work))
                  .anyMatch(shouldDoWork -> !shouldDoWork);


   public addJob(Job job) {
       myJobs.add(job);
   }
   // also for remove

并且有许多线程随时调用此函数中的任何一个

java.util.ConcurrentModificationException         在java.util.HashMap $ KeySpliterator.tryAdvance(HashMap.java:1579)〜[?:1.8.0_202]         在java.util.stream.ReferencePipeline.forEachWithCancel(ReferencePipeline.java:126)〜[?:1.8.0_202]         在java.util.stream.AbstractPipeline.copyIntoWithCancel(AbstractPipeline.java:498)〜[?:1.8.0_202]         在java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:485)〜[?:1.8.0_202]         在java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471)〜[?:1.8.0_202]         在java.util.stream.MatchOps $ MatchOp.evaluateSequential(MatchOps.java:230)〜[?:1.8.0_202]         在java.util.stream.MatchOps $ MatchOp.evaluateSequential(MatchOps.java:196)〜[?:1.8.0_202]         在java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)〜[?:1.8.0_202]         在java.util.stream.ReferencePipeline.anyMatch(ReferencePipeline.java:449)〜[?:1.8.0_202]         

知道为什么会引发ConcurrentModificationException吗?

是因为集合(myJobs)发生了变化,还是因为shouldDoWork的值被其他变化了?

1 个答案:

答案 0 :(得分:1)

  

知道为什么会引发ConcurrentModificationException吗?

是的

  

是因为集合(myJobs)正在更改,还是因为   应该由其他方式更改工作吗?

前者。您列出了两种方法,shouldDoWork()addJob()。第一个对集合myJobs执行流操作,第二个向该集合添加元素。如果确实如此

  

有很多线程随时调用此函数

那么很可能某个线程将在某个时刻调用addJob(),从而在另一个人从该集合构造流的时间之间,对结构myJobs进行结构性修改。当其他人消耗完该流时。修改集合中的对象不会导致ConcurrentModificationException(尽管这样做通常会使集合无效,如果这样做会更改元素的哈希码)。它正在修改可以做的集合本身。

实际上,您很幸运获得了CME,因为在您所描述的同步修改不正确的情况下,这不能保证。相反,您可能会得到垃圾结果。

对于两者,您至少都有两个合理的选择,可以实现正确的同步并避免CME:

  1. 同步对myJobs 的访问。例如,

    public boolean shouldDoWork(Work work) {
        synchronized(myJobs) {
            return !myJobs.stream()
                  .map(job -> job.doWork(work))
                  .anyMatch(shouldDoWork -> !shouldDoWork);
        }
    }
    
    public addJob(Job job) {
        synchronized (myJobs) {
            myJobs.add(job);
        }
    }
    

    如果采用这种方法,则必须类似地将所有访问同步到myJobs。或

  2. 使用不需要显式同步的另一种容器(例如ConcurrentHashMap

    private ConcurrentHashMap<Job, Job> myJobs = new ConcurrentHashMap<>();
    
    public boolean shouldDoWork(Work work) {
        return !myJobs.keySet().stream()
              .map(job -> job.doWork(work))
              .anyMatch(shouldDoWork -> !shouldDoWork);
    }
    
    public addJob(Job job) {
        myJobs.put(job, job);
    }
    

    在实施此类更改之前,您应该阅读提议的替代类(在这种情况下为ConcurrentHashMap)的文档,以确保您理解其中的含义。此替代方法可能比同步性能更好,但这是以较弱的行为保证为代价的。