public class Curiousity {
public volatile int[] array;
public void nonAtomic() {
array = new int[1];
array[0] = 1;
}
public void probablyAtomic() {
array = new int[] { 1 };
}
}
thread #1: set x = 2
processor #1: save_cache(x, 2)
processor #1: save_memory(x, 2)
thread #2: set x = 1
processor #2: save_cache(x, 1)
processor #2: save_memory(x, 1)
thread #1: read x
processor #1: read_cache(x) = 2 // в то время как х уже был обновлен значением 1 в thread #2
它不使用所谓的。单一事实来源以控制 X 的值,因此这种异常是可能的。据我所知,直接读写内存(或内存和处理器的共享缓存)正是强制使用 volatile 修饰符的原因(我在这里可能是错的)。
client: journal.push {withdrawMoney {card=41111111, cvc=123}, reserveTicket {concert=123}, sendEmail {address=nobody@localhost}}
client: <журнал подтвердил получение и запись задания>
journal: process withdrawMoney
journal: markCompleted withdrawMoney
journal: process reserveTicket
journal: <умирает, не успев записать выполнение reserveTicket>
journal: <восстанавливается>
journal: process reserveTicket # сайд-эффект вызывается еще раз, но только в случае некорректной работы
journal: markCompleted reserveTicket
journal: process sendEmail
journal: markCompleted sendEmail
如何定义原子性?
操作的原子性通常由其不可分割性的符号表示:操作可以完全应用或根本不应用。一个很好的例子是将值写入数组:
使用方法
nonAtomic
时,有可能某个线程在未初始化array[0]
时访问并接收到意外值。array[0]
使用时probablyAtomic
(前提是首先填充数组然后才分配 -我现在不能保证在 java 中就是这种情况,但想象一下这个规则在示例的框架内有效)这不应该是:数组总是包含null,或初始化数组,但数组 [0] 不能包含除 1 以外的任何内容。此操作是不可分割的,不能像以前那样应用一半nonAtomic
- 要么完全要么根本不,其余代码可以安全地预期数组将为 null 或值,而无需诉诸额外的检查。此外,操作的原子性通常意味着其结果对其所引用的系统中的所有参与者(在本例中为线程)可见;这是合乎逻辑的,但在我看来,这并不是原子性的强制性标志。
它为什么如此重要?
原子性通常源于应用程序的业务需求:银行交易必须全部应用,音乐会门票必须立即按指定数量订购,等等。具体来说,在解析的上下文中(java 中的多线程),任务更原始,但是从相同的需求增长:例如,如果正在编写一个 Web 应用程序,那么解析 HTTP 请求的服务器必须有一个原子附加队列传入请求,否则存在传入请求丢失的风险,从而导致服务质量下降。原子操作提供保证(不可分割性),并且应该在需要这些保证时调用它们。
此外,原子操作是线性化的——粗略地说,它们的执行可以分解为一个线性历史,而简单的操作可以产生一个历史图,这在某些情况下是不可接受的。
为什么原始操作本身不是原子的?这对每个人来说也会更容易。
现代运行时环境非常复杂,并且可以对代码进行大量优化,但在大多数情况下,这些优化会违反保证。由于大多数代码实际上并不需要这些保证,因此将具有特定保证的操作分离到一个单独的类中会更容易,反之亦然。最常见的例子是表达式的重新排序——处理器和 JVM 有权以不同于代码中描述的顺序执行表达式,直到程序员使用具有特定保证的操作强制执行特定顺序。您还可以举一个从内存中读取值的示例(但是我不确定它在形式上是否正确):
它不使用所谓的。单一事实来源以控制 X 的值,因此这种异常是可能的。据我所知,直接读写内存(或内存和处理器的共享缓存)正是强制使用 volatile 修饰符的原因(我在这里可能是错的)。
当然,优化后的代码运行速度更快,但绝不应该为了代码性能而牺牲必要的保证。
这仅适用于与设置变量和其他处理器活动相关的操作吗?
不。任何操作都可以是原子的或非原子的,例如,经典关系数据库保证一个事务——可能包括对数据的兆字节更改——要么完全应用,要么不应用。处理器指令在这里无关紧要;一个操作可以是原子的,只要它本身是原子的,或者它的结果显示为另一个原子操作(例如,数据库事务的结果显示为对文件的写入)。
另外,据我了解,“指令在一个周期内没有时间-操作是非原子的”这一说法也是不正确的,因为有一些专门的指令,没有人费心去原子地设置任何值内存在受保护块的入口处,并在出口处将其删除。
任何操作都可以是原子的吗?
不。我非常缺乏正确表述的资格,但据我了解,任何涉及两个或多个外部影响(副作用)的操作都不能按定义是原子的。副作用主要是指与某些外部系统(无论是文件系统还是外部 API)交互,但即使是同步块内的两个变量设置表达式也不能被视为原子操作,直到其中一个可以抛出异常 - 而这个,鉴于 OutOfMemoryError 和其他可能的结果,可能根本不可能。
我的手术有两种或多种副作用。我还能做些什么吗?
是的,可以创建一个保证所有操作都将被应用的系统,但条件是任何副作用都可以无限次调用。您可以创建一个日志系统,自动记录计划的操作,定期检查日志,并执行尚未应用的操作。这可以表示如下:
这确保了算法的进展,但消除了时间范围内的所有义务(从形式上讲,无论如何并不是一切都井井有条)。如果操作是幂等的,那么这样的系统迟早会达到所需的状态,与预期的状态没有任何明显的差异(执行时间除外)。
你如何确定java中操作的原子性?
在这种情况下,事实的主要来源是 Java 内存模型,它定义了哪些假设和保证适用于 JVM 中的代码。然而Java内存模型理解起来相当复杂,涵盖的操作范围比原子操作的范围大得多,所以在本题的上下文中,知道volatile修饰符提供原子读写就足够了,并且 Atomic* 类允许您执行比较和交换操作以原子地更改值,而不必担心另一个值会介于读取和写入之间,并且在下面的评论中,在阅读时,他们可能添加了其他内容我忘了。
冒着招致性别歧视指责的风险,我无法抗拒并举一个原子操作的例子:怀孕是一个严格的原子操作,永远只有一个父亲(我们会把各种遗传技巧排除在外)括号)。
反之亦然,非原子操作的一个例子:唉,抚养孩子是一个非原子操作,不幸的是,孩子是孩子脆弱灵魂上许多不同的不同步操作的主题:妈妈,爸爸,祖母,祖父,僵尸,幼儿园,学校,朋友,女朋友等。按清单。
我会尽力解释。我可能是错的。
有java,源代码被编译成字节码。字节码在程序执行期间被转换成机器码。字节码中的一条指令/命令可以翻译成多条机器码指令。这就是原子性的问题。处理器不能一次执行一条用高级语言编写的指令:它执行包含一系列指令的机器码。因此,如果不同的处理器对相同的数据进行操作,那么处理器的不同指令可以交错。
我举个例子:
有一个全局变量:
递增变量不是原子操作:它至少需要三个指令:
因此,两个线程必须执行这个序列,但它们之间的执行顺序没有定义。因此,可能会出现如下情况:
结果,我们得到的结果是 1,而不是预期的 2。
为了防止这种情况发生,使用了包中的同步或原子原语
java.util.concurrent