MST

星途 面试题库

面试题:Objective-C单例模式在多线程环境下的优化考量

在Objective-C中,当单例模式应用于多线程环境时,可能会出现哪些问题?请列举至少两种,并阐述相应的优化措施以及每种措施的原理。
36.9万 热度难度
编程语言Objective-C

知识考点

AI 面试

面试题答案

一键面试

可能出现的问题

  1. 创建多个实例:在多线程环境下,如果多个线程同时调用单例的创建方法,可能会导致创建出多个单例实例,违背单例模式的初衷。例如,两个线程几乎同时判断单例实例为 nil,然后都开始创建实例。
  2. 数据竞争:如果单例对象中的属性或数据在多线程环境下被多个线程同时读写,可能会出现数据竞争问题,导致数据不一致或程序崩溃。比如一个线程正在修改单例中的某个数组,另一个线程同时读取这个数组,可能读到不完整或错误的数据。

优化措施及原理

  1. 使用 @synchronized 关键字
    • 措施:在创建单例的方法中,使用 @synchronized 块包裹实例创建的代码。例如:
+ (instancetype)sharedInstance {
    static MySingleton *sharedInstance = nil;
    @synchronized(self) {
        if (sharedInstance == nil) {
            sharedInstance = [[self alloc] init];
        }
    }
    return sharedInstance;
}
- **原理**:`@synchronized` 会为传入的对象(通常是类对象 `self`)创建一个互斥锁。当一个线程进入 `@synchronized` 块时,它会获取这个锁,其他线程如果也尝试进入相同对象的 `@synchronized` 块,就会被阻塞,直到当前线程释放锁。这样就保证了同一时间只有一个线程可以执行实例创建的代码,避免创建多个实例。

2. 使用 dispatch_once - 措施:使用 dispatch_once 函数来创建单例。示例代码如下:

+ (instancetype)sharedInstance {
    static MySingleton *sharedInstance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedInstance = [[self alloc] init];
    });
    return sharedInstance;
}
- **原理**:`dispatch_once` 是基于 Grand Central Dispatch (GCD) 的机制。它内部维护一个标记,当第一次调用 `dispatch_once` 时,会执行传入的代码块并设置标记。后续再次调用 `dispatch_once` 时,会检查这个标记,如果标记已设置,就不会再执行代码块,从而确保单例只被创建一次。`dispatch_once` 是线程安全的,并且效率较高,因为它只在第一次调用时执行初始化操作,并且在底层使用了高效的原子操作来保证线程安全。

3. 使用原子属性(针对数据竞争问题) - 措施:将单例中可能被多线程访问的属性声明为原子属性。例如:

@property (nonatomic, atomic, strong) NSMutableArray *dataArray;
- **原理**:原子属性会自动为属性的读写操作添加锁机制,确保在同一时间只有一个线程可以对属性进行读写。对于 `nonatomic`(非原子)属性,读写操作可能会被其他线程打断,导致数据不一致。而 `atomic`(原子)属性通过锁机制,保证了属性的读写操作的原子性,即要么完整执行读操作,要么完整执行写操作,不会出现中间状态,从而避免数据竞争问题。但需要注意的是,原子属性虽然保证了单个属性操作的线程安全,但对于涉及多个属性的复合操作,仍可能需要额外的同步机制。