我正在尝试找到一种实现以下API的好方法:
void add(Object o);
void processAndClear();
该类将存储对象,并在调用processAndClear时遍历当前存储的对象,以某种方式处理它们,然后清除存储.该类应该是线程安全的.
最明显的方法是使用锁定,但是我想变得更“并行”.这是我将使用的方法:
class Store{
private AtomicReference<CopyOnWriteArrayList<Object>> store = new AtomicReference<>(new CopyOnWriteArrayList <>());
void add(Object o){
store.get().add(o);
}
void processAndClear(){
CopyOnWriteArrayList<Object> objects = store.get();
store.compareAndSet(objects, new CopyOnWriteArrayList<>());
for (Object object : objects) {
//do sth
}
}
}
这将允许尝试添加对象的线程几乎立即进行,而无需任何锁定/等待xlearing完成.这或多或少是正确的方法?
解决方法:
您上面的代码不是线程安全的.想象以下情况:
>在store.get()之后,线程A在add()处暂停
>线程B在processAndClear()中,替换列表,处理旧元素的所有元素,然后返回.
>线程A恢复,并将新项目添加到现在已过时的列表中,该列表将永远不会被处理.
这里可能最简单的解决方案是使用LinkedBlockingQueue,这也将大大简化任务:
class Store{
final LinkedBlockingQueue<Object> queue = new LinkedBlockingQueue<>();
void add(final Object o){
queue.put(o); // blocks until there is free space in the optionally bounded queue
}
void processAndClear(){
Object element;
while ((element = queue.poll()) != null) { // does not block on empty list but returns null instead
doSomething(element);
}
}
}
编辑:如何做到这一点与同步:
class Store{
final LinkedList<Object> queue = new LinkedList<>(); // has to be final for synchronized to work
void add(final Object o){
synchronized(queue) { // on the queue as this is the shared object in question
queue.add(o);
}
}
void processAndClear() {
final LinkedList<Object> elements = new LinkedList<>(); // temporary local list
synchronized(queue) { // here as well, as every access needs to be properly synchronized
elements.addAll(queue);
queue.clear();
}
for (Object e : elements) {
doSomething(e); // this is thread-safe as only this thread can access these now local elements
}
}
}
为什么这不是一个好主意
尽管这是线程安全的,但与并发版本相比要慢得多.假设您的系统中有100个线程经常调用add,而一个线程调用processAndClear.然后,将出现以下性能瓶颈:
>如果一个线程调用添加了其他99个线程调用,则同时搁置.
>在processAndClear的第一部分中,所有100个线程都被保留.
如果您假定这100个正在添加的线程没有其他事情要做,那么您可以轻松地证明,该应用程序的运行速度与单线程应用程序的运行速度相同,而没有同步成本.这意味着:100个线程的添加实际上要比1个线程慢.如果像第一个示例中那样使用并发列表,则情况并非如此.
但是,由于线程可以在旧元素上运行doSomething,而新元素可以添加,因此处理线程将获得较小的性能提升.但同样,并发示例可能会更快,因为您可能有多个线程同时进行处理.
也可以使用有效地同步,但是您将自动引入性能瓶颈,这可能导致应用程序以单线程运行的速度变慢,从而迫使您进行复杂的性能测试.此外,扩展功能总是存在引入线程问题的风险,因为锁定需要手动完成.与此相反,并发列表无需任何额外的代码即可解决所有这些问题,并且以后可以轻松更改或扩展代码.