我有一组预先填充的字符串。我想迭代项目,并在迭代时,我需要“做工作”,这也可能从集合中删除项目。我想为每个项目的“do work”生成一个新线程。请注意,在“工作”期间,只有部分项目会从集合中删除。
现在我有以下问题,
我可以通过简单地使用Collections.synchronizedSet(new HashSet())来实现这一点; ?我猜这会抛出ConcurrentModificationException,因为我在迭代时从列表中删除项目。如何在没有一致性问题的情况下有效地实现上述行为?
谢谢!
答案 0 :(得分:4)
我会使用ExecutorService
ExecutorService es = Executors.newFixedThreadPool(n);
List<Future<String>> toRemove = new ARraysList<>();
for(String s: set)
toRemove.add(es.submit(new Task(s)));
for(Future<String> future : toRemove()) {
String s = future.get();
if (s != null)
set.remove(s);
}
这避免了需要以多线程方式访问集合。
答案 1 :(得分:1)
使用主生产者线程,该线程将从集合中删除元素,并将它们提供给消费者线程。消费者线程无需“亲自”删除项目。
答案 2 :(得分:1)
是的,SynchronisedSet仍然会抛出ConcurrentModificationExceptions。
试试这个:
Set s = Collections.newSetFromMap(new ConcurrentHashMap())
当多个线程正在访问和修改它时,ConcurrentHashMap不应抛出ConcurrentModificationException。
答案 3 :(得分:0)
该方法取决于集合中数据与成功完成操作之间的关系。
如果您不关心线程执行的实际结果,您可以在调度任务时查看集合并删除每个项目(您已经有一些示例)
如果从集合中删除应该是执行成功的事务,您可以使用Future
来收集有关任务执行成功的信息。这样,只有成功执行的项目才会从原始集合中删除。无需同时访问Set结构,因为您可以使用Future
和ExecutorService
将执行与检查分开。例如:
// This task will execute the job and,
// if successful, return the string used as context
class Task implements Callable<String> {
final String target;
Task(String s) {
this.target = s;
}
@Override
public String call() throws Exception {
// do your stuff
// throw an exception if failed
return target;
}
}
这就是它的用法:
ExecutorService executor;
Set<Callable<String>> myTasks = new HashSet<Callable<String>>();
for(String s: set) {
myTasks.add(new Task(s));
}
List<Future<String>> results = executor.invoqueAll(myTasks);
for (Future<String> result:results) {
try {
set.remove(result.get());
} catch (ExecutionException ee) {
// the task failed during execution - handle as required
} catch (CancellationException ce) {
// the task was cancelled - handle as required
}
}