MST

星途 面试题库

面试题:Objective-C中weak和strong在多线程环境下的表现与优化

在多线程环境中,weak和strong引用会遇到哪些潜在的问题?如何通过代码示例展示这些问题,并且针对这些问题提出可行的优化方案。请结合原子性、线程安全等概念进行阐述。
27.3万 热度难度
编程语言Objective-C

知识考点

AI 面试

面试题答案

一键面试

潜在问题

  1. weak引用
    • 对象提前释放:在多线程环境下,可能出现对象在一个线程中被释放,而其他线程中weak指针还未更新为nil的情况。比如在一个线程中对象A的引用计数降为0并被释放,与此同时另一个线程持有指向A的weak指针,此时该weak指针可能没有及时更新,导致使用时可能访问已释放的内存(虽然在ARC下会自动置为nil,但多线程场景可能存在延迟更新问题)。
    • 数据竞争:如果多个线程同时访问和修改与weak引用相关的对象状态,可能会引发数据竞争问题。例如,一个线程通过weak指针访问对象并修改其属性,同时另一个线程释放该对象,可能导致未定义行为。
  2. strong引用
    • 循环引用:多线程环境下循环引用问题依然存在。两个或多个对象相互持有strong引用,导致对象无法释放。例如,对象A持有对象B的strong引用,对象B也持有对象A的strong引用,在多线程中这种情况可能更难排查,因为对象的引用关系可能在不同线程中动态变化。
    • 原子性问题:对strong引用的对象进行操作可能不是原子性的。比如在一个线程中对对象进行赋值操作object = newObject,如果在操作过程中线程被打断,另一个线程读取object可能得到部分修改的结果,引发数据不一致问题。同时,在多线程环境下对对象属性的修改如果没有适当同步,也可能导致线程安全问题。例如,一个线程读取对象属性,另一个线程同时修改该属性,可能导致读取到不一致的数据。

代码示例展示问题

  1. weak引用问题示例(以Objective - C为例)
@interface MyObject : NSObject
@property (nonatomic, strong) NSString *name;
@end

@implementation MyObject
@end

- (void)testWeakReference {
    __weak MyObject *weakObject;
    dispatch_queue_t queue1 = dispatch_queue_create("queue1", DISPATCH_QUEUE_CONCURRENT);
    dispatch_queue_t queue2 = dispatch_queue_create("queue2", DISPATCH_QUEUE_CONCURRENT);

    dispatch_async(queue1, ^{
        MyObject *strongObject = [[MyObject alloc] init];
        strongObject.name = @"Initial Name";
        weakObject = strongObject;
        NSLog(@"Queue 1: Weak object set to %@", weakObject);
        // 模拟释放对象
        strongObject = nil;
    });

    dispatch_async(queue2, ^{
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"Queue 2: Weak object value is %@", weakObject);
            // 这里可能访问到已释放的对象(尽管ARC下通常会置nil,但存在多线程延迟更新问题)
        });
    });
}
  1. strong引用问题示例(以Objective - C为例)
@interface ObjectA : NSObject
@property (nonatomic, strong) ObjectB *objectB;
@end

@implementation ObjectA
@end

@interface ObjectB : NSObject
@property (nonatomic, strong) ObjectA *objectA;
@end

@implementation ObjectB
@end

- (void)testStrongReference {
    dispatch_queue_t queue1 = dispatch_queue_create("queue1", DISPATCH_QUEUE_CONCURRENT);
    dispatch_queue_t queue2 = dispatch_queue_create("queue2", DISPATCH_QUEUE_CONCURRENT);

    dispatch_async(queue1, ^{
        ObjectA *objectA = [[ObjectA alloc] init];
        ObjectB *objectB = [[ObjectB alloc] init];
        objectA.objectB = objectB;
        objectB.objectA = objectA;
        // 这里产生循环引用,即使在多线程中对象也无法释放
    });

    dispatch_async(queue2, ^{
        // 假设另一个线程尝试访问这些对象
        // 如果在对象A和B释放前访问,可能因循环引用导致内存问题
    });
}

优化方案

  1. weak引用优化
    • 使用同步机制:可以使用锁(如@synchronized块或dispatch_semaphore)来确保在对象释放时,所有对weak指针的更新操作都能正确完成。例如:
@synchronized(self) {
    MyObject *strongObject = [[MyObject alloc] init];
    __weak MyObject *weakObject = strongObject;
    // 其他操作
    strongObject = nil;
}
  • 引入中间层管理:可以创建一个中间对象来管理weak引用关系。例如,使用一个WeakObjectManager类,在对象释放时,通过该类来统一更新所有相关的weak指针,确保一致性。
  1. strong引用优化
    • 打破循环引用:对于可能出现的循环引用,可以将其中一个引用改为weak或unowned(在Swift中)。例如,在上述ObjectAObjectB的例子中,将ObjectBObjectA的引用改为@property (nonatomic, weak) ObjectA *objectA;,这样就打破了循环引用。
    • 使用原子操作:对于对strong引用对象的操作,可以使用原子操作。在Objective - C中,atomic属性可以保证属性的读写操作是原子性的。例如,@property (nonatomic, atomic, strong) MyObject *myObject;,但需要注意atomic属性并不能保证整个对象操作的线程安全性,只是保证属性的读写原子性,对于复杂的对象操作仍需要使用同步机制。
    • 同步机制:使用锁(如@synchronizedpthread_mutex等)或GCD的同步队列(dispatch_queue_tDISPATCH_QUEUE_SERIAL)来保证对对象的操作在多线程环境下的线程安全性。例如:
dispatch_queue_t serialQueue = dispatch_queue_create("serialQueue", DISPATCH_QUEUE_SERIAL);
dispatch_sync(serialQueue, ^{
    // 对对象的操作,保证线程安全
});