如何解决如果多个线程从不修改同一项目,可以从多个线程修改ArrayList中的项目吗?
一些(简化的)上下文。
假设我有一个ArrayList<ContentStub>
,其中ContentStub
是:
public class ContentStub {
ContentType contentType;
Object content;
}
并且我有多个类的实现,这些类为每个ContentType
“填充”存根,例如
public class TypeAStubInflater {
public void inflate(List<ContentStub> contentStubs) {
contentStubs.forEach(stub ->
{
if(stub.contentType == ContentType.TYPE_A) {
stub.content = someService.getContent();
}
});
}
}
想法是,TypeAStubInflater
仅修改在一个线程中运行的项目ContentType.TYPE_A
,而TypeBStubInflater
仅修改项目ContentType.TYPE_B
等,但每个实例的inflate()
方法正在并行修改同一contentStubs
列表中的项目。
但是:
- 没有线程会更改
ArrayList
的大小
- 没有线程尝试修改另一个线程正在修改的值
- 没有线程尝试读取另一个线程写入的值
鉴于所有这些,似乎没有必要采取其他措施来确保线程安全。从(非常)快速了解ArrayList
的实现来看,似乎没有ConcurrentModificationException
的风险-但是,这并不意味着其他地方也不会出错。我是否想念某些东西,或者这样做安全?
解决方法
通常,这将起作用,因为您没有修改List
本身的状态,如果在循环时有任何迭代器处于活动状态,则将抛出ConcurrentModificationException
,而是进行修改只是列表中的一个对象,这可以从列表的POV中获得。
我建议将您分成Map<ContentType,List<ContentStub>>
,然后使用这些特定列表启动线程。
您可以使用以下方法将列表转换为地图:
Map<ContentType,ContentStub> typeToStubMap = stubs.stream().collect(Collectors.toMap(stub -> stub.contentType,Function.identity()));
如果您的列表不是那么大(.foreach
,我们假设线程 A 写入TYPE_A
内容,线程 B 写入TYPE_B
内容。列表contentStubs
仅用于获取ContentStub
的实例:仅读访问。因此,从 A , B 和contentStubs
的角度来看,这没有问题。但是,由线程 A 和 B 完成的更新可能永远不会被其他线程看到,例如另一个线程 C 可能会得出结论:stub.content == null
对于列表中的所有元素。
其原因是Java Memory Model。如果不使用锁,同步,易失性和原子变量之类的构造,则内存模型无法保证一个线程对对象的修改是否以及何时对另一线程可见。为了使这更实用,让我们举个例子。
想象一下,线程 A 执行以下代码:
stub.content = someService.getContent(); // happens to be element[17]
列表元素17是对全局堆上的ContentStub
对象的引用。允许VM制作该对象的私有线程副本。线程 A 中对引用的所有后续访问都使用 copy 。 VM可以自由决定何时以及是否更新全局堆上的原始对象。
现在想象一下执行以下代码的线程 C :
ContentStub stub = contentStubs.get(17);
VM可能会对线程 C 中的私有副本执行相同的操作。
如果线程 C 在线程 A 更新对象之前已经访问了该对象,则线程 C 可能会使用–未更新–复制并忽略长期以来的全球原始 。但是,即使线程 C 在线程 A 更新对象之后第一次访问对象,也无法保证私有副本中的更改 A 线程已终止在全局堆中。
简而言之:在没有锁或同步的情况下,线程 C 几乎肯定只能读取每个null
中的stub.content
值。
此内存模型的原因是性能。在现代硬件上,所有CPU /内核之间的性能和一致性之间需要进行权衡。如果现代语言的内存模型要求一致性,那么很难在所有硬件上保证,这可能会对性能造成太大影响。因此,现代语言具有较低的一致性,并在需要时为开发人员提供了明确的结构来强制实施。结合编译器和处理器的指令重新排序,这使得关于程序代码的老式线性推理变得很有趣。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。