一. 線程的創(chuàng)建
-
創(chuàng)建線程并且手動開啟, 同時在這條線程執(zhí)行selector的任務
// 1. 創(chuàng)建線程對象 NSThread *thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(task1:) object:@"創(chuàng)建線程方式1"]; // 2. 為線程添加標識 [thread1 setName:@"thread1"]; // 3. 設置線程的優(yōu)先級(優(yōu)先級高的線程,會優(yōu)先執(zhí)行) [thread1 setThreadPriority:0.5]; // 4. 開始執(zhí)行 [thread1 start];
-
分離出一條子線程, 同時在這條線程執(zhí)行selector的任務
// 2. 分離子線程 [NSThread detachNewThreadSelector:@selector(task1:) toTarget:self withObject:@"創(chuàng)建線程方式2"];
-
開啟一條后臺線程, 同時在這條線程執(zhí)行selector的任務(注意, 該方法是由當前控制器調(diào)用的)
// 3. 創(chuàng)建后臺線程 [self performSelectorInBackground:@selector(task1:) withObject:@"創(chuàng)建線程方式3"];
-
自定義線程
- 創(chuàng)建一個NSThread的子類
- 在類中重寫
-(void)main
方法, 將這個線程需要執(zhí)行的任務, 在這個方法中實現(xiàn) - 使用
alloc/init
創(chuàng)建這個自定義線程, 當使用start
執(zhí)行這個線程的時候, 系統(tǒng)會自動調(diào)用main方法來執(zhí)行其中的任務
-
創(chuàng)建線程方法的優(yōu)缺點
- 手動創(chuàng)建線程, 可以獲取線程對象, 可以對這個線程對象進行詳細的設置, 如標識/優(yōu)先級等
- 而采用分離子線程或者開啟后臺線程的方法, 使用方法簡單, 可以直接開啟一個子線程去執(zhí)行耗時任務, 但是由于無法獲取到這個線程對象, 因此無法對其進行設置
二. 線程的生命周期
- 線程的五種狀態(tài)
- New: 一個剛剛新建出來的線程, 此時沒有任何任務在執(zhí)行當中
- Runable: 處于可調(diào)度線程池的線程, 只有在線程池中的線程, 才可以被CPU調(diào)度
- Running: 使用了
-(void)start
方法, 被激活工作, 且正在被CPU調(diào)度的線程, 一個線程如果還未完成它所有的任務, 那么他就會一直在Runable和Running兩個狀態(tài)中相互切換 - Blocked: 調(diào)用了
[NSThread sleepForTimeInterval]
方法或等待同步鎖的放行時的狀態(tài), 此時線程暫時被移除可調(diào)度線程池, 但是并沒有被銷毀掉, 當sleep的時間到了或同步鎖放行, 就會恢復Runable狀態(tài) - Dead: 當一條線程的任務執(zhí)行完畢/異趁倘常或強制退出
exit方法
時, 這條線程就會被銷毀, 銷毀后的線程與對象一樣, 不能再次使用
三. 線程安全
- 多線程應用時的安全隱患
-
當多個線程, 出現(xiàn)訪問同一塊資源的時候, 這樣會導致在存取的過程中, 被取資源中保存的值由于可能同時在修改, 導致值發(fā)生錯誤
// 例子: 三個售票員同時售票 - (void)sale { while (1) { @synchronized(self) { // 如果這里不增加線程鎖, 就會導致存取錯誤 // 1. 檢查余票 int count = self.count; if (count > 0) { // 演示耗時操作 for (int i = 0; i < 100000; i++) { // 如果線程的任務中有耗時操作各聘,就有可能引起共同訪問一塊資源導致數(shù)據(jù)錯誤 // 因此這時需要加入線程鎖 } // 訪問屬性 self.count = count - 1; NSLog(@"%@賣出去了一張票蹦渣,還剩%d張", [NSThread currentThread], self.count); } else { NSLog(@"票已經(jīng)賣完"); [NSThread exit]; } } } }
解決方法: 在線程要訪問資源之前, 增加一個互斥鎖
@synchronized(鎖對象){ 需要鎖定的代碼 }
-
互斥鎖:
- 通常鎖對象, 是全局唯一的一個對象, 一般使用NSObject作為對象的類型
- 注意點
- 一份代碼, 只能使用同一把互斥鎖
- 鎖對象本身有兩種狀態(tài): 打開/關(guān)閉
- 當互斥鎖關(guān)閉的時候, 隊列中的線程就會進入Blocked狀態(tài), 直到互斥鎖打開, 該線程才會進入運行狀態(tài)
- 鎖對象可以使用當前的控制器, 也就是self
- 加鎖的位置需要注意, 位置不同, 執(zhí)行的代碼也會不同
- 加鎖的前提條件: 只有出現(xiàn)多個線程同時訪問同一塊資源的時候, 才需要使用互斥鎖進行保護
- 互斥鎖的使用, 會增加額外的資源消耗, 所以能不使用就不要使用, 盡量避免出現(xiàn)多線程搶奪資源
- 互斥鎖會造成線程同步: 每個線程會在鎖的外面排隊執(zhí)行任務
- 但是隊列中的線程是異步的: 到底是哪條線程訪問鎖內(nèi)的資源, 順序是不確定的
-
四. 原子和非原子性
-
atomic:
- 屬性為原子性, 該關(guān)鍵字會為setter方法增加一個互斥鎖, 因此它是線程安全的
- 但是他會消耗大量的內(nèi)存資源, 并且在我們的開發(fā)過程中, 很少發(fā)生多線程訪問同一個屬性的情況, 因此基本不會使用這個關(guān)鍵字
-
nonatomic:
- 屬性為非原子性, 此關(guān)鍵字不會為setter方法增加互斥鎖, 因此是非線程安全的, 適合內(nèi)存小的移動設備, 即iOS開發(fā)中屬性主要使用的關(guān)鍵字,
- 因此, 在日常開發(fā)中, 盡量要避免多線程搶奪資源的情況, 加鎖/資源搶奪的業(yè)務邏輯通常會交由服務器端來處理, 即每次只能接收/發(fā)送一份網(wǎng)絡請求
五. 線程間的通信
- 需求:
開啟條子線程, 并且下載一張圖片
將獲得到的圖片, 在主線程中設置給imageView
-
注意點: 遵循耗時操作交給子線程, UI操作回到主線程
// 0. 創(chuàng)建子線程執(zhí)行下載任務線程 NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(download) object:nil]; [thread start]; - (void)download { // 計算時間(絕對時間) CFTimeInterval start = CFAbsoluteTimeGetCurrent(); // 1. 創(chuàng)建url路徑 NSURL *url = [NSURL URLWithString:@"http://dimg07.c-ctrip.com/images/tg/946/212/497/81b56770ed4544a6a8a1125fb381753d_C_640_640.jpg"]; // 2. 將圖片轉(zhuǎn)換為二進制數(shù)據(jù) NSData *data = [NSData dataWithContentsOfURL:url]; // 3. 轉(zhuǎn)換格式(二進制 -> UIImage) UIImage *image = [UIImage imageWithData:data]; // 4. 回主線程設置圖片 // [self performSelectorOnMainThread:@selector(showImage:) withObject:image waitUntilDone:NO]; // 該方法可以直接使用主線程去執(zhí)行任務 [self performSelector:@selector(showImage:) onThread:[NSThread mainThread] withObject:image waitUntilDone:NO]; // 獲取絕對時間 CFTimeInterval end = CFAbsoluteTimeGetCurrent(); NSLog(@"%f", end - start); }