使用 `AtomicBoolean` 和 `AtomicReference` 的正确姿势:避免并发陷阱

1,658 阅读4分钟

在多线程编程中,确保线程安全是一个关键问题。AtomicBooleanAtomicReference 是 Java 提供的原子变量类,用于在多线程环境中安全地操作布尔值和引用类型。然而,错误地使用这些原子类可能会导致并发问题。本文将通过一个常见的错误用法示例,探讨如何正确使用 AtomicBooleanAtomicReference,并深入分析其工作原理和应用场景。

错误示例

以下是一个错误使用 AtomicBoolean 的示例:

image.png

在这个示例中,registerBroadcast 方法试图通过检查 isRegistered 的值来决定是否调用 registerBroadcast 方法。然而,这种用法存在潜在的并发问题。

问题分析

上述代码的问题在于 isRegistered.get().not()registerBroadcast() 之间没有任何同步机制。假设有两个线程同时调用 registerBroadcast 方法:

1. 线程 A 调用 isRegistered.get().not(),返回 true
2. 线程 B 也调用 isRegistered.get().not(),也返回 true
3. 线程 A 调用 registerBroadcast()
4. 线程 B 也调用 registerBroadcast()

结果是 registerBroadcast 方法被调用了两次,这显然不是预期的行为。

正确用法

为了确保 registerBroadcast 方法只被调用一次,我们需要使用 AtomicBoolean 的原子操作。compareAndSet 方法可以确保在检查和设置值时的原子性:

image.png

在这个改进的版本中,compareAndSet(false, true) 方法会在 isRegistered 的值为 false 时将其设置为 true,并返回 true。如果 isRegistered 的值已经是 true,则返回 false。这样可以确保 registerBroadcast 方法只会被调用一次。

深入理解 AtomicBooleanAtomicReference

AtomicBooleanAtomicReference 是 Java 中 java.util.concurrent.atomic 包的一部分,它们提供了一种在多线程环境中安全操作布尔值和引用类型的方式。AtomicBooleanAtomicReference 使用了底层的硬件支持(如 CAS 操作)来确保操作的原子性。

CAS(Compare-And-Swap):CAS 是一种原子操作,它比较内存中的值与预期值,如果相等则更新为新值。这个操作是原子的,意味着在多线程环境中不会出现竞态条件。
CAS 更加详细的解析 深入解析Android中的CAS与synchronized并发控制:LifecycleScope与ViewModelScope源码分析

应用场景

AtomicBooleanAtomicReference 适用于以下场景:

1. 单次初始化:确保某个操作(如资源初始化、注册广播等)只执行一次。
2. 状态标记:在多线程环境中标记某个状态(如任务是否完成、资源是否释放等)。
3. 轻量级锁:在某些情况下,可以使用 AtomicBoolean 实现轻量级的锁机制。

实际案例:Android 系统源码中的 Lifecycle.coroutineScope

在 Android 系统源码中,有一个使用 AtomicReference 的示例,它确保 LifecycleCoroutineScope 只被初始化一次:

image.png

详细解释

internalScopeRef:这是一个 AtomicReference,用于存储 LifecycleCoroutineScopeImpl 的实例。
get 方法:在 get 方法中,首先尝试获取 internalScopeRef 的当前值。如果已经存在一个 LifecycleCoroutineScopeImpl 实例,则直接返回。
创建新实例:如果 internalScopeRef 为空,则创建一个新的 LifecycleCoroutineScopeImpl 实例。
compareAndSet 方法:使用 compareAndSet(null, newScope) 方法确保只有一个线程能够成功将 internalScopeRefnull 设置为 newScope。如果成功,则注册并返回新的 LifecycleCoroutineScopeImpl 实例。

其他注意事项

初始化:确保 AtomicBooleanAtomicReference 在使用前已经正确初始化。
其他原子类:Java 提供了其他原子类,如 AtomicIntegerAtomicLongAtomicReference,它们也可以用于类似的场景。
性能:虽然原子操作比锁更高效,但在高并发场景下,仍需注意性能问题。

实际案例

假设我们有一个需要在多线程环境中初始化的资源,我们可以使用 AtomicBoolean 来确保初始化操作只执行一次:

image.png

在这个示例中,无论有多少线程同时调用 initialize 方法,初始化操作只会执行一次。

总结

在多线程编程中,正确使用 AtomicBooleanAtomicReference 等原子类可以帮助我们避免并发问题。通过使用 compareAndSet 方法,我们可以确保操作的原子性,从而避免多个线程同时执行不安全的操作。希望本文能帮助你更好地理解和使用 AtomicBooleanAtomicReference,编写出更加健壮的多线程代码。

参考资料

- Java Documentation for AtomicBoolean
- Java Documentation for AtomicReference