原文:http://www.cocoachina.com/ios/20170707/19769.html
本文主要是分享iOS多線程的相關(guān)內(nèi)容,為了更系統(tǒng)的講解燥翅,將分為以下7個(gè)方面來(lái)展開描述骑篙。
多線程的基本概念
線程的狀態(tài)與生命周期
多線程的四種解決方案:pthread,NSThread森书,GCD靶端,NSOperation
線程安全問(wèn)題
NSThread的使用
GCD的理解與使用
NSOperation的理解與使用
Demo在這里:WHMultiThreadDemo
Demo的運(yùn)行g(shù)if圖如下:
一、多線程的基本概念
進(jìn)程:可以理解成一個(gè)運(yùn)行中的應(yīng)用程序拄氯,是系統(tǒng)進(jìn)行資源分配和調(diào)度的基本單位躲查,是操作系統(tǒng)結(jié)構(gòu)的基礎(chǔ),主要管理資源译柏。
線程:是進(jìn)程的基本執(zhí)行單元镣煮,一個(gè)進(jìn)程對(duì)應(yīng)多個(gè)線程。
主線程:處理UI鄙麦,所有更新UI的操作都必須在主線程上執(zhí)行典唇。不要把耗時(shí)操作放在主線程,會(huì)卡界面胯府。
多線程:在同一時(shí)刻介衔,一個(gè)CPU只能處理1條線程,但CPU可以在多條線程之間快速的切換骂因,只要切換的足夠快炎咖,就造成了多線程一同執(zhí)行的假象。
線程就像火車的一節(jié)車廂寒波,進(jìn)程則是火車乘盼。車廂(線程)離開火車(進(jìn)程)是無(wú)法跑動(dòng)的,而火車(進(jìn)程)至少有一節(jié)車廂(主線程)俄烁。多線程可以看做多個(gè)車廂绸栅,它的出現(xiàn)是為了提高效率。
多線程是通過(guò)提高資源使用率來(lái)提高系統(tǒng)總體的效率页屠。
我們運(yùn)用多線程的目的是:將耗時(shí)的操作放在后臺(tái)執(zhí)行粹胯!
二、線程的狀態(tài)與生命周期
下圖是線程狀態(tài)示意圖辰企,從圖中可以看出線程的生命周期是:新建 - 就緒 - 運(yùn)行 - 阻塞 - 死亡
下面分別闡述線程生命周期中的每一步
新建:實(shí)例化線程對(duì)象
就緒:向線程對(duì)象發(fā)送start消息风纠,線程對(duì)象被加入可調(diào)度線程池等待CPU調(diào)度。
運(yùn)行:CPU 負(fù)責(zé)調(diào)度可調(diào)度線程池中線程的執(zhí)行牢贸。線程執(zhí)行完成之前议忽,狀態(tài)可能會(huì)在就緒和運(yùn)行之間來(lái)回切換。就緒和運(yùn)行之間的狀態(tài)變化由CPU負(fù)責(zé)十减,程序員不能干預(yù)栈幸。
阻塞:當(dāng)滿足某個(gè)預(yù)定條件時(shí),可以使用休眠或鎖帮辟,阻塞線程執(zhí)行速址。sleepForTimeInterval(休眠指定時(shí)長(zhǎng)),sleepUntilDate(休眠到指定日期)由驹,@synchronized(self):(互斥鎖)芍锚。
死亡:正常死亡,線程執(zhí)行完畢蔓榄。非正常死亡并炮,當(dāng)滿足某個(gè)條件后,在線程內(nèi)部中止執(zhí)行/在主線程中止線程對(duì)象
還有線程的exit和cancel
[NSThread exit]:一旦強(qiáng)行終止線程甥郑,后續(xù)的所有代碼都不會(huì)被執(zhí)行逃魄。
[thread cancel]取消:并不會(huì)直接取消線程,只是給線程對(duì)象添加 isCancelled 標(biāo)記澜搅。
三伍俘、多線程的四種解決方案
多線程的四種解決方案分別是:pthread,NSThread勉躺,GCD癌瘾, NSOperation。
下圖是對(duì)這四種方案進(jìn)行了解讀和對(duì)比饵溅。
四妨退、線程安全問(wèn)題
當(dāng)多個(gè)線程訪問(wèn)同一塊資源時(shí),很容易引發(fā)數(shù)據(jù)錯(cuò)亂和數(shù)據(jù)安全問(wèn)題蜕企。就好比幾個(gè)人在同一時(shí)修改同一個(gè)表格咬荷,造成數(shù)據(jù)的錯(cuò)亂。
解決多線程安全問(wèn)題的方法
方法一:互斥鎖(同步鎖)
@synchronized(鎖對(duì)象)?{
//?需要鎖定的代碼
}
判斷的時(shí)候鎖對(duì)象要存在糖赔,如果代碼中只有一個(gè)地方需要加鎖萍丐,大多都使用self作為鎖對(duì)象,這樣可以避免單獨(dú)再創(chuàng)建一個(gè)鎖對(duì)象放典。
加了互斥做的代碼逝变,當(dāng)新線程訪問(wèn)時(shí),如果發(fā)現(xiàn)其他線程正在執(zhí)行鎖定的代碼奋构,新線程就會(huì)進(jìn)入休眠壳影。
方法二:自旋鎖
加了自旋鎖,當(dāng)新線程訪問(wèn)代碼時(shí)弥臼,如果發(fā)現(xiàn)有其他線程正在鎖定代碼宴咧,新線程會(huì)用死循環(huán)的方式,一直等待鎖定的代碼執(zhí)行完成径缅。相當(dāng)于不停嘗試執(zhí)行代碼掺栅,比較消耗性能烙肺。
屬性修飾atomic本身就有一把自旋鎖。
下面說(shuō)一下屬性修飾nonatomic 和 atomic
nonatomic?非原子屬性,同一時(shí)間可以有很多線程讀和寫
atomic?原子屬性(線程安全)氧卧,保證同一時(shí)間只有一個(gè)線程能夠?qū)懭?但是同一個(gè)時(shí)間多個(gè)線程都可以取值)桃笙,atomic?本身就有一把鎖(自旋鎖)
atomic:線程安全,需要消耗大量的資源
nonatomic:非線程安全沙绝,不過(guò)效率更高搏明,一般使用nonatomic
五、NSThread的使用
No.1:NSThread創(chuàng)建線程
NSThread有三種創(chuàng)建方式:
init方式
detachNewThreadSelector創(chuàng)建好之后自動(dòng)啟動(dòng)
performSelectorInBackground創(chuàng)建好之后也是直接啟動(dòng)
/**?方法一闪檬,需要start?*/
NSThread?*thread1?=?[[NSThread?alloc]?initWithTarget:self?selector:@selector(doSomething1:)?object:@"NSThread1"];
//?線程加入線程池等待CPU調(diào)度星著,時(shí)間很快,幾乎是立刻執(zhí)行
[thread1?start];
/**?方法二粗悯,創(chuàng)建好之后自動(dòng)啟動(dòng)?*/
[NSThread?detachNewThreadSelector:@selector(doSomething2:)?toTarget:self?withObject:@"NSThread2"];
/**?方法三虚循,隱式創(chuàng)建,直接啟動(dòng)?*/
[self?performSelectorInBackground:@selector(doSomething3:)?withObject:@"NSThread3"];
-?(void)doSomething1:(NSObject?*)object?{
//?傳遞過(guò)來(lái)的參數(shù)
NSLog(@"%@",object);
NSLog(@"doSomething1:%@",[NSThread?currentThread]);
}
-?(void)doSomething2:(NSObject?*)object?{
NSLog(@"%@",object);
NSLog(@"doSomething2:%@",[NSThread?currentThread]);
}
-?(void)doSomething3:(NSObject?*)object?{
NSLog(@"%@",object);
NSLog(@"doSomething3:%@",[NSThread?currentThread]);
}
No.2:NSThread的類方法
返回當(dāng)前線程
//?當(dāng)前線程
[NSThread?currentThread];
NSLog(@"%@",[NSThread?currentThread]);
//?如果number=1为黎,則表示在主線程邮丰,否則是子線程
打印結(jié)果:{number?=?1,?name?=?main}
阻塞休眠
//休眠多久
[NSThread?sleepForTimeInterval:2];
//休眠到指定時(shí)間
[NSThread?sleepUntilDate:[NSDate?date]];
類方法補(bǔ)充
//退出線程
[NSThread?exit];
//判斷當(dāng)前線程是否為主線程
[NSThread?isMainThread];
//判斷當(dāng)前線程是否是多線程
[NSThread?isMultiThreaded];
//主線程的對(duì)象
NSThread?*mainThread?=?[NSThread?mainThread];
No.3:NSThread的一些屬性
//線程是否在執(zhí)行
thread.isExecuting;
//線程是否被取消
thread.isCancelled;
//線程是否完成
thread.isFinished;
//是否是主線程
thread.isMainThread;
//線程的優(yōu)先級(jí),取值范圍0.0到1.0铭乾,默認(rèn)優(yōu)先級(jí)0.5剪廉,1.0表示最高優(yōu)先級(jí),優(yōu)先級(jí)高炕檩,CPU調(diào)度的頻率高
thread.threadPriority;
Demo:WHMultiThreadDemo
六斗蒋、GCD的理解與使用
No.1:GCD的特點(diǎn)
GCD會(huì)自動(dòng)利用更多的CPU內(nèi)核
GCD自動(dòng)管理線程的生命周期(創(chuàng)建線程,調(diào)度任務(wù)笛质,銷毀線程等)
程序員只需要告訴 GCD 想要如何執(zhí)行什么任務(wù)泉沾,不需要編寫任何線程管理代碼
No.2:GCD的基本概念
任務(wù)(block):任務(wù)就是將要在線程中執(zhí)行的代碼,將這段代碼用block封裝好妇押,然后將這個(gè)任務(wù)添加到指定的執(zhí)行方式(同步執(zhí)行和異步執(zhí)行)跷究,等待CPU從隊(duì)列中取出任務(wù)放到對(duì)應(yīng)的線程中執(zhí)行。
同步(sync):一個(gè)接著一個(gè)敲霍,前一個(gè)沒(méi)有執(zhí)行完俊马,后面不能執(zhí)行,不開線程肩杈。
異步(async):開啟多個(gè)新線程柴我,任務(wù)同一時(shí)間可以一起執(zhí)行。異步是多線程的代名詞
隊(duì)列:裝載線程任務(wù)的隊(duì)形結(jié)構(gòu)扩然。(系統(tǒng)以先進(jìn)先出的方式調(diào)度隊(duì)列中的任務(wù)執(zhí)行)艘儒。在GCD中有兩種隊(duì)列:串行隊(duì)列和并發(fā)隊(duì)列。
并發(fā)隊(duì)列:線程可以同時(shí)一起進(jìn)行執(zhí)行。實(shí)際上是CPU在多條線程之間快速的切換界睁。(并發(fā)功能只有在異步(dispatch_async)函數(shù)下才有效)
串行隊(duì)列:線程只能依次有序的執(zhí)行觉增。
GCD總結(jié):將任務(wù)(要在線程中執(zhí)行的操作block)添加到隊(duì)列(自己創(chuàng)建或使用全局并發(fā)隊(duì)列),并且指定執(zhí)行任務(wù)的方式(異步dispatch_async翻斟,同步dispatch_sync)
No.3:隊(duì)列的創(chuàng)建方法
使用dispatch_queue_create來(lái)創(chuàng)建隊(duì)列對(duì)象抑片,傳入兩個(gè)參數(shù),第一個(gè)參數(shù)表示隊(duì)列的唯一標(biāo)識(shí)符杨赤,可為空。第二個(gè)參數(shù)用來(lái)表示串行隊(duì)列(DISPATCH_QUEUE_SERIAL)或并發(fā)隊(duì)列(DISPATCH_QUEUE_CONCURRENT)截汪。
//?串行隊(duì)列
dispatch_queue_t?queue?=?dispatch_queue_create("test",?DISPATCH_QUEUE_SERIAL);
//?并發(fā)隊(duì)列
dispatch_queue_t?queue1?=?dispatch_queue_create("test",?DISPATCH_QUEUE_CONCURRENT);
GCD的隊(duì)列還有另外兩種:
主隊(duì)列:主隊(duì)列負(fù)責(zé)在主線程上調(diào)度任務(wù)疾牲,如果在主線程上已經(jīng)有任務(wù)正在執(zhí)行,主隊(duì)列會(huì)等到主線程空閑后再調(diào)度任務(wù)衙解。通常是返回主線程更新UI的時(shí)候使用阳柔。dispatch_get_main_queue()
dispatch_async(dispatch_get_global_queue(0,?0),?^{
//?耗時(shí)操作放在這里
3
dispatch_async(dispatch_get_main_queue(),?^{
//?回到主線程進(jìn)行UI操作
3
});
});
全局并發(fā)隊(duì)列:全局并發(fā)隊(duì)列是就是一個(gè)并發(fā)隊(duì)列,是為了讓我們更方便的使用多線程蚓峦。dispatch_get_global_queue(0, 0)
//全局并發(fā)隊(duì)列
dispatch_queue_t?queue?=?dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,?0);
//全局并發(fā)隊(duì)列的優(yōu)先級(jí)
#define?DISPATCH_QUEUE_PRIORITY_HIGH?2?//?高優(yōu)先級(jí)
#define?DISPATCH_QUEUE_PRIORITY_DEFAULT?0?//?默認(rèn)(中)優(yōu)先級(jí)
#define?DISPATCH_QUEUE_PRIORITY_LOW?(-2)?//?低優(yōu)先級(jí)
#define?DISPATCH_QUEUE_PRIORITY_BACKGROUND?INT16_MIN?//?后臺(tái)優(yōu)先級(jí)
//iOS8開始使用服務(wù)質(zhì)量舌剂,現(xiàn)在獲取全局并發(fā)隊(duì)列時(shí),可以直接傳0
dispatch_get_global_queue(0,?0);
No.4:同步/異步/任務(wù)暑椰、創(chuàng)建方式
同步(sync)使用dispatch_sync來(lái)表示霍转。
異步(async)使用dispatch_async。
任務(wù)就是將要在線程中執(zhí)行的代碼一汽,將這段代碼用block封裝好避消。
代碼如下:
//?同步執(zhí)行任務(wù)
dispatch_sync(dispatch_get_global_queue(0,?0),?^{
//?任務(wù)放在這個(gè)block里
NSLog(@"我是同步執(zhí)行的任務(wù)");
});
//?異步執(zhí)行任務(wù)
dispatch_async(dispatch_get_global_queue(0,?0),?^{
//?任務(wù)放在這個(gè)block里
NSLog(@"我是異步執(zhí)行的任務(wù)");
});
Demo:WHMultiThreadDemo
No.5:GCD的使用
由于有多種隊(duì)列(串行/并發(fā)/主隊(duì)列)和兩種執(zhí)行方式(同步/異步),所以他們之間可以有多種組合方式召夹。
串行同步
串行異步
并發(fā)同步
并發(fā)異步
主隊(duì)列同步
主隊(duì)列異步
串行同步
執(zhí)行完一個(gè)任務(wù)岩喷,再執(zhí)行下一個(gè)任務(wù)。不開啟新線程监憎。
/**?串行同步?*/
-?(void)syncSerial?{
NSLog(@"\n\n**************串行同步***************\n\n");
//?串行隊(duì)列
dispatch_queue_t?queue?=?dispatch_queue_create("test",?DISPATCH_QUEUE_SERIAL);
//?同步執(zhí)行
dispatch_sync(queue,?^{
for?(int?i?=?0;?i?<?3;?i++)?{
NSLog(@"串行同步1???%@",[NSThread?currentThread]);
}
});
dispatch_sync(queue,?^{
for?(int?i?=?0;?i?<?3;?i++)?{
NSLog(@"串行同步2???%@",[NSThread?currentThread]);
}
});
dispatch_sync(queue,?^{
for?(int?i?=?0;?i?<?3;?i++)?{
NSLog(@"串行同步3???%@",[NSThread?currentThread]);
}
});
}
輸入結(jié)果為順序執(zhí)行纱意,都在主線程:
串行同步1???{number?=?1,?name?=?main}
串行同步1???{number?=?1,?name?=?main}
串行同步1???{number?=?1,?name?=?main}
串行同步2???{number?=?1,?name?=?main}
串行同步2???{number?=?1,?name?=?main}
串行同步2???{number?=?1,?name?=?main}
串行同步3???{number?=?1,?name?=?main}
串行同步3???{number?=?1,?name?=?main}
串行同步3???{number?=?1,?name?=?main}
串行異步
開啟新線程,但因?yàn)槿蝿?wù)是串行的鲸阔,所以還是按順序執(zhí)行任務(wù)偷霉。
/**?串行異步?*/
-?(void)asyncSerial?{
NSLog(@"\n\n**************串行異步***************\n\n");
//?串行隊(duì)列
dispatch_queue_t?queue?=?dispatch_queue_create("test",?DISPATCH_QUEUE_SERIAL);
//?同步執(zhí)行
dispatch_async(queue,?^{
for?(int?i?=?0;?i?<?3;?i++)?{
NSLog(@"串行異步1???%@",[NSThread?currentThread]);
}
});
dispatch_async(queue,?^{
for?(int?i?=?0;?i?<?3;?i++)?{
NSLog(@"串行異步2???%@",[NSThread?currentThread]);
}
});
dispatch_async(queue,?^{
for?(int?i?=?0;?i?<?3;?i++)?{
NSLog(@"串行異步3???%@",[NSThread?currentThread]);
}
});
}
輸入結(jié)果為順序執(zhí)行,有不同線程:
串行異步1???{number?=?3,?name?=?(null)}
串行異步1???{number?=?3,?name?=?(null)}
串行異步1???{number?=?3,?name?=?(null)}
串行異步2???{number?=?3,?name?=?(null)}
串行異步2???{number?=?3,?name?=?(null)}
串行異步2???{number?=?3,?name?=?(null)}
串行異步3???{number?=?3,?name?=?(null)}
串行異步3???{number?=?3,?name?=?(null)}
串行異步3???{number?=?3,?name?=?(null)}
并發(fā)同步
因?yàn)槭峭降牧フ詧?zhí)行完一個(gè)任務(wù)腾它,再執(zhí)行下一個(gè)任務(wù)。不會(huì)開啟新線程死讹。
/**?并發(fā)同步?*/
-?(void)syncConcurrent?{
3
NSLog(@"\n\n**************并發(fā)同步***************\n\n");
3
//?并發(fā)隊(duì)列
dispatch_queue_t?queue?=?dispatch_queue_create("test",?DISPATCH_QUEUE_CONCURRENT);
3
//?同步執(zhí)行
dispatch_sync(queue,?^{
for?(int?i?=?0;?i?<?3;?i++)?{
NSLog(@"并發(fā)同步1???%@",[NSThread?currentThread]);
}
});
dispatch_sync(queue,?^{
for?(int?i?=?0;?i?<?3;?i++)?{
NSLog(@"并發(fā)同步2???%@",[NSThread?currentThread]);
}
});
dispatch_sync(queue,?^{
for?(int?i?=?0;?i?<?3;?i++)?{
NSLog(@"并發(fā)同步3???%@",[NSThread?currentThread]);
}
});
}
輸入結(jié)果為順序執(zhí)行瞒滴,都在主線程:
并發(fā)同步1???{number?=?1,?name?=?main}
并發(fā)同步1???{number?=?1,?name?=?main}
并發(fā)同步1???{number?=?1,?name?=?main}
并發(fā)同步2???{number?=?1,?name?=?main}
并發(fā)同步2???{number?=?1,?name?=?main}
并發(fā)同步2???{number?=?1,?name?=?main}
并發(fā)同步3???{number?=?1,?name?=?main}
并發(fā)同步3???{number?=?1,?name?=?main}
并發(fā)同步3???{number?=?1,?name?=?main}
并發(fā)異步
任務(wù)交替執(zhí)行,開啟多線程。
/**?并發(fā)異步?*/
-?(void)asyncConcurrent?{
NSLog(@"\n\n**************并發(fā)異步***************\n\n");
//?并發(fā)隊(duì)列
dispatch_queue_t?queue?=?dispatch_queue_create("test",?DISPATCH_QUEUE_CONCURRENT);
//?同步執(zhí)行
dispatch_async(queue,?^{
for?(int?i?=?0;?i?<?3;?i++)?{
NSLog(@"并發(fā)異步1???%@",[NSThread?currentThread]);
}
});
dispatch_async(queue,?^{
for?(int?i?=?0;?i?<?3;?i++)?{
NSLog(@"并發(fā)異步2???%@",[NSThread?currentThread]);
}
});
dispatch_async(queue,?^{
for?(int?i?=?0;?i?<?3;?i++)?{
NSLog(@"并發(fā)異步3???%@",[NSThread?currentThread]);
}
});
}
輸入結(jié)果為無(wú)序執(zhí)行妓忍,有多條線程:
并發(fā)異步1???{number?=?3,?name?=?(null)}
并發(fā)異步2???{number?=?4,?name?=?(null)}
并發(fā)異步3???{number?=?5,?name?=?(null)}
并發(fā)異步1???{number?=?3,?name?=?(null)}
并發(fā)異步2???{number?=?4,?name?=?(null)}
并發(fā)異步3???{number?=?5,?name?=?(null)}
并發(fā)異步1???{number?=?3,?name?=?(null)}
并發(fā)異步2???{number?=?4,?name?=?(null)}
并發(fā)異步3???{number?=?5,?name?=?(null)}
主隊(duì)列同步
如果在主線程中運(yùn)用這種方式虏两,則會(huì)發(fā)生死鎖,程序崩潰世剖。
/**?主隊(duì)列同步?*/
-?(void)syncMain?{
NSLog(@"\n\n**************主隊(duì)列同步定罢,放到主線程會(huì)死鎖***************\n\n");
//?主隊(duì)列
dispatch_queue_t?queue?=?dispatch_get_main_queue();
dispatch_sync(queue,?^{
for?(int?i?=?0;?i?<?3;?i++)?{
NSLog(@"主隊(duì)列同步1???%@",[NSThread?currentThread]);
}
});
dispatch_sync(queue,?^{
for?(int?i?=?0;?i?<?3;?i++)?{
NSLog(@"主隊(duì)列同步2???%@",[NSThread?currentThread]);
}
});
dispatch_sync(queue,?^{
for?(int?i?=?0;?i?<?3;?i++)?{
NSLog(@"主隊(duì)列同步3???%@",[NSThread?currentThread]);
}
});
}
主隊(duì)列同步造成死鎖的原因:
如果在主線程中運(yùn)用主隊(duì)列同步,也就是把任務(wù)放到了主線程的隊(duì)列中旁瘫。
而同步對(duì)于任務(wù)是立刻執(zhí)行的祖凫,那么當(dāng)把第一個(gè)任務(wù)放進(jìn)主隊(duì)列時(shí),它就會(huì)立馬執(zhí)行酬凳。
可是主線程現(xiàn)在正在處理syncMain方法惠况,任務(wù)需要等syncMain執(zhí)行完才能執(zhí)行。
syncMain執(zhí)行到第一個(gè)任務(wù)的時(shí)候宁仔,又要等第一個(gè)任務(wù)執(zhí)行完才能往下執(zhí)行第二個(gè)和第三個(gè)任務(wù)稠屠。
這樣syncMain方法和第一個(gè)任務(wù)就開始了互相等待,形成了死鎖翎苫。
主隊(duì)列異步
在主線程中任務(wù)按順序執(zhí)行权埠。
/**?主隊(duì)列異步?*/
-?(void)asyncMain?{
NSLog(@"\n\n**************主隊(duì)列異步***************\n\n");
//?主隊(duì)列
dispatch_queue_t?queue?=?dispatch_get_main_queue();
dispatch_sync(queue,?^{
for?(int?i?=?0;?i?<?3;?i++)?{
NSLog(@"主隊(duì)列異步1???%@",[NSThread?currentThread]);
}
});
dispatch_sync(queue,?^{
for?(int?i?=?0;?i?<?3;?i++)?{
NSLog(@"主隊(duì)列異步2???%@",[NSThread?currentThread]);
}
});
dispatch_sync(queue,?^{
for?(int?i?=?0;?i?<?3;?i++)?{
NSLog(@"主隊(duì)列異步3???%@",[NSThread?currentThread]);
}
});
}
輸入結(jié)果為在主線程中按順序執(zhí)行:
主隊(duì)列異步1???{number?=?1,?name?=?main}
主隊(duì)列異步1???{number?=?1,?name?=?main}
主隊(duì)列異步1???{number?=?1,?name?=?main}
主隊(duì)列異步2???{number?=?1,?name?=?main}
主隊(duì)列異步2???{number?=?1,?name?=?main}
主隊(duì)列異步2???{number?=?1,?name?=?main}
主隊(duì)列異步3???{number?=?1,?name?=?main}
主隊(duì)列異步3???{number?=?1,?name?=?main}
主隊(duì)列異步3???{number?=?1,?name?=?main}
GCD線程之間的通訊
開發(fā)中需要在主線程上進(jìn)行UI的相關(guān)操作,通常會(huì)把一些耗時(shí)的操作放在其他線程煎谍,比如說(shuō)圖片文件下載等耗時(shí)操作攘蔽。
當(dāng)完成了耗時(shí)操作之后,需要回到主線程進(jìn)行UI的處理粱快,這里就用到了線程之間的通訊秩彤。
-?(IBAction)communicationBetweenThread:(id)sender?{
//?異步
dispatch_async(dispatch_get_global_queue(0,?0),?^{
//?耗時(shí)操作放在這里,例如下載圖片事哭。(運(yùn)用線程休眠兩秒來(lái)模擬耗時(shí)操作)
[NSThread?sleepForTimeInterval:2];
NSString?*picURLStr?=?@"http://www.bangmangxuan.net/uploads/allimg/160320/74-160320130500.jpg";
NSURL?*picURL?=?[NSURL?URLWithString:picURLStr];
NSData?*picData?=?[NSData?dataWithContentsOfURL:picURL];
UIImage?*image?=?[UIImage?imageWithData:picData];
//?回到主線程處理UI
dispatch_async(dispatch_get_main_queue(),?^{
//?在主線程上添加圖片
self.imageView.image?=?image;
});
});
}
上面的代碼是在新開的線程中進(jìn)行圖片的下載漫雷,下載完成之后回到主線程顯示圖片。
GCD柵欄
當(dāng)任務(wù)需要異步進(jìn)行鳍咱,但是這些任務(wù)需要分成兩組來(lái)執(zhí)行降盹,第一組完成之后才能進(jìn)行第二組的操作。這時(shí)候就用了到GCD的柵欄方法dispatch_barrier_async谤辜。
-?(IBAction)barrierGCD:(id)sender?{
//?并發(fā)隊(duì)列
dispatch_queue_t?queue?=?dispatch_queue_create("test",?DISPATCH_QUEUE_CONCURRENT);
//?異步執(zhí)行
dispatch_async(queue,?^{
for?(int?i?=?0;?i?<?3;?i++)?{
NSLog(@"柵欄:并發(fā)異步1???%@",[NSThread?currentThread]);
}
});
dispatch_async(queue,?^{
for?(int?i?=?0;?i?<?3;?i++)?{
NSLog(@"柵欄:并發(fā)異步2???%@",[NSThread?currentThread]);
}
});
dispatch_barrier_async(queue,?^{
NSLog(@"------------barrier------------%@",?[NSThread?currentThread]);
NSLog(@"*******?并發(fā)異步執(zhí)行蓄坏,但是34一定在12后面?*********");
});
dispatch_async(queue,?^{
for?(int?i?=?0;?i?<?3;?i++)?{
NSLog(@"柵欄:并發(fā)異步3???%@",[NSThread?currentThread]);
}
});
dispatch_async(queue,?^{
for?(int?i?=?0;?i?<?3;?i++)?{
NSLog(@"柵欄:并發(fā)異步4???%@",[NSThread?currentThread]);
}
});
}
上面代碼的打印結(jié)果如下,開啟了多條線程丑念,所有任務(wù)都是并發(fā)異步進(jìn)行涡戳。但是第一組完成之后,才會(huì)進(jìn)行第二組的操作脯倚。
柵欄:并發(fā)異步1???{number?=?3,?name?=?(null)}
柵欄:并發(fā)異步2???{number?=?6,?name?=?(null)}
柵欄:并發(fā)異步1???{number?=?3,?name?=?(null)}
柵欄:并發(fā)異步2???{number?=?6,?name?=?(null)}
柵欄:并發(fā)異步1???{number?=?3,?name?=?(null)}
柵欄:并發(fā)異步2???{number?=?6,?name?=?(null)}
------------barrier------------{number?=?6,?name?=?(null)}
*******?并發(fā)異步執(zhí)行渔彰,但是34一定在12后面?*********
柵欄:并發(fā)異步4???{number?=?3,?name?=?(null)}
柵欄:并發(fā)異步3???{number?=?6,?name?=?(null)}
柵欄:并發(fā)異步4???{number?=?3,?name?=?(null)}
柵欄:并發(fā)異步3???{number?=?6,?name?=?(null)}
柵欄:并發(fā)異步4???{number?=?3,?name?=?(null)}
柵欄:并發(fā)異步3???{number?=?6,?name?=?(null)}
GCD延時(shí)執(zhí)行
當(dāng)需要等待一會(huì)再執(zhí)行一段代碼時(shí)嵌屎,就可以用到這個(gè)方法了:dispatch_after。
dispatch_after(dispatch_time(DISPATCH_TIME_NOW,?(int64_t)(5.0?*?NSEC_PER_SEC)),?dispatch_get_main_queue(),?^{
//?5秒后異步執(zhí)行
NSLog(@"我已經(jīng)等待了5秒恍涂!");
});
GCD實(shí)現(xiàn)代碼只執(zhí)行一次
使用dispatch_once能保證某段代碼在程序運(yùn)行過(guò)程中只被執(zhí)行1次宝惰。可以用來(lái)設(shè)計(jì)單例再沧。
static?dispatch_once_t?onceToken;
dispatch_once(&onceToken,?^{
NSLog(@"程序運(yùn)行過(guò)程中我只執(zhí)行了一次尼夺!");
});
GCD快速迭代
GCD有一個(gè)快速迭代的方法dispatch_apply,dispatch_apply可以同時(shí)遍歷多個(gè)數(shù)字炒瘸。
-?(IBAction)applyGCD:(id)sender?{
NSLog(@"\n\n**************?GCD快速迭代?***************\n\n");
//?并發(fā)隊(duì)列
dispatch_queue_t?queue?=?dispatch_get_global_queue(0,?0);
//?dispatch_apply幾乎同時(shí)遍歷多個(gè)數(shù)字
dispatch_apply(7,?queue,?^(size_t?index)?{
NSLog(@"dispatch_apply:%zd======%@",index,?[NSThread?currentThread]);
});
}
打印結(jié)果如下:
dispatch_apply:0======{number?=?1,?name?=?main}
dispatch_apply:1======{number?=?1,?name?=?main}
dispatch_apply:2======{number?=?1,?name?=?main}
dispatch_apply:3======{number?=?1,?name?=?main}
dispatch_apply:4======{number?=?1,?name?=?main}
dispatch_apply:5======{number?=?1,?name?=?main}
dispatch_apply:6======{number?=?1,?name?=?main}
GCD隊(duì)列組
異步執(zhí)行幾個(gè)耗時(shí)操作淤堵,當(dāng)這幾個(gè)操作都完成之后再回到主線程進(jìn)行操作巫湘,就可以用到隊(duì)列組了炫乓。
隊(duì)列組有下面幾個(gè)特點(diǎn):
所有的任務(wù)會(huì)并發(fā)的執(zhí)行(不按序)挫以。
所有的異步函數(shù)都添加到隊(duì)列中勤揩,然后再納入隊(duì)列組的監(jiān)聽(tīng)范圍。
使用dispatch_group_notify函數(shù)卿拴,來(lái)監(jiān)聽(tīng)上面的任務(wù)是否完成,如果完成, 就會(huì)調(diào)用這個(gè)方法。
隊(duì)列組示例代碼:
-?(void)testGroup?{
dispatch_group_t?group?=??dispatch_group_create();
dispatch_group_async(group,?dispatch_get_global_queue(0,?0),?^{
NSLog(@"隊(duì)列組:有一個(gè)耗時(shí)操作完成技俐!");
});
dispatch_group_async(group,?dispatch_get_global_queue(0,?0),?^{
NSLog(@"隊(duì)列組:有一個(gè)耗時(shí)操作完成!");
});
dispatch_group_notify(group,?dispatch_get_main_queue(),?^{
NSLog(@"隊(duì)列組:前面的耗時(shí)操作都完成了统台,回到主線程進(jìn)行相關(guān)操作");
});
}
打印結(jié)果如下:
隊(duì)列組:有一個(gè)耗時(shí)操作完成雕擂!
隊(duì)列組:有一個(gè)耗時(shí)操作完成!
隊(duì)列組:前面的耗時(shí)操作都完成了贱勃,回到主線程進(jìn)行相關(guān)操作
至此井赌,GCD的相關(guān)內(nèi)容敘述完畢。下面讓我們繼續(xù)學(xué)習(xí)NSOperation贵扰。
Demo:WHMultiThreadDemo
七仇穗、NSOperation的理解與使用
No.1:NSOperation簡(jiǎn)介
NSOperation是基于GCD之上的更高一層封裝,NSOperation需要配合NSOperationQueue來(lái)實(shí)現(xiàn)多線程戚绕。
NSOperation實(shí)現(xiàn)多線程的步驟如下:
1.?創(chuàng)建任務(wù):先將需要執(zhí)行的操作封裝到NSOperation對(duì)象中纹坐。
2.?創(chuàng)建隊(duì)列:創(chuàng)建NSOperationQueue。
3.?將任務(wù)加入到隊(duì)列中:將NSOperation對(duì)象添加到NSOperationQueue中舞丛。
需要注意的是耘子,NSOperation是個(gè)抽象類,實(shí)際運(yùn)用時(shí)中需要使用它的子類球切,有三種方式:
使用子類NSInvocationOperation
使用子類NSBlockOperation
定義繼承自NSOperation的子類谷誓,通過(guò)實(shí)現(xiàn)內(nèi)部相應(yīng)的方法來(lái)封裝任務(wù)。
No.2:NSOperation的三種創(chuàng)建方式
NSInvocationOperation的使用
創(chuàng)建NSInvocationOperation對(duì)象并關(guān)聯(lián)方法吨凑,之后start捍歪。
-?(void)testNSInvocationOperation?{
//?創(chuàng)建NSInvocationOperation
NSInvocationOperation?*invocationOperation?=?[[NSInvocationOperation?alloc]?initWithTarget:self?selector:@selector(invocationOperation)?object:nil];
//?開始執(zhí)行操作
[invocationOperation?start];
}
-?(void)invocationOperation?{
NSLog(@"NSInvocationOperation包含的任務(wù),沒(méi)有加入隊(duì)列========%@",?[NSThread?currentThread]);
}
打印結(jié)果如下,得到結(jié)論:程序在主線程執(zhí)行费封,沒(méi)有開啟新線程焕妙。
這是因?yàn)镹SOperation多線程的使用需要配合隊(duì)列NSOperationQueue,后面會(huì)講到NSOperationQueue的使用弓摘。
NSInvocationOperation包含的任務(wù)焚鹊,沒(méi)有加入隊(duì)列========{number?=?1,?name?=?main}
NSBlockOperation的使用
把任務(wù)放到NSBlockOperation的block中,然后start韧献。
-?(void)testNSBlockOperation?{
//?把任務(wù)放到block中
NSBlockOperation?*blockOperation?=?[NSBlockOperation?blockOperationWithBlock:^{
NSLog(@"NSBlockOperation包含的任務(wù)末患,沒(méi)有加入隊(duì)列========%@",?[NSThread?currentThread]);
}];
[blockOperation?start];
}
執(zhí)行結(jié)果如下,可以看出:主線程執(zhí)行锤窑,沒(méi)有開啟新線程璧针。
同樣的,NSBlockOperation可以配合隊(duì)列NSOperationQueue來(lái)實(shí)現(xiàn)多線程渊啰。
NSBlockOperation包含的任務(wù)探橱,沒(méi)有加入隊(duì)列========{number?=?1,?name?=?main}
但是NSBlockOperation有一個(gè)方法addExecutionBlock:,通過(guò)這個(gè)方法可以讓NSBlockOperation實(shí)現(xiàn)多線程绘证。
-?(void)testNSBlockOperationExecution?{
NSBlockOperation?*blockOperation?=?[NSBlockOperation?blockOperationWithBlock:^{
NSLog(@"NSBlockOperation運(yùn)用addExecutionBlock主任務(wù)========%@",?[NSThread?currentThread]);
}];
[blockOperation?addExecutionBlock:^{
NSLog(@"NSBlockOperation運(yùn)用addExecutionBlock方法添加任務(wù)1========%@",?[NSThread?currentThread]);
}];
[blockOperation?addExecutionBlock:^{
NSLog(@"NSBlockOperation運(yùn)用addExecutionBlock方法添加任務(wù)2========%@",?[NSThread?currentThread]);
}];
[blockOperation?addExecutionBlock:^{
NSLog(@"NSBlockOperation運(yùn)用addExecutionBlock方法添加任務(wù)3========%@",?[NSThread?currentThread]);
}];
[blockOperation?start];
}
執(zhí)行結(jié)果如下隧膏,可以看出,NSBlockOperation創(chuàng)建時(shí)block中的任務(wù)是在主線程執(zhí)行嚷那,而運(yùn)用addExecutionBlock加入的任務(wù)是在子線程執(zhí)行的胞枕。
NSBlockOperation運(yùn)用addExecutionBlock========{number?=?1,?name?=?main}
addExecutionBlock方法添加任務(wù)1========{number?=?3,?name?=?(null)}
addExecutionBlock方法添加任務(wù)3========{number?=?5,?name?=?(null)}
addExecutionBlock方法添加任務(wù)2========{number?=?4,?name?=?(null)}
運(yùn)用繼承自NSOperation的子類
首先我們定義一個(gè)繼承自NSOperation的類,然后重寫它的main方法魏宽,之后就可以使用這個(gè)子類來(lái)進(jìn)行相關(guān)的操作了腐泻。
/*******************"WHOperation.h"*************************/
#import?@interface?WHOperation?:?NSOperation
@end
/*******************"WHOperation.m"*************************/
#import?"WHOperation.h"
@implementation?WHOperation
-?(void)main?{
for?(int?i?=?0;?i?<?3;?i++)?{
NSLog(@"NSOperation的子類WHOperation======%@",[NSThread?currentThread]);
}
}
@end
/*****************回到主控制器使用WHOperation**********************/
-?(void)testWHOperation?{
WHOperation?*operation?=?[[WHOperation?alloc]?init];
[operation?start];
}
運(yùn)行結(jié)果如下,依然是在主線程執(zhí)行队询。
SOperation的子類WHOperation======{number?=?1,?name?=?main}
NSOperation的子類WHOperation======{number?=?1,?name?=?main}
NSOperation的子類WHOperation======{number?=?1,?name?=?main}
所以派桩,NSOperation是需要配合隊(duì)列NSOperationQueue來(lái)實(shí)現(xiàn)多線程的。下面就來(lái)說(shuō)一下隊(duì)列NSOperationQueue蚌斩。
No.3:隊(duì)列NSOperationQueue
NSOperationQueue只有兩種隊(duì)列:主隊(duì)列窄坦、其他隊(duì)列。其他隊(duì)列包含了串行和并發(fā)凳寺。
主隊(duì)列的創(chuàng)建如下鸭津,主隊(duì)列上的任務(wù)是在主線程執(zhí)行的。
NSOperationQueue?*mainQueue?=?[NSOperationQueue?mainQueue];
其他隊(duì)列(非主隊(duì)列)的創(chuàng)建如下肠缨,加入到‘非隊(duì)列’中的任務(wù)默認(rèn)就是并發(fā)逆趋,開啟多線程。
NSOperationQueue?*queue?=?[[NSOperationQueue?alloc]?init];
注意:
非主隊(duì)列(其他隊(duì)列)可以實(shí)現(xiàn)串行或并行晒奕。
隊(duì)列NSOperationQueue有一個(gè)參數(shù)叫做最大并發(fā)數(shù):maxConcurrentOperationCount闻书。
maxConcurrentOperationCount默認(rèn)為-1名斟,直接并發(fā)執(zhí)行,所以加入到‘非隊(duì)列’中的任務(wù)默認(rèn)就是并發(fā)魄眉,開啟多線程砰盐。
當(dāng)maxConcurrentOperationCount為1時(shí),則表示不開線程坑律,也就是串行岩梳。
當(dāng)maxConcurrentOperationCount大于1時(shí),進(jìn)行并發(fā)執(zhí)行晃择。
系統(tǒng)對(duì)最大并發(fā)數(shù)有一個(gè)限制冀值,所以即使程序員把maxConcurrentOperationCount設(shè)置的很大,系統(tǒng)也會(huì)自動(dòng)調(diào)整宫屠。所以把最大并發(fā)數(shù)設(shè)置的很大是沒(méi)有意義的列疗。
No.4:NSOperation + NSOperationQueue
把任務(wù)加入隊(duì)列,這才是NSOperation的常規(guī)使用方式浪蹂。
addOperation添加任務(wù)到隊(duì)列
先創(chuàng)建好任務(wù)抵栈,然后運(yùn)用- (void)addOperation:(NSOperation *)op 方法來(lái)吧任務(wù)添加到隊(duì)列中,示例代碼如下:
-?(void)testOperationQueue?{
//?創(chuàng)建隊(duì)列坤次,默認(rèn)并發(fā)
NSOperationQueue?*queue?=?[[NSOperationQueue?alloc]?init];
//?創(chuàng)建操作竭讳,NSInvocationOperation
NSInvocationOperation?*invocationOperation?=?[[NSInvocationOperation?alloc]?initWithTarget:self?selector:@selector(invocationOperationAddOperation)?object:nil];
//?創(chuàng)建操作,NSBlockOperation
NSBlockOperation?*blockOperation?=?[NSBlockOperation?blockOperationWithBlock:^{
for?(int?i?=?0;?i?<?3;?i++)?{
NSLog(@"addOperation把任務(wù)添加到隊(duì)列======%@",?[NSThread?currentThread]);
}
}];
[queue?addOperation:invocationOperation];
[queue?addOperation:blockOperation];
}
-?(void)invocationOperationAddOperation?{
NSLog(@"invocationOperation===aaddOperation把任務(wù)添加到隊(duì)列====%@",?[NSThread?currentThread]);
}
運(yùn)行結(jié)果如下浙踢,可以看出,任務(wù)都是在子線程執(zhí)行的灿渴,開啟了新線程洛波!
invocationOperation===addOperation把任務(wù)添加到隊(duì)列===={number?=?4,?name?=?(null)}
addOperation把任務(wù)添加到隊(duì)列======{number?=?3,?name?=?(null)}
addOperation把任務(wù)添加到隊(duì)列======{number?=?3,?name?=?(null)}
addOperation把任務(wù)添加到隊(duì)列======{number?=?3,?name?=?(null)}
addOperationWithBlock添加任務(wù)到隊(duì)列
這是一個(gè)更方便的把任務(wù)添加到隊(duì)列的方法,直接把任務(wù)寫在block中骚露,添加到任務(wù)中蹬挤。
-?(void)testAddOperationWithBlock?{
//?創(chuàng)建隊(duì)列,默認(rèn)并發(fā)
NSOperationQueue?*queue?=?[[NSOperationQueue?alloc]?init];
//?添加操作到隊(duì)列
[queue?addOperationWithBlock:^{
for?(int?i?=?0;?i?<?3;?i++)?{
NSLog(@"addOperationWithBlock把任務(wù)添加到隊(duì)列======%@",?[NSThread?currentThread]);
}
}];
}
運(yùn)行結(jié)果如下棘幸,任務(wù)確實(shí)是在子線程中執(zhí)行焰扳。
addOperationWithBlock把任務(wù)添加到隊(duì)列======{number?=?3,?name?=?(null)}
addOperationWithBlock把任務(wù)添加到隊(duì)列======{number?=?3,?name?=?(null)}
addOperationWithBlock把任務(wù)添加到隊(duì)列======{number?=?3,?name?=?(null)}
運(yùn)用最大并發(fā)數(shù)實(shí)現(xiàn)串行
上面已經(jīng)說(shuō)過(guò),可以運(yùn)用隊(duì)列的屬性maxConcurrentOperationCount(最大并發(fā)數(shù))來(lái)實(shí)現(xiàn)串行误续,值需要把它設(shè)置為1就可以了吨悍,下面我們通過(guò)代碼驗(yàn)證一下。
-?(void)testMaxConcurrentOperationCount?{
//?創(chuàng)建隊(duì)列蹋嵌,默認(rèn)并發(fā)
NSOperationQueue?*queue?=?[[NSOperationQueue?alloc]?init];
//?最大并發(fā)數(shù)為1育瓜,串行
queue.maxConcurrentOperationCount?=?1;
//?最大并發(fā)數(shù)為2,并發(fā)
//????queue.maxConcurrentOperationCount?=?2;
//?添加操作到隊(duì)列
[queue?addOperationWithBlock:^{
for?(int?i?=?0;?i?<?3;?i++)?{
NSLog(@"addOperationWithBlock把任務(wù)添加到隊(duì)列1======%@",?[NSThread?currentThread]);
}
}];
//?添加操作到隊(duì)列
[queue?addOperationWithBlock:^{
for?(int?i?=?0;?i?<?3;?i++)?{
NSLog(@"addOperationWithBlock把任務(wù)添加到隊(duì)列2======%@",?[NSThread?currentThread]);
}
}];
//?添加操作到隊(duì)列
[queue?addOperationWithBlock:^{
for?(int?i?=?0;?i?<?3;?i++)?{
NSLog(@"addOperationWithBlock把任務(wù)添加到隊(duì)列3======%@",?[NSThread?currentThread]);
}
}];
}
運(yùn)行結(jié)果如下栽烂,當(dāng)最大并發(fā)數(shù)為1的時(shí)候躏仇,雖然開啟了線程恋脚,但是任務(wù)是順序執(zhí)行的,所以實(shí)現(xiàn)了串行焰手。
你可以嘗試把上面的最大并發(fā)數(shù)變?yōu)?糟描,會(huì)發(fā)現(xiàn)任務(wù)就變成了并發(fā)執(zhí)行。
addOperationWithBlock把任務(wù)添加到隊(duì)列1======{number?=?3,?name?=?(null)}
addOperationWithBlock把任務(wù)添加到隊(duì)列1======{number?=?3,?name?=?(null)}
addOperationWithBlock把任務(wù)添加到隊(duì)列1======{number?=?3,?name?=?(null)}
addOperationWithBlock把任務(wù)添加到隊(duì)列2======{number?=?3,?name?=?(null)}
addOperationWithBlock把任務(wù)添加到隊(duì)列2======{number?=?3,?name?=?(null)}
addOperationWithBlock把任務(wù)添加到隊(duì)列2======{number?=?3,?name?=?(null)}
addOperationWithBlock把任務(wù)添加到隊(duì)列3======{number?=?3,?name?=?(null)}
addOperationWithBlock把任務(wù)添加到隊(duì)列3======{number?=?3,?name?=?(null)}
addOperationWithBlock把任務(wù)添加到隊(duì)列3======{number?=?3,?name?=?(null)}
No.5:NSOperation的其他操作
取消隊(duì)列NSOperationQueue的所有操作书妻,NSOperationQueue對(duì)象方法
-?(void)cancelAllOperations
取消NSOperation的某個(gè)操作船响,NSOperation對(duì)象方法
-?(void)cancel
使隊(duì)列暫停或繼續(xù)
//?暫停隊(duì)列
[queue?setSuspended:YES];
判斷隊(duì)列是否暫停
-?(BOOL)isSuspended
暫停和取消不是立刻取消當(dāng)前操作驻子,而是等當(dāng)前的操作執(zhí)行完之后不再進(jìn)行新的操作灿意。
No.6:NSOperation的操作依賴
NSOperation有一個(gè)非常好用的方法,就是操作依賴崇呵$途纾可以從字面意思理解:某一個(gè)操作(operation2)依賴于另一個(gè)操作(operation1),只有當(dāng)operation1執(zhí)行完畢域慷,才能執(zhí)行operation2荒辕,這時(shí),就是操作依賴大顯身手的時(shí)候了犹褒。
-?(void)testAddDependency?{
//?并發(fā)隊(duì)列
NSOperationQueue?*queue?=?[[NSOperationQueue?alloc]?init];
//?操作1
NSBlockOperation?*operation1?=?[NSBlockOperation?blockOperationWithBlock:^{
for?(int?i?=?0;?i?<?3;?i++)?{
NSLog(@"operation1======%@",?[NSThread??currentThread]);
}
}];
//?操作2
NSBlockOperation?*operation2?=?[NSBlockOperation?blockOperationWithBlock:^{
NSLog(@"****operation2依賴于operation1抵窒,只有當(dāng)operation1執(zhí)行完畢,operation2才會(huì)執(zhí)行****");
for?(int?i?=?0;?i?<?3;?i++)?{
NSLog(@"operation2======%@",?[NSThread??currentThread]);
}
}];
//?使操作2依賴于操作1
[operation2?addDependency:operation1];
//?把操作加入隊(duì)列
[queue?addOperation:operation1];
[queue?addOperation:operation2];
}
運(yùn)行結(jié)果如下叠骑,操作2總是在操作1之后執(zhí)行李皇,成功驗(yàn)證了上面的說(shuō)法。
operation1======{number?=?3,?name?=?(null)}
operation1======{number?=?3,?name?=?(null)}
operation1======{number?=?3,?name?=?(null)}
****operation2依賴于operation1宙枷,只有當(dāng)operation1執(zhí)行完畢掉房,operation2才會(huì)執(zhí)行****
operation2======{number?=?4,?name?=?(null)}
operation2======{number?=?4,?name?=?(null)}
operation2======{number?=?4,?name?=?(null)}
后記
本文所述的示例代碼在這里:WHMultiThreadDemo
推薦簡(jiǎn)單又好用的分類集合:WHKit
github地址:https://github.com/remember17