基于本文回答

播面 播面

刷题像听歌,多听自然懂
0
评论

atomic 和 nonatomic 的区别?

知识点图片

atomicnonatomic 是 Objective-C 中用于定义属性(Property)特性的关键字,它们主要决定了编译器生成的 getter(获取器)setter(设置器) 方法是如何处理多线程访问的。

简单总结:

  • atomic(原子性): 默认属性。线程安全(仅指读写操作),速度慢。保证读取到的数据是完整的。
  • nonatomic(非原子性): 非线程安全,速度快。不保证数据完整性,但性能高。

以下是详细的区别分析:

1. 核心区别:线程安全性

atomic (默认)

  • 机制: 编译器会自动为生成的 getter 和 setter 方法加锁(通常是自旋锁或互斥锁)。
  • 效果: 保证在多线程环境下,当一个线程正在写入数据时,另一个线程无法读取或写入,必须等待当前操作完成。
  • 保证: 它保证你读取到的数据是完整的(即:要么是修改前的值,要么是修改后的值,不会是写了一半的“脏数据”)。
  • 缺点: 加锁和解锁会有较大的性能开销。

nonatomic

  • 机制: 编译器生成的 getter 和 setter 方法不加锁,直接访问内存地址。
  • 效果: 多个线程可以同时对该属性进行读写。
  • 风险: 如果两个线程同时操作(例如一个读、一个写,或者两个同时写),可能会导致:
    • 数据错误: 读取到只写了一半的数据(坏内存)。
    • 崩溃(Crash): 特别是对于对象类型,可能会导致 EXC_BAD_ACCESS(野指针错误),因为一个线程可能释放了对象,而另一个线程正在读取它。
  • 优点: 访问速度非常快,没有锁的开销。

2. 最大的误区:Atomic 就是线程安全的吗?

答案是:不完全是。

atomic 只能保证 setter 和 getter 的原子性,也就是保证“读”和“写”这两个单独的操作是安全的。但它不能保证线程安全的操作逻辑

举个例子:
假设有一个 atomic 的数组属性 @property (atomic, strong) NSMutableArray *array;

plaintext
// 线程 A
[self.array addObject:obj1];

// 线程 B
[self.array removeLastObject];

即使 self.arrayatomic 的:

  1. atomic 保证了你获取 self.array 指针时,这个指针是合法的。
  2. 但是,atomic 不保护数组内部的操作。线程 A 和 线程 B 可能同时操作数组内部数据,导致崩溃。

再举个例子(读写依赖):

plaintext
// 假设 count 是 atomic 的
if (self.count < 10) {
    // 在这两行代码之间,另一个线程可能已经把 count 改成了 100
    self.count = self.count + 1;
}

虽然读取 count 和写入 count 各自是原子的,但整个 if 逻辑块并不是线程安全的。


3. 性能对比

  • atomicnonatomic 慢得多。
  • 在早期的测试中,atomic 的访问速度可能比 nonatomic 慢 20 倍甚至更多(取决于具体的锁机制和竞争情况)。虽然现代硬件对其进行了优化,但在高频访问的场景下,性能损耗依然可观。

4. 开发中的最佳实践

在 iOS 开发中,绝大多数情况(99%)我们都使用 nonatomic

原因如下:

  1. 性能优先: 移动设备的资源有限,我们希望属性访问尽可能快。
  2. 手动管理线程安全: 如果真的需要多线程安全,atomic 提供的保护通常不够用(如上文所述)。开发者通常会使用更高级的锁机制(如 @synchronized, NSLock, dispatch_semaphore, serial queue)来保护一段完整的代码逻辑,而不仅仅是保护一个属性的读写。
  3. UI 线程: 所有的 UI 控件属性都在主线程访问,不存在多线程竞争,因此完全不需要 atomic

什么时候使用 atomic?

  • 当你确实需要在不同线程读写某个基本数据类型(如 BOOL, int)或指针,且不涉及复杂的逻辑操作时。
  • 作为库的开发者,为了防止使用者在多线程下因误用导致野指针崩溃,可能会将其设为 atomic 作为一种防御性编程。

总结表

特性 atomic nonatomic
默认值 是 (Objective-C 中)
线程安全 基本的读写安全 (防止脏读) 不安全 (可能导致 Crash)
性能 慢 (有锁开销) 快 (直接内存访问)
使用场景 极少使用 绝大多数场景
Swift 对应 Swift 属性默认非原子,无对应关键字 默认行为
00:00
00:00