如何同时添加和删除列表?

时间:2014-09-27 08:57:46

标签: java list guava monitor guard

我在一个项目中偶然发现了以下方法,对我来说这看起来并不安全:

    public class EntityProcessor
    {

        private static final Monitor monitor = new Monitor();
        private static final List<String> entitiesInProcess = newArrayList();


        public processEntities(Entities entities)
        {
            for (final Entities entity : entities)
            {
                final String id = getId();

                try
                {
                    monitor.enterWhen(new Guard(monitor)
                    {
                        @Override
                        public boolean isSatisfied()
                        {
                            return !entitiesInProcess.contains(id);
                        }
                    });
                    entitiesInProcess.add(id);

                }
                catch (final InterruptedException e)
                {
                    e.printStackTrace();
                }

                finally
                {
                    monitor.leave();
                }

                try
                {
                    processEntity(entity)
                }
                catch (final Exception e)
                {
                    e.printStackTrace();
                }
                finally
                {
                    entitiesInProcess.remove(id);
                }
            }
        }
    }

对我来说,似乎这个arraylist可能会被破坏,因为删除没有被保护。 但是,我不知道如何正确实施它。 期望的状态: 使用arrayList作为控件对象来确定具有给定id的实体是否已经被处理,并且想要处理具有相同id的实体的线程因此应该等待它不再被处理,然后开始处理它(再次)。

情形:

  • 给出了身份证明(例如,我们无法创建自己的身份)。
  • processEntity(entity)可以同时为具有不同id的实体调用。

问题:

  • 这种方法是完全错误的(例如,无法实施 - 重新设计)?
  • 如果没有 - 任何建议如何达到理想状态?
  • 如果 - 关于如何重新设计的任何想法?

示例代码表示赞赏: - )。

更新

所以,我尝试了set方法,这给了我一个情况,如果我尝试处理具有相同ID的多个权限(如100个具有10个线程的实体),一些线程将无休止地挂起等待条件。我认为代码中的某个地方还有另一个错误,可能会发生这种情况,也许每次都会产生新的锁定问题。所以在上面的例子中,91个实体将被处理,而线程看起来像这样(每个线程的id和条件都不同:

    pool-2-thread-4" prio=6 tid=0x00000000285d0800 nid=0x278c waiting on condition [0x000000002935e000]
       java.lang.Thread.State: WAITING (parking)
        at sun.misc.Unsafe.park(Native Method)
        - parking to wait for  <0x000000057eee67c0> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
        at java.util.concurrent.locks.LockSupport.park(LockSupport.java:186)
        at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2043)
        at com.google.common.util.concurrent.Monitor.waitInterruptibly(Monitor.java:831)
        at com.google.common.util.concurrent.Monitor.enterWhen(Monitor.java:366)

我还尝试了Collections.synchronizedList方法,另外将issisfisfied变为:

                public boolean isSatisfied()
                {
                    final boolean contains = entitiesInProcess.contains(entityId);
                    if (!contains)
                    {
                        entitiessInProcess.add(entityId);
                    }
                    return !contains;
                }

这是为了不会在两个线程同时尝试添加实体的情况下结束,例如第一个线程检查包含,第二个线程添加一个实体,第一个线程也添加相同的实体。

注意:这里的实体不是java的Entity类,也许最好将它称为Item。

3 个答案:

答案 0 :(得分:0)

我认为你需要的只是:

private static final List<String> entitiesInProcess = 
    Collections.synchronizedList( newArrayList() );

由于可以同时调用processEntities,因此必须同步单个资源entitiesInProcess。

然而,这(虽然紧跟原始代码)导致过度工程。你所拥有的是一个关键部分processEntity(entity) w.r.t.类Entity的对象。因此,您只需要

for (final Entity entity: entities){
    synchronized( entity ){
        processEntity(entity);
    }
}

无名单,无需监视器。使用针对InterruptedException的try / catch块来围绕它可能是个好主意,这取决于你是否可以在循环内处理这个案例,所以我把它留了出来。

第二次修改 不同实体对象可能具有相同id属性和锁定的(奇怪)情况应基于这些可以使用

处理
 for (final Entity entity: entities){
    synchronized( entity.getId().intern() ){
        processEntity(entity);
    }
}

答案 1 :(得分:0)

烧掉它并深埋。

它访问的方式是Set的调用。并发模式只是哭泣。

private static final Set<String> entitiesInProcess = Sets.newConcurrentHashSet();

public void processEntities(Entities entities) {
    for (final Entity entity : entities) {
        final String id = entity.getId();
        final boolean isInProcess = !entitiesInProcess.add(id);
        if (isInProcess) continue;
        try {
            processEntity(entity);
        } finally {
            entitiesInProcess.remove(id);
        }
    }
}

这允许最大的并发性。 Set基于ConcurrentHashMap,它是一组同步地图的集合,其中细分由密钥的hashCode确定。所以它保证add是原子的,只有一个线程可以回来。在这里,它被用作一种锁定图。

如果processEntity速度很慢,那么synchronizedSet同样也会很好。否则,这个解决方案会更好。

您的问题标有Guava,因此您可以使用Sets。但您可以轻松使用Map<String, Whatever>

简化

我想,你需要的只是一个ConcurrentQueue。在遇到要处理的新实体时,将其放入。当线程想要帮助处理时,让它poll队列(可能在循环中)并处理获得的entity(除非{返回{1}}意味着无事可做)。 它不会变得更简单。

答案 2 :(得分:0)

确定。所以感谢所有的输入和答案。最终,它引导我找到了一个有效的解决方案。

注意:最好的解决方案可能是将其刻录并深埋并使用内部队列和ExecutorCompletionService重新实现,在让工作人员导入项目之前,在单个调度程序线程中执行整个重复检查。

手头代码的解决方案是不要在监视器中退出isSatisfied()。 因此解决这一切的是在该方法中进行循环以检查条件。 我被困在版本11的番石榴上(不幸的是我们正在构建我们的东西的产品捆绑在一起),所以我可以看到这是该版本中的一个错误,因为javadoc声明它应该与if子句一起使用。不是最优的(考虑到重复实体的争用),但现在已经足够好了。