單例的定義
單例模式確保某一個(gè)類只有一個(gè)實(shí)例,而且自行實(shí)例化并向整個(gè)系統(tǒng)提供這個(gè)實(shí)例單例模式琐脏。
注:單例模式只應(yīng)在有真正的“單一實(shí)例”的需求時(shí)才可使用攒砖。
單例的創(chuàng)建
單線程
+ (instancetype)shareInstance{
static Singleton* single;
if(!single) {
single = [[self alloc] init];
}
return single;
}
注:上面是單線程的單例。如果有多線程同時(shí)訪問日裙,不能保證為同一個(gè)實(shí)例吹艇。
多線程
使用@synchronized加鎖來創(chuàng)建單例
+ (instancetype)shareInstance{
static Singleton* single;
@synchronized(self) {
if(!single) {
single = [[self alloc] init];
}
}
return single;
}
使用@synchronized雖然解決了多線程的問題,但有性能問題昂拂。因?yàn)榧渔i只有在single未創(chuàng)建時(shí)才有必要受神。如果single已經(jīng)創(chuàng)建,就不需要加鎖格侯,但程序會(huì)再次進(jìn)行synchronized驗(yàn)證鼻听,浪費(fèi)性能。
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主要是根據(jù)onceToken的值來決定怎么去執(zhí)行代碼撑碴。
- 當(dāng)onceToken= 0時(shí),線程執(zhí)行dispatch_once的block中代碼
- 當(dāng)onceToken= -1時(shí)朝墩,線程跳過dispatch_once的block中代碼不執(zhí)行
- 當(dāng)onceToken為其他值時(shí)醉拓,線程被線程被阻塞,等待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
發(fā)現(xiàn)其是 typedef long
類型
靜態(tài)變量在程序運(yùn)行期間只被初始化一次,然后其在下一次被訪問時(shí)亿卤,其值都是上次的值愤兵,其在除了這個(gè)初始化方法以外的任何地方都不能直接修改這兩個(gè)變量的值。這是單例只被初始化一次的前提排吴。
先聲明了dispatch_once
函數(shù)秆乳,下面又實(shí)現(xiàn)了_dispatch_once
函數(shù)。
先取消dispatch_once
的定義傍念, 然后把_dispatch_once
定義為dispatch_once
矫夷。
所以用戶調(diào)用 dispatch_once
函數(shù),實(shí)際上調(diào)用的是_dispatch_once
函數(shù)憋槐;
而真正的dispatch_once
函數(shù)是在_dispatch_once
內(nèi)調(diào)用的双藕。
有一個(gè)判斷條件是一個(gè)宏DISPATCH_EXPECT
,而判斷條件為 DISPATCH_EXPECT(*predicate, ~0l)
阳仔,就是說忧陪,*predicate
很可能是 ~0l
,而當(dāng) DISPATCH_EXPECT(*predicate, ~0l)
不是 ~0!
時(shí)才調(diào)用真正的dispatch_once
函數(shù)近范。
~0l
的意思是長整型0
按位取反嘶摊,其實(shí)就是長整型的-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)
就是第一個(gè)參數(shù)(x)
。
__GNUC__
只代表gcc的主版本號(hào)
__builtin_expect
作用是允許程序員將最有可能執(zhí)行的分支告訴編譯器评矩。
這個(gè)指令的寫法為:__builtin_expect(EXP, N)
, 意思是:EXP==N的概率很大叶堆。
目的是將“分支轉(zhuǎn)移”的信息提供給編譯器,這樣編譯器可以對(duì)代碼進(jìn)行優(yōu)化斥杜,以減少指令跳轉(zhuǎn)帶來的性能下降虱颗。
總結(jié)
第一次運(yùn)行,predicate
的值是默認(rèn)值0
蔗喂,線程執(zhí)行dispatch_once
忘渔,執(zhí)行完之后predicate=-1
,當(dāng)另一個(gè)線程再次訪問缰儿,predicate=-1
畦粮,不執(zhí)行dispatch_once
;
如果有兩個(gè)進(jìn)程同時(shí)運(yùn)行到dispatch_once
方法時(shí)乖阵,這個(gè)兩個(gè)進(jìn)程獲取到的predicate
值都是0
宣赔,那么最終兩個(gè)進(jìn)程都會(huì)調(diào)用 最原始那個(gè)dispatch_once
函數(shù)。
猜測(cè):在極端情況下瞪浸,也有可能出現(xiàn)2個(gè)實(shí)例拉背。dispatch_once
也不是線程安全。