在多线程应用程序中,我们有一个在单例中处理列表的方法。要获取列表的快照,请执行以下操作:
public List swapList() {
if (syncLinkedList.isEmpty()) {
return null;
}
List currentList = normalLinkList;
normalLinkList = new LinkedList();//java.util.LinkedList.LinkedList()
syncLinkedList = Collections.synchronizedList(normalLinkList);
return currentList;
}一个线程在单例之上进行处理。
另一个对象在许多具有套接字的线程上接收请求,在许多线程上向syncLinkedList添加新的请求,如下所示:
syncLinkedList.add(obj);这是一种安全的交换单例的方式吗?
7月11日更新:项目是否会出现在两个列表之一- currentList或新的normalLinkList?只要项目在其中之一,我们就不会有问题。
发布于 2017-07-11 15:12:09
这不安全。
当您考虑线程安全时,请始终考虑不同线程上的两个操作,以及它们之间是否存在正式的happens-before relationship。这对于线程安全来说并不总是足够的(取决于您需要完成什么),但它几乎总是必需的。在这种情况下,我们有以下操作:
线程A实例化一个新的列表并将其分配给normalLinkList
时,
这是相当高层次的描述,但它实际上比我们需要的更详细一些。让我们把它归结为真正的本质:
线程A实例化一个新的列表,并将其分配给normalLinkList
对其进行访问
请注意,线程B没有对锁做任何事情。这意味着在它访问列表和线程A的任何修改之间没有发生-之前关系。锁的相关发生-之前关系是:
在监视器上发生解锁-在该监视器上的每个后续锁定之前。
..。但是因为B不会获得锁,所以之前不会发生这种情况。这就是所谓的数据竞赛,这就是为什么JavaDocs for Collections.synchronizedList明确表示:
通过返回的列表完成对支持列表的所有访问,这一点很重要。
那么,你应该怎么做呢?JavaDocs说要做的就是:只访问synchronizedList (决不是它的支持列表),如果你做了任何需要迭代它的事情,只有在持有它的锁的情况下才能这样做。
最简单的方法是根本不交换内容,而是将内容复制到新的列表中,然后清除同步的内容:
// Only do this once. And note, we don't even keep a reference
// to the underlying list. We only access it through syncLinkedList.
syncLinkedList = Collections.synchronizedList(new LinkedList());
...
List readSnapshot;
synchronized (syncLinkedList) {
readSnapshot = new LinkedList(syncLinkedList); // implicit iteration
syncLinkedList.clear(); // prepare for next batch (while holding lock)
}
return readSnapshot;在保持锁的同时调用clear()是很重要的。否则,如果有人在您释放锁之后但在您清除列表之前添加到列表中,则添加的内容将丢失(因为它不在快照中,但无论如何都会被清除)。
https://stackoverflow.com/questions/45017374
复制相似问题