5

这是示例代码的链接http://developer.apple.com/library/ios/#samplecode/MVCNetworking/Introduction/Intro.html

下面是文件NetworkManager.m中的代码片段

+ (NetworkManager *)sharedManager
// See comment in header.
{
    static NetworkManager * sNetworkManager;

    // This can be called on any thread, so we synchronise.  We only do this in 
    // the sNetworkManager case because, once sNetworkManager goes non-nil, it can 
    // never go nil again.

    if (sNetworkManager == nil) {
        @synchronized (self) {
            sNetworkManager = [[NetworkManager alloc] init];
            assert(sNetworkManager != nil);
        }
    }
    return sNetworkManager;
}

显然这里存在线程安全问题。当有多个线程时,可能会创建两个 NetworkManager 实例。所以苹果犯了一个错误,对吧?

4

3 回答 3

2

是的你是对的。在并发环境中会出现问题。更好的方法是在之前使用双重检查alloc

+ (NetworkManager *)sharedManager
{
    static NetworkManager * sNetworkManager;
    if (sNetworkManager == nil) {
        @synchronized (self) {
            if (sNetworkManager == nil) {
                sNetworkManager = [[NetworkManager alloc] init];
                assert(sNetworkManager != nil);
            }
        }
    }
    return sNetworkManager;
}

并且有很多方法可以使用 Ojbective-C 编写单例,请查看这篇文章:我的 Objective-C 单例应该是什么样子?

更新

BobCromwell是正确的。double check lock苹果不推荐,苹果的文档Threading Programming Guide

双重检查锁试图通过在获取锁之前测试锁定条件来减少获取锁的开销。因为双重检查锁可能是不安全的,所以系统不为它们提供明确的支持,并且不鼓励使用它们。

于 2012-04-07T06:10:13.510 回答
1

是的,这是错误的。sNetworkManager从as开始nil,考虑两个线程 T1 和 T2。

如果不太可能,一种可能的情况是:

T1: Determines (sNetworkManager == nil) is true
T2: Determines (sNetworkManager == nil) is true
T1: Takes the @synchronized lock
    Creates a NetworkManager 
    Sets sNetworkManager
    Releases the lock
T2: Takes the @synchronized lock
    Creates a NetworkManager 
    Sets sNetworkManager, LEAKING the first one
    Releases the lock

这个问题有一些更安全的方法。

于 2012-04-06T06:27:33.440 回答
0

这段代码没有错误。只创建了一个 sNetworkManager,原因很简单,即使用了“静态”一词。此处使用 static 关键字将变量定义为全局变量,但仅对该函数可见。该变量在 + (NetworkManager *)sharedManager 的第一次调用中分配,然后它不再为 null 并且不再初始化。

于 2012-04-06T06:13:33.123 回答