开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第10天,点击查看活动详情
原子性
原子操作是那些总是一起执行的操作。它们要么一起执行,要么都不执行。如果一个操作是原子的,那么它不可能是部分完成的,要么它将是完整的,要么根本不开始,但不会是不完整的。
考虑下面的例子-
在上面的例子中,我们将值1写入int i。这是一个原子操作,因为将1赋值给i时,这将全部发生,或者根本不会发生。一个操作或一组操作的原子属性称为原子性。
现在,考虑下面的例子-
乍一看,这似乎是一个原子操作,但实际上不是。这一行实际上包含3个操作。
读取操作,其中i的值被读取。
修改操作,其中计算一个新值(i + 1)。
写入操作,其中新值被写入变量i。
现在,由于这些操作是分开的,这些操作集目前不是原子的,因为它们可能部分执行。这在单线程环境中可能有点难以理解,但是请想象一下在多个线程上运行这段代码。如果我们在一个线程上调用这个方法2次,它将导致i等于2。但是想象一下在两个线程上调用它。理想情况下,结果应该保持相同,但如果我们同时在两个线程上调用这个方法,那么线程A和B将同时读取值并同时更新值,结果i等于1。这是因为这里的操作集不是原子的。让我们看看如何使这些操作具有原子性。
原子变量
在这种情况下,原子变量可以发挥作用。原子变量允许我们对变量执行原子操作。
考虑下面的例子-
上面的例子展示了如何使用AtomicInt原子地更新值。incrementAndGet()所做的是自动地将值加1,然后返回更新后的值。如果程序现在在多线程环境中运行,假设有两个线程,那么最终结果将总是i等于2。这是因为无论哪个线程首先获得incrementAndGet()方法,由于它是一个原子操作,线程将把i的值更新为1,只有在这时另一个线程才能访问或更新它,这将使i的值变为2,从而给出正确的结果。
最常用的原子类是- AtomicInt, AtomicLong, AtomicBoolean和AtomicReference。所有这些都为各自的类提供了原子操作。AtomicReference可以用于任何类型的对象。
结论
当在多线程环境中编程时,我们需要避免一组操作的并发执行可能导致错误或意外行为的情况。所以,我们需要使这些操作具有原子性。对于单个变量的操作,我们可以通过使用原子变量类来实现这一点,它为我们提供了对变量的原子操作,从而在多线程环境中实现正确的行为。