在多线程(Java或.Net)程序中,我可以假设复制变量是原子的吗?
当我想知道这个问题时,我正担心我正在设计的应用程序中的竞争条件
假设我有一个大型数组或某种集合,由程序的一个组件管理,我们称之为组件监视器。它的工作是定期检查集合是否“脏”,即最近是否发生了更改,如果是,则将快照写入磁盘(这是为了在发生崩溃时检查应用程序),并再次将其标记为干净
在不同线程中运行的同一程序的其他组件调用监视器的方法向数组/集合中添加数据或修改数据。这些方法将集合标记为脏
现在,改变方法在其他组件的线程中运行,对吗?如果我没那么幸运,可能会在快照写入磁盘时调用它们,更改已经写入的数据,设置脏标志,然后监视器的线程将其取消设置,而没有保存更改(更改时它已经超过元素)。所以我有一个肮脏的收藏被标记为干净
有一段时间,我认为我可以通过制作一个集合的临时副本,将其标记为干净,然后对副本进行序列化来解决这个问题。但是复制会是原子的吗?也就是说,我能确保在复制时集合不会改变吗
同时,我想我已经找到了更好的解决方案,比如
- 在开始写入磁盘之前设置锁定标志,并使数据更改方法等待,直到该标志取消设置
- 让数据更改方法写入“更改队列”,而不是直接写入集合,并让执行磁盘写入过程的线程写入该队列
我认为锁定标志可能是最好的方式但我还是很好奇:复制变量是原子的吗强>
跟进:也许这应该是一个问题本身,但实际上基本相同。根据下面的答案,我的“锁定标志”方法也可能不起作用,对吗?因为数据更改方法可能会在将锁定标志设置为“锁定”值时检查锁定标志,并确定其未锁定。所以我需要一个特殊的结构,比如互斥体,如果我真的想做对的话,对吗
我对他对我的后续行动表示赞赏。我真的应该问这两个问题,这样我就可以接受两个答案了。请你也给他投票
# 1 楼答案
设置32位(至少在.NET中)是原子级的,但它对您没有好处。你必须阅读它才能知道它是否被锁定,所以你可能会阅读它,在阅读之后,其他人会在你设置它之前阅读它,所以两个线程最后会出现在“受保护”代码中。这正是实际同步对象(如.NET Monitor类)的用途。您还可以使用Interlocked来检查并增加锁变量
另见:Is accessing a variable in C# an atomic operation?
# 2 楼答案
看看java.util.concurrent.atomic——里面可能有一些你可以使用的好东西
# 3 楼答案
不。例如,Java中的长变量在32位机器上不是原子的
此外,还有一个“线程缓存”问题——除非变量是可变的或在同步块内,否则另一个线程可能看不到变量值的更改。这适用于所有类型的变量,而不仅仅是长变量
阅读这里:http://gee.cs.oswego.edu/dl/cpj/jmm.html,尤其是“原子性”和“可见性”段落
# 4 楼答案
在JVM上工作时,您需要关注对其他线程的更改的可见性。一般来说,应该在
synchronized
块中进行赋值,或者变量应该是volatile
,或者应该使用java.util.concurrent.atomic
包中的变量包装器然而,在您的情况下,听起来好像只有一个线程清除了“脏”标记,即保存数据的线程。如果是这种情况,请在写入数据之前清除标志。如果其他线程在您写入数据时设置了它,它将一直保持设置,直到下一次计划写入。我将使用
AtomicBoolean
,在检查标志和清除标志之间赋予持久性线程原子性,如下所示:# 5 楼答案
这在很大程度上取决于您运行的硬件和JVM
在一些硬件和JVM上,一些拷贝是原子的 但更安全的是,假设情况并非如此,即使是简单的整数到整数分配也可以转换为x856硬件上的四条机器指令
字符串和数组副本可能涉及数千条指令的序列 两个线程可以同时更新
# 6 楼答案
不,它不是原子弹See this question为什么以及该怎么办