第二部分 NSThread
先看一段API文檔的描述
An NSThread object controls a thread of execution. Use this class when you want to have an Objective-C method run in its own thread of execution. Threads are especially useful when you need to perform a lengthy task, but don’t want it to block the execution of the rest of the application. In particular, you can use threads to avoid blocking the main thread of the application, which handles user interface and event-related actions. Threads can also be used to divide a large job into several smaller jobs, which can lead to performance increases on multi-core computers.
大概的意思是:一個NSThread對象管理一個線程的執行。當你想要将一個Objective-C方法運作在它自己獨立的線程中,可以使用這個類。當你想執行一個比較耗時(冗長)的操作而又不想阻塞程式其他部分的運作狀态時,線程是特别有用的。尤其是你可以使用線程來避免阻塞主線程處理使用者界面以及和事件相關的活動。線程可以将待處理任務分割成小任務以提高多核計算機的性能。
##一、NSThread的使用
###1.線程的建立
方式一:
/ * 建立并啟動線程
*
* 參數1要執行的方法
* 參數2提供selector的對象,通常是self
* 參數3傳遞給selector的參數
*/
[NSThread detachNewThreadSelector:(nonnull SEL)> toTarget:(nonnull id) withObject:(nullable id)]
方式二:
//參數一:提供selector的對象,通常是self,參數2:要執行的方法,參數3:傳遞給selector的參數(如果selector方法不帶參數,就使用nil)
NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(doSomething) object:nil];
方式三:
//隐式建立并啟動線程,第一個參數為調用的方法,第二個參數為傳給selector方法的參數
- (void)performSelectorInBackground:(SEL)aSelector
withObject:(id)arg
NSThread對象的常見屬性
//隻讀屬性,線程是否在執行
thread.isExecuting;
//隻讀屬性,線程是否被取消
thread.isCancelled;
//隻讀屬性,線程是否完成
thread.isFinished;
//是否是主線程
thread.isMainThread;
//線程的優先級,取值範圍0.0到1.0,預設優先級0.5,1.0表示最高優 //先級,優先級高,CPU排程的頻率高
thread.threadPriority;
//線程的堆棧大小,線程執行前堆棧大小為512K,線程完成後堆棧大小 為0K
//注意:線程執行完畢後,由于記憶體空間被釋放,不能再次啟動
thread.stackSize;
NSThread對象的方法
//線程開始,線程加入線程池等待CPU排程(并非真正開始執行,隻是通常等待時間都非常短,看不出效果)
[thread start];
if(!thread.isCancelled){//在執行之前需要先确認線程狀态,如果已經取消就直接傳回
[thread cancel]; //通知線程取消,可以在外不終止線程執行
}else{
return;
}
###2.NSThread的類方法
類方法都用線上程内部,也就是說類方法作用于包含本行類方法的線程。
<1>目前線程,在開發中常用于調試,适用于所有多線程計數,傳回一個線程号碼
//number == 1 表示主線程,number != 1表示背景線程
int number = [NSThread currentThread];
<2>阻塞方法
//休眠到指定時間
[NSThread sleepUntilDate:[NSDate date]];
//休眠指定時長
[NSThread sleepForTimeInterval:4.5];
<3>其他類方法
//退出線程
[NSThread exit];
//目前線程是否為主線程
[NSThread isMainThread];
//是否多線程
[NSThread isMultiThreaded];
//傳回主線程的對象
NSThread *mainThread = [NSThread mainThread];
###3.線程的狀态

]
<1>建立:執行個體化對象
<2>就緒:向線程對象發送 start 消息,線程對象被加入“可排程線程池”等待 CPU 排程;detach 方法和 performSelectorInBackground 方法會直接執行個體化一個線程對象并加入“可排程線程池”
<3>運作:CPU 負責排程“可排程線程池”中線程的執行,線程執行完成之前,狀态可能會在“就緒”和“運作”之間來回切換,“就緒”和“運作”之間的狀态變化由 CPU 負責,程式員不能幹預
<4>阻塞:當滿足某個預定條件時,可以使用休眠或鎖阻塞線程執行,影響的方法有:sleepForTimeInterval,sleepUntilDate,@synchronized(self)x線程鎖;
線程對象進入阻塞狀态後,會被從“可排程線程池”中移出,CPU 不再排程
<5>死亡
死亡方式
正常死亡:線程執行完畢
非正常死亡:線程内死亡--->[NSThread exit]:強行中止後,後續代碼都不會在執行
線程外死亡:[threadObj cancel]--->通知線程對象取消,線上程執行方法中需要增加 isCancelled 判斷,如果 isCancelled == YES,直接傳回
死亡後線程對象的 isFinished 屬性為 YES;如果是發送 calcel 消息,線程對象的 isCancelled 屬性為YES;死亡後 stackSize == 0,記憶體空間被釋放。
###4.多線程的安全問題
多個線程通路同一塊資源進行讀寫,如果不加控制随意通路容易産生資料錯亂進而引發資料安全問題。為了解決這一問題,就有了加鎖的概念。加鎖的原理就是當有一個線程正在通路資源進行寫的時候,不允許其他線程再通路該資源,隻有當該線程通路結束後,其他線程才能按順序進行通路。對于讀取資料,有些程式設計是允許多線程同時讀的,有些不允許。
解決多線程安全問題
<1>互斥鎖
// 注意:鎖定1份代碼隻用1把鎖,用多把鎖是無效的
@synchronized(鎖對象) { // 需要鎖定的代碼 }
使用互斥鎖,在同一個時間,隻允許一條線程執行鎖中的代碼.因為互斥鎖的代價非常昂貴,是以鎖定的代碼範圍應該盡可能小,隻要鎖住資源讀寫部分的代碼即可。使用互斥鎖也會影響并發的目的。
<2>原子屬性
@property (strong, nonatomic) UIWindow *window;
atomic:能夠實作“單寫多讀”的資料保護,同一時間隻允許一個線程修改屬性值,但是允許多個線程同時讀取屬性值,在多線程讀取資料時,有可能出現“髒”資料 - 讀取的資料可能會不正确。原子屬性是預設屬性,如果不需要考慮線程安全,要指定 nonatomic。
atomic(原子屬性)在setter方法内部加了一把自旋鎖
nonatomic(非原子屬性)下,set和get方法都不會加鎖,消耗資源小适合記憶體小的移動裝置
UIKit中幾乎所有控件都不是線程安全的,是以需要在主線程上更新UI
原子屬性内部使用的 自旋鎖
自旋鎖和互斥鎖的差別
共同點: 都可以鎖定一段代碼。 同一時間, 隻有線程能夠執行這段鎖定的代碼
差別:互斥鎖,在鎖定的時候,其他線程會睡眠,等待條件滿足,再喚醒
自旋鎖,在鎖定的時候, 其他的線程會做死循環,一直等待這條件滿足,一旦條件滿足,立馬去執行,少了一個喚醒過程
// 在主線程更新UI,有什麼好處?
- 隻在主線程更新UI,就不會出現多個線程同時改變 同一個UI控件
- 主線程的優先級最高。也就意味UI的更新優先級高。 會讓使用者感覺很流暢
開發建議
1.所有屬性都聲明為nonatomic
2.盡量避免多線程搶奪同一塊資源
3.盡量将加鎖、資源搶奪的業務邏輯交給伺服器端處理,減小移動用戶端的壓力
###5.自動釋放池和運作循環
<1>運作循環
作用:保證程式不退出,堅挺所有事件,例如:手勢觸摸,網絡加載等
特性:沒有事件時,會休眠(省電),一旦監聽到事件,會立即響應,每一個線程都有一個 runloop,但是隻有主線程的 runloop 會預設啟動。
<2>自動釋放池
工作原理:自動釋放池被銷毀或耗盡時會向池中所有對象發送 release 消息,釋放所有 autorelease 的對象!
建立和銷毀:每一次運作循環啟動後會建立自動釋放池;程式執行過程中,所有 autorelease 對象在出了作用域之後,會被添加到最近建立的自動釋放池中;運作循環結束前,會釋放自動釋放池。
自動釋放池在ARC中同樣需要。
工作原理圖:
常見面試題:
int largeNumber = 2 * 1024 * 1024;
// 問題:(1)以下代碼是否存在問題?(2)如果有,怎麼修改?
for (int i = 0; i < largeNumber; i++) {
@autoreleasepool {
NSString *str = [NSString stringWithFormat:@"Hello "];
str = [str uppercaseString];
str = [str stringByAppendingString:@" - World"];
}
}
網上的解決辦法:
1)@autoreleasepool 放在外面,保證循環之後釋放循環中的自動釋放對象
2)@autoreleasepool 放在内部,每一次循環之後,都傾倒一次自動釋放池,記憶體管理是最好的,但是性能不好!
###6.線程通信(方法繼承自NSObject)
//在主線程上執行操作,例如給UIImageVIew設定圖檔
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait
//在指定線程上執行操作
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thread withObject:(id)arg waitUntilDone:(BOOL)wai