单例的定义
单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例单例模式。
注:单例模式只应在有真正的“单一实例”的需求时才可使用。
单例的创建
单线程
+ (instancetype)shareInstance{
static Singleton* single;
if(!single) {
single = [[self alloc] init];
}
return single;
}
注:上面是单线程的单例。如果有多线程同时访问,不能保证为同一个实例。
多线程
使用@synchronized加锁来创建单例
+ (instancetype)shareInstance{
static Singleton* single;
@synchronized(self) {
if(!single) {
single = [[self alloc] init];
}
}
return single;
}
使用@synchronized虽然解决了多线程的问题,但有性能问题。因为加锁只有在single未创建时才有必要。如果single已经创建,就不需要加锁,但程序会再次进行synchronized验证,浪费性能。
dispatch_once 能做到既解决同步多线程问题而又不影响性能。
+ (SingletonManager*)shareInstance {
static Singleton* single;
static dispatch_once_t token;
dispatch_once(&token, ^{
if(single == nil) {
single = [[self alloc] init];
}
});
return single;
}
dispatch_once的原理
dispatch_once主要是根据onceToken的值来决定怎么去执行代码。
- 当onceToken= 0时,线程执行dispatch_once的block中代码
- 当onceToken= -1时,线程跳过dispatch_once的block中代码不执行
- 当onceToken为其他值时,线程被线程被阻塞,等待onceToken值改变
#ifdef __BLOCKS__
__OSX_AVAILABLE_STARTING(__MAC_10_6,__IPHONE_4_0)
DISPATCH_EXPORT DISPATCH_NONNULL_ALL DISPATCH_NOTHROW
void
dispatch_once(dispatch_once_t *predicate, dispatch_block_t block);
DISPATCH_INLINE DISPATCH_ALWAYS_INLINE DISPATCH_NONNULL_ALL DISPATCH_NOTHROW
void
_dispatch_once(dispatch_once_t *predicate, dispatch_block_t block)
{
if (DISPATCH_EXPECT(*predicate, ~0l) != ~0l) {
dispatch_once(predicate, block);
}
}
#undef dispatch_once
#define dispatch_once _dispatch_once
dispatch_once_t 发现其是 typedef long类型
静态变量在程序运行期间只被初始化一次,然后其在下一次被访问时,其值都是上次的值,其在除了这个初始化方法以外的任何地方都不能直接修改这两个变量的值。这是单例只被初始化一次的前提。
先声明了dispatch_once 函数,下面又实现了_dispatch_once 函数。
先取消dispatch_once 的定义, 然后把_dispatch_once 定义为dispatch_once。
所以用户调用 dispatch_once 函数,实际上调用的是_dispatch_once 函数;
而真正的dispatch_once 函数是在_dispatch_once 内调用的。
有一个判断条件是一个宏DISPATCH_EXPECT,而判断条件为 DISPATCH_EXPECT(*predicate, ~0l) ,就是说,*predicate 很可能是 ~0l ,而当 DISPATCH_EXPECT(*predicate, ~0l) 不是 ~0! 时才调用真正的dispatch_once 函数。
~0l的意思是长整型0按位取反,其实就是长整型的-1
DISPATCH_EXPECT的宏定义为
#if __GNUC__
#define DISPATCH_EXPECT(x, v) __builtin_expect((x), (v))
#else
#define DISPATCH_EXPECT(x, v) (x)
意思是如果没有定义__GNUC__的话 DISPATCH_EXPECT(x, v)就是第一个参数(x)。
__GNUC__只代表gcc的主版本号
__builtin_expect 作用是允许程序员将最有可能执行的分支告诉编译器。
这个指令的写法为:__builtin_expect(EXP, N), 意思是:EXP==N的概率很大。
目的是将“分支转移”的信息提供给编译器,这样编译器可以对代码进行优化,以减少指令跳转带来的性能下降。
总结
第一次运行,predicate的值是默认值0,线程执行dispatch_once,执行完之后predicate=-1,当另一个线程再次访问,predicate=-1,不执行dispatch_once;
如果有两个进程同时运行到dispatch_once 方法时,这个两个进程获取到的predicate 值都是0,那么最终两个进程都会调用 最原始那个dispatch_once 函数。
猜测:在极端情况下,也有可能出现2个实例。dispatch_once也不是线程安全。