我在一个项目中偶然发现了以下方法,对我来说这看起来并不安全:
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的实体的线程因此应该等待它不再被处理,然后开始处理它(再次)。
情形:
问题:
示例代码表示赞赏: - )。
所以,我尝试了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。
答案 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子句一起使用。不是最优的(考虑到重复实体的争用),但现在已经足够好了。