01-多線程

一、多線程基礎(chǔ)

基本概念

  • 進(jìn)程
    進(jìn)程是指在系統(tǒng)中正在運(yùn)行的一個(gè)應(yīng)用程序
    每個(gè)進(jìn)程之間是獨(dú)立的篙顺,每個(gè)進(jìn)程均運(yùn)行在其專(zhuān)用且受保護(hù)的內(nèi)存空間內(nèi)
    通過(guò) 活動(dòng)監(jiān)視器 可以查看 Mac 系統(tǒng)中所開(kāi)啟的進(jìn)程

  • 線程
    進(jìn)程要想執(zhí)行任務(wù)面褐,必須得有線程拌禾,進(jìn)程至少要有一條線程
    程序啟動(dòng)會(huì)默認(rèn)開(kāi)啟一條線程,這條線程被稱為主線程或UI 線程
    線程是進(jìn)程的基本執(zhí)行單元展哭,進(jìn)程的所有任務(wù)都在線程中執(zhí)行

  • 多線程

    • 一個(gè)進(jìn)程中可以開(kāi)啟多條線程湃窍,每條線程可以同時(shí)執(zhí)行不同的任務(wù)
      進(jìn)程 -> 公司
      線程 -> 員工
      主線程 -> 老板(第一個(gè)員工)
    • 多線程技術(shù)可以提高程序的執(zhí)行效率
  • 多線程原理
    • 同一時(shí)間,CPU只能處理一條線程匪傍,只有一條線程在執(zhí)行
    • 多線程同時(shí)執(zhí)行您市,其實(shí)是CPU快速地在多條線程之間切換
    • 如果CPU調(diào)度線程的時(shí)間足夠快,就造成了多線程并發(fā)執(zhí)行的假象
    • 如果線程非常多析恢,會(huì)在多條線程之間來(lái)回切換墨坚,消耗大量的 CPU 資源
      • 每個(gè)線程被調(diào)度的次數(shù)會(huì)降低
      • 線程的執(zhí)行效率會(huì)下降

iOS 8.0 主線程的默認(rèn)堆棧大小也是 512K

  • 多線程優(yōu)缺點(diǎn)

    • 優(yōu)點(diǎn)
      能適當(dāng)提高資源利用率(CPU、內(nèi)存利用率)
      能適當(dāng)提高程序的執(zhí)行效率

    • 缺點(diǎn)
      開(kāi)啟線程需要占用一定的內(nèi)存空間映挂,如果開(kāi)啟大量的線程泽篮,會(huì)占用大量的內(nèi)存空間,降低程序的性能
      線程越多柑船,CPU在調(diào)度線程上的開(kāi)銷(xiāo)就越大
      程序設(shè)計(jì)更加復(fù)雜:比如線程之間的通信帽撑、多線程的數(shù)據(jù)共享

  • 主線程

    • 程序啟動(dòng)創(chuàng)建的線程,被稱為主線程或UI線程
    • 主線程的作用
      • 顯示/刷新 UI 界面
      • 處理 UI 事件:點(diǎn)擊鞍时、滾動(dòng)亏拉、拖拽等事件
    • 注意:要將耗時(shí)操作放在后臺(tái)線程執(zhí)行,否則會(huì)影響 UI 的流暢度逆巍,破壞用戶體驗(yàn)
    • 所有網(wǎng)絡(luò)訪問(wèn)都是耗時(shí)操作及塘!
  • iOS中多線程的實(shí)現(xiàn)方案

二、耗時(shí)操作示例

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    // 在主線程執(zhí)行
    [self longOperation];
    // 在后臺(tái)線程執(zhí)行
//[self performSelectorInBackground:@selector(longOperation) withObject:nil];
}
// 耗時(shí)操作
- (void)longOperation{
    NSLog(@"start = %@",[NSThread currentThread]);
    int largeNumber = 1000 * 1000 * 10;
    for (int index = 0; index < largeNumber; index ++) {
        // 棧區(qū)
//        int num = 10;
        
        // 靜態(tài)區(qū)/常量區(qū)
//        NSString *str = @"hello world";
        // 在 oc 中,只要使用 @"" 定義的字符串,如果內(nèi)容一樣,無(wú)論在哪里,地址都一樣锐极。
        
        // stringWithFormat:生成的字符串是保存在堆區(qū)的
        // 棧區(qū)操作效率要比堆區(qū)快
        // 程序員只需要管理堆區(qū)的內(nèi)存
        NSString *str = [NSString stringWithFormat:@"hello world - %d",index];
    }
    NSLog(@"over");
}

[NSThread currentThread] 是獲取當(dāng)前線程的對(duì)象笙僚。
最常用的就是根據(jù) number 判斷是否主線程。
number == 1 就是主線程 灵再。
number != 1 就是后臺(tái)線程肋层。
不要糾結(jié) number 的具體數(shù)字亿笤,由 CPU 決定。
演示因耗時(shí)操作導(dǎo)致按鈕和 UITextView 不能繼續(xù)響應(yīng)用戶點(diǎn)擊和拖拽事件栋猖。
學(xué)習(xí)多線程的目的:就是將耗時(shí)操作放到后臺(tái)去執(zhí)行净薛。

三、pthread

  • 1蒲拉、簡(jiǎn)介

    • pthread 是 POSIX 多線程開(kāi)發(fā)框架肃拜,由于是跨平臺(tái)的 C 語(yǔ)言框架,在蘋(píng)果的頭文件中并沒(méi)有詳細(xì)的注釋雌团。
    • 要查閱 pthread 有關(guān)資料爆班,可以訪問(wèn) http://baike.baidu.com
  • 2辱姨、導(dǎo)入頭文件

#import <pthread.h>
  • 3、pthread示例
// 創(chuàng)建線程戚嗅,并且在線程中執(zhí)行 demo 函數(shù)
- (void)pthreadDemo {
    /**
     參數(shù):
     1> 指向線程標(biāo)識(shí)符的指針雨涛,C 語(yǔ)言中類(lèi)型的結(jié)尾通常 _t/Ref,而且不需要使用 *
     -- 在 C 語(yǔ)言中懦胞,沒(méi)有對(duì)象的概念替久,對(duì)象是以結(jié)構(gòu)體的方式來(lái)實(shí)現(xiàn)的。
     ---- 通常躏尉,在 C 語(yǔ)言框架中蚯根,對(duì)象類(lèi)型以 _t/Ref 結(jié)尾,而且聲明時(shí)不需要使用 *
     2> 用來(lái)設(shè)置線程屬性
     3> 線程運(yùn)行函數(shù)的起始地址
     --- 在 C 語(yǔ)言中胀糜,函數(shù)名就是指向函數(shù)在內(nèi)存中的起始地址
     --- 類(lèi)似的一個(gè)概念:數(shù)組名是指向數(shù)組第一個(gè)元素的地址颅拦。
     在 C 語(yǔ)言中, void *(指向任何地址的指針) 和 OC 中的 id(萬(wàn)能指針) 是等價(jià)的
     參數(shù)3的格式: void * (*) (void *)
     返回值 (*函數(shù)指針) (參數(shù))
     4> 運(yùn)行函數(shù)的參數(shù)
     
     返回值:
     - 若線程創(chuàng)建成功教藻,則返回0
     - 若線程創(chuàng)建失敗距帅,則返回出錯(cuò)編號(hào)
     */
    pthread_t threadId = NULL;
    NSString *str = @"Hello Pthread";
    int result = pthread_create(&threadId, NULL, demo, (__bridge void *)(str));
    if (result == 0) {
        NSLog(@"創(chuàng)建線程 OK");
    } else {
        NSLog(@"創(chuàng)建線程失敗 %d", result);
    }
}
// 后臺(tái)線程調(diào)用函數(shù)
void *demo(void *params) {
    NSString *str = (__bridge NSString *)(params);
    NSLog(@"%@ - %@", [NSThread currentThread], str);
    return NULL;
}
  • 4、小結(jié)
    • 在 C 語(yǔ)言中括堤,沒(méi)有對(duì)象的概念碌秸,對(duì)象是以結(jié)構(gòu)體的方式來(lái)實(shí)現(xiàn)的。
    • 通常悄窃,在 C 語(yǔ)言框架中讥电,對(duì)象類(lèi)型以 _t/Ref 結(jié)尾,而且聲明時(shí)不需要使用 *
    • C 語(yǔ)言中的 void * 和 OC 中的 id 是等價(jià)的
    • 內(nèi)存管理
      • 在 OC 中轧抗,如果是 ARC 開(kāi)發(fā)恩敌,編譯器會(huì)在編譯時(shí),會(huì)根據(jù)代碼結(jié)構(gòu)鸦致,自動(dòng)添加retain/release/autorelease
      • 但是潮剪,ARC 只負(fù)責(zé)管理 OC 部分的內(nèi)存管理涣楷,而不負(fù)責(zé) C 語(yǔ)言 代碼的內(nèi)存管理
      • 因此,開(kāi)發(fā)過(guò)程中抗碰,如果使用的 C 語(yǔ)言框架出現(xiàn) retain/create/copy/new 等字樣的函數(shù)狮斗,大多都需要 release,否則會(huì)出現(xiàn)內(nèi)存泄漏
    • 在混合開(kāi)發(fā)時(shí)弧蝇,如果在 C 和 OC 之間傳遞數(shù)據(jù)碳褒,需要使用 __bridge 進(jìn)行橋接,橋接的目的就是為了告訴編譯器如何管理內(nèi)存看疗。__bridge 表示什么特殊處理都不做沙峻。
    • 橋接的添加可以借助 Xcode 的輔助功能添加。
    • MRC 中不需要使用橋接两芳。因?yàn)镸RC的內(nèi)存管理需要程序員手動(dòng)管理摔寨。

四、NSThread

4.1怖辆、創(chuàng)建線程的方式(3種)

準(zhǔn)備在后臺(tái)線程調(diào)用的方法 longOperation:

- (void)longOperation:(id)obj {
    NSLog(@"%@ - %@", [NSThread currentThread], obj);
}
4.1.1是复、alloc / init - start
- (void)threadDemo1 {
    // 創(chuàng)建線程
    NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(download) object:@"Alloc"];
    // 開(kāi)啟線程
    [thread start];
    NSLog(@"after %@", [NSThread currentThread]);
}
- (void)download {
    
}
  • 小結(jié)
    1.[thread start];執(zhí)行后竖螃,會(huì)在另外一個(gè)線程執(zhí)行 longOperation: 方法
    2.在 OC 中淑廊,任何一個(gè)方法的代碼都是從上向下順序執(zhí)行的
    3.同一個(gè)方法內(nèi)的代碼,都是在相同線程執(zhí)行的(block除外)
4.1.2特咆、detachNewThreadSelector
- (void)threadDemo2 {
    NSLog(@"before %@", [NSThread currentThread]);
    [NSThread detachNewThreadSelector:@selector(longOperation:) toTarget:self withObject:@"DETACH"];
    NSLog(@"after %@", [NSThreadcurrentThread]);
}
或:
- (void)threadDemo2 {
    // 在同一個(gè)方法中,代碼是從上往下執(zhí)行的.
    // 同一個(gè)線程中,代碼也是從上往下執(zhí)行的(block除外)
    // 多線程開(kāi)發(fā),不要相信第一次執(zhí)行的結(jié)果
    NSLog(@"start = %@",[NSThread currentThread]);
    // detach:分離
    // 創(chuàng)建線程,并啟動(dòng)線程.
    //    [self download:@"xxx"];
    // 創(chuàng)建線程本身是在主線程創(chuàng)建,
    [NSThread detachNewThreadSelector:@selector(download:) toTarget:self.person withObject:@"detach"];
    NSLog(@"over");
}
  • 小結(jié)
    detachNewThreadSelector 類(lèi)方法不需要啟動(dòng)季惩,會(huì)自動(dòng)創(chuàng)建線程并執(zhí)行 @selector 方法。
4.1.3腻格、分類(lèi)方法:performSelectorInBackground
- (void)threadDemo3 {
    NSLog(@"before %@", [NSThread currentThread]);
    [self performSelectorInBackground:@selector(longOperation) withObject:@"PERFORM"];
    NSLog(@"after %@", [NSThread currentThread]);
}

- (void)longOperation {

}
  • 小結(jié)
    1.performSelectorInBackground 是 NSObject 的分類(lèi)方法画拾。
    2.會(huì)自動(dòng)在后臺(tái)線程執(zhí)行 @selector 方法。
    3.沒(méi)有 thread 字眼菜职,隱式創(chuàng)建并啟動(dòng)線程碾阁。
    4.所有 NSObject 都可以使用此方法,在其他線程執(zhí)行方法
4.1.4些楣、創(chuàng)建和啟動(dòng)線程
  • 一個(gè)NSThread對(duì)象就代表一條線程

  • 創(chuàng)建脂凶、啟動(dòng)線程

NSThread*thread = [[NSThreadalloc] initWithTarget:selfselector:@selector(run) object:nil];
[thread start];
// 線程一啟動(dòng),就會(huì)在線程thread中執(zhí)行self的run方法
  • 主線程相關(guān)用法
+ (NSThread*)mainThread;// 獲得主線程
- (BOOL)isMainThread;// 是否為主線程
+ (BOOL)isMainThread;// 是否為主線程
4.1.5愁茁、其他用法

獲得當(dāng)前線程

NSThread *current = [NSThreadcurrentThread];

線程的調(diào)度優(yōu)先級(jí)

+ (double)threadPriority;
+ (BOOL)setThreadPriority:(double)p;
- (double)threadPriority;
- (BOOL)setThreadPriority:(double)p;
調(diào)度優(yōu)先級(jí)的取值范圍是0.0~1.0蚕钦,默認(rèn)0.5,值越大鹅很,優(yōu)先級(jí)越高

線程的名字

- (void)setName:(NSString*)name;
- (NSString*)name;
4.1.6嘶居、其他創(chuàng)建線程方式

創(chuàng)建線程后自動(dòng)啟動(dòng)線程

[NSThreaddetachNewThreadSelector:@selector(run) toTarget:selfwithObject:nil];

隱式創(chuàng)建并啟動(dòng)線程

[selfperformSelectorInBackground:@selector(run) withObject:nil];
  • 上述2種創(chuàng)建線程方式的優(yōu)缺點(diǎn)
    優(yōu)點(diǎn):簡(jiǎn)單快捷
    缺點(diǎn):無(wú)法對(duì)線程進(jìn)行更詳細(xì)的設(shè)置

4.2、NSThread的Target

NSThread 的實(shí)例化方法中的 target 指的是開(kāi)啟線程后,在線程中執(zhí)行 哪一個(gè)對(duì)象 的 @selector 方法邮屁。

4.3整袁、示例

準(zhǔn)備對(duì)象

@interface Person : NSObject
@property (nonatomic, copy) NSString *name;
@end

@implementation Person
+ (instancetype)personWithDict:(NSDictionary *)dict {
    id obj = [[self alloc] init];
    [obj setValuesForKeysWithDictionary:dict];
    return obj;
}
- (void)longOperation:(id)obj {
    NSLog(@"%@ - %@ - %@", [NSThreadcurrentThread], self.name, obj);
}
@end

定義屬性 :

@property (nonatomic, strong) Person *person;

懶加載

- (Person *)person {
    if (_person == nil) {
        _person = [Person personWithDict:@{@"name": @"zhangsan"}];
    }
    return _person;
}
三種線程調(diào)度方法

1、alloc / init

NSThread *thread = [[NSThread alloc] initWithTarget:self.person(調(diào)用者) selector:@selector(longOperation:)(調(diào)用者調(diào)用此方法) object(參數(shù)):   @"THREAD"];
[thread start];

2佑吝、Detach (分離)

[NSThread  detachNewThreadSelector:@selector(longOperation:) toTarget:self.person withObject:@"DETACH"];

3坐昙、分類(lèi)方法(創(chuàng)建一個(gè)后臺(tái)子線程并運(yùn)行)

[self.person performSelectorInBackground:@selector(longOperation:) withObject:@"PERFORM"];
  • 小結(jié)
    通過(guò)指定不同的 target 會(huì)在后臺(tái)線程執(zhí)行該對(duì)象的 @selector 方法
    提示:不要看見(jiàn) target 就寫(xiě) self
    performSelectorInBackground 可以讓方便地在后臺(tái)線程執(zhí)行任意 NSObject 對(duì)象的方法
4.4、線程狀態(tài)
  • 新建
    實(shí)例化線程對(duì)象

  • 就緒
    向線程對(duì)象發(fā)送 start 消息芋忿,線程對(duì)象被加入 可調(diào)度線程池 等待 CPU 調(diào)度
    detach 方法和 performSelectorInBackground 方法會(huì)直接實(shí)例化一個(gè)線程對(duì)象并加入 可調(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í)行。
      當(dāng)滿足某個(gè)條件后殉了,在主線程中止線程對(duì)象开仰。

  • [NSThread exit];
    一旦強(qiáng)行終止線程,后續(xù)的所有代碼都不會(huì)被執(zhí)行
    注意:在終止線程之前薪铜,應(yīng)該注意釋放之前分配的對(duì)象抖所!

控制線程狀態(tài)

啟動(dòng)線程

- (void)start;
// 進(jìn)入就緒狀態(tài) ->運(yùn)行狀態(tài)。當(dāng)線程任務(wù)執(zhí)行完畢痕囱,自動(dòng)進(jìn)入死亡狀態(tài)

阻塞(暫停)線程

+ (void)sleepUntilDate:(NSDate*)date;
+ (void)sleepForTimeInterval:(NSTimeInterval)ti;
// 進(jìn)入阻塞狀態(tài)

強(qiáng)制停止線程

+ (void)exit;
// 進(jìn)入死亡狀態(tài)

注意:一旦線程停止(死亡)了,就不能再次開(kāi)啟任務(wù)

4.4.1暴匠、示例代碼
- (void)statusDemo {
     NSLog(@"先睡會(huì)");
    [NSThread sleepForTimeInterval:1.0];
    for (int i = 0; i < 20; i++) {
             if (i == 9) {
            NSLog(@"再睡會(huì)");
            [NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:1.0]];
     }
    NSLog(@"%d %@", i, [NSThreadcurrentThread]);
   if (i == 16) {
            NSLog(@"88");
            // 終止線程之前鞍恢,需要記住釋放資源
            [NSThread exit];
        }
    }
    NSLog(@"over");
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    // 注意不要在主線程上調(diào)用 exit 方法
    //    [NSThread exit];

    // 實(shí)例化線程對(duì)象(新建)
    NSThread *t = [[NSThread alloc] initWithTarget:self selector:@selector(statusDemo)   object:nil];

   // 線程就緒(被添加到可調(diào)度線程池中)
    [t start];
}
4.4.2、取消線程
- (void)download{
    NSThread *thread = [NSThread currentThread];
    // 判斷線程是否取消
    if (thread.isCancelled) {
        NSLog(@"1...888");
        return;
    }
    // 睡0.2秒
    [NSThread sleepForTimeInterval:0.2];
    NSLog(@"睡會(huì)");
    for (int index = 0; index < 10; index ++) {
        if (thread.isCancelled) {
            NSLog(@"2...888");
            return;
        }
         NSLog(@"%@",[NSThread currentThread]);
    }
}
 
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    // 創(chuàng)建
     NSThread *thread =  [[NSThread alloc] initWithTarget:self selector:@selector(download) object:nil];
    // 就緒 -> 進(jìn)入 CPU 的可調(diào)用線程池
    [thread start];
    
    // 休眠一會(huì)
    [NSThread sleepForTimeInterval:0.2];
    // 取消線程 cancel 是給線程發(fā)送一個(gè)取消的消息每窖。設(shè)置線程的狀態(tài)為取消帮掉。
    // 但是:如果要線程終止,需要在線程內(nèi)部判斷窒典。
    [thread cancel];
}
4.4.3蟆炊、小結(jié)
  • 阻塞
    方法執(zhí)行過(guò)程,符合某一條件時(shí)瀑志,可以利用 sleep 方法讓線程進(jìn)入 阻塞 狀態(tài)
sleepForTimeInterval // 從現(xiàn)在起睡多少秒
sleepUntilDate       // 從現(xiàn)在起睡到指定的日期
  • 死亡
[NSThread exit];

一旦強(qiáng)行終止線程涩搓,后續(xù)的所有代碼都不會(huì)被執(zhí)行
注意:在終止線程之前,應(yīng)該注意釋放之前分配的對(duì)象劈猪!

  • 注意:線程從就緒和運(yùn)行狀態(tài)之間的切換是由 CPU 負(fù)責(zé)的昧甘,程序員無(wú)法干預(yù)
4.5、線程的屬性
  • name - 線程名稱(需要設(shè)置)
    在大的商業(yè)項(xiàng)目中战得,通常需要在程序崩潰時(shí)充边,獲取程序準(zhǔn)確執(zhí)行所在的線程。

  • threadPriority - 線程優(yōu)先級(jí)
    優(yōu)先級(jí)常侦,是一個(gè)浮點(diǎn)數(shù)浇冰,取值范圍從 0~1.0
    1.0表示優(yōu)先級(jí)最高
    0.0表示優(yōu)先級(jí)最低
    默認(rèn)優(yōu)先級(jí)是0.5

優(yōu)先級(jí)高只是保證 CPU 調(diào)度頻率的可能性會(huì)高
建議:在開(kāi)發(fā)的時(shí)候贬媒,不要修改優(yōu)先級(jí),調(diào)度頻率快慢由 CPU決定肘习。
多線程的目的:是將耗時(shí)的操作放在后臺(tái)际乘,不阻塞主線程和用戶的交互礁竞!
多線程開(kāi)發(fā)的原則:簡(jiǎn)單

  • stackSize - 棧區(qū)大小
    默認(rèn)情況下突梦,無(wú)論是主線程還是子線程览效,棧區(qū)大小都是 512K
    棧區(qū)大小可以設(shè)置

    [NSThread currentThread].stackSize = 1024 * 1024;

  • isMainThread - 是否主線程
4.5.1敬尺、示例代碼
// MARK: - 線程屬性
- (void)threadProperty {
    NSThread *t1 = [[NSThread alloc] initWithTarget:self selector:@selector(demo) object:nil];

   // 1. 線程名稱
    t1.name = @"Thread AAA";
    // 2. 優(yōu)先級(jí)
    t1.threadPriority = 0;
    [t1 start];
   NSThread *t2 = [[NSThread alloc] initWithTarget:self selector:@selector(demo) object:nil];
   // 1. 線程名稱
    t2.name = @"Thread BBB";
    // 2. 優(yōu)先級(jí)
    t2.threadPriority = 1;
    [t2 start];
}
- (void)demo {
    for (int i = 0; i < 10; ++i) {
        // 堆棧大小
        NSLog(@"%@ 堆棧大斜锨础:%tuK", [NSThreadcurrentThread], [NSThread currentThread].stackSize / 1024);
    }
   // 判斷是否是主線程
   if (![NSThread currentThread].isMainThread) {
   }

}

4.6太颤、資源共享

4.6.1陈瘦、多線程的安全隱患

資源共享
1塊資源可能會(huì)被多個(gè)線程共享启搂,也就是多個(gè)線程可能會(huì)訪問(wèn)同一塊資源
比如多個(gè)線程訪問(wèn)同一個(gè)對(duì)象墓拜、同一個(gè)變量港柜、同一個(gè)文件
當(dāng)多個(gè)線程訪問(wèn)同一塊資源時(shí),很容易引發(fā)數(shù)據(jù)錯(cuò)亂和數(shù)據(jù)安全問(wèn)題




安全隱患解決 – 互斥鎖

  • 互斥鎖使用格式
    @synchronized(鎖對(duì)象) { // 需要鎖定的代碼 }

  • 注意:鎖定1份代碼只用1把鎖咳榜,用多把鎖是無(wú)效的

  • 互斥鎖的優(yōu)缺點(diǎn)
    優(yōu)點(diǎn):能有效防止因多線程搶奪資源造成的數(shù)據(jù)安全問(wèn)題
    缺點(diǎn):需要消耗大量的CPU資源

  • 互斥鎖的使用前提:多條線程搶奪同一塊資源

  • 相關(guān)專(zhuān)業(yè)術(shù)語(yǔ):線程同步
    線程同步的意思是:多條線程在同一條線上執(zhí)行(按順序地執(zhí)行任務(wù))
    互斥鎖夏醉,就是使用了線程同步技術(shù)

4.6.2、資源共享-賣(mài)票
  • 多線程開(kāi)發(fā)的復(fù)雜度相對(duì)較高涌韩,在開(kāi)發(fā)時(shí)可以按照以下套路編寫(xiě)代碼:
    1.首先確保單個(gè)線程執(zhí)行正確
    2.添加線程
  • 賣(mài)票邏輯
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
      self.tickets = 20;
      [self saleTickets];
}
/// 賣(mài)票邏輯 - 每一個(gè)售票邏輯(窗口)應(yīng)該把所有的票賣(mài)完
- (void)saleTickets {
    while (YES) {
        if (self.tickets > 0) {
            self.tickets--;
            NSLog(@"剩余票數(shù) %d %@", self.tickets, [NSThread currentThread]);
        } else {
            NSLog(@"沒(méi)票了 %@", [NSThreadcurrentThread]);
            break;
        }
    }
}

添加線程

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    self.tickets = 20;
    NSThread *t1 = [[NSThread alloc] initWithTarget:self selector:@selector(saleTickets) object:nil];
    t1.name = @"售票員 A";
    [t1 start];
    NSThread *t2 = [[NSThread alloc] initWithTarget:self selector:@selector(saleTickets) object:nil];
    t2.name = @"售票員 B";
    [t2 start];
}

添加休眠

- (void)saleTickets {
    while (YES) {
        // 模擬休眠
        [NSThreadsleepForTimeInterval:1.0];
       if (self.tickets > 0) {
            self.tickets--;
            NSLog(@"剩余票數(shù) %d %@", self.tickets, [NSThread currentThread]);
        } else {
            NSLog(@"沒(méi)票了 %@", [NSThreadcurrentThread]);
            break;
        }
    }
}

運(yùn)行測(cè)試結(jié)果

4.6.3畔柔、互斥鎖
- (void)saleTickets {
   while (YES) {
        [NSThread sleepForTimeInterval:1.0];
      @synchronized(self) {
            if (self.tickets > 0) {
                self.tickets--;
                NSLog(@"剩余票數(shù) %d %@", self.tickets, [NSThread currentThread]);
                continue;
            }
        }
        NSLog(@"沒(méi)票了 %@", [NSThreadcurrentThread]);
        break;
    }
}
  • 互斥鎖小結(jié)
    1.保證鎖內(nèi)的代碼,同一時(shí)間臣樱,只有一條線程能夠執(zhí)行靶擦!
    2.互斥鎖的鎖定范圍,應(yīng)該盡量小雇毫,鎖定范圍越大玄捕,效率越差!
    3.速記技巧 [[NSUserDefaults standardUserDefaults] synchronize];

  • 互斥鎖參數(shù)
    1.能夠加鎖的任意 NSObject 對(duì)象
    2.注意:鎖對(duì)象一定要保證所有的線程都能夠訪問(wèn)
    3.如果代碼中只有一個(gè)地方需要加鎖棚放,大多都使用 self枚粘,這樣可以避免單獨(dú)再創(chuàng)建一個(gè)鎖對(duì)象

4.7、原子屬性

原子屬性(線程安全)飘蚯,是針對(duì)多線程設(shè)計(jì)的馍迄,是默認(rèn)屬性
多個(gè)線程在寫(xiě)入原子屬性時(shí)(調(diào)用 setter 方法),能夠保證同一時(shí)間只有一個(gè)線程執(zhí)行寫(xiě)入操作
原子屬性是一種單(線程)寫(xiě)多(線程)讀的多線程技術(shù)
原子屬性的效率比互斥鎖高局骤,不過(guò)可能會(huì)出現(xiàn)臟數(shù)據(jù)
在定義屬性時(shí)柬姚,必須顯示地指定 nonatomic,否則默認(rèn)為atomic

4.7.1庄涡、代碼演練

1量承、定義屬性

@property (nonatomic, strong) NSObject *obj1;
@property (atomic, strong) NSObject *obj2;
// 模擬原子屬性
@property (atomic, strong) NSObject *obj3;

2、模擬原子屬性

/**
 如果重寫(xiě)了 atomic 屬性的 setter方法,就必須重寫(xiě) getter 方法撕捍。
 - 如果同時(shí)重寫(xiě)了 setter 和 getter 方法拿穴,蘋(píng)果就不再提供_成員變量
 - @synthesize 合成指令,用處就是指定屬性的 成員變量忧风。 
 */

@synthesize obj3 = _obj3;

- (void)setObj3:(NSObject *)obj3 {
    @synchronized(self) {
        _obj3 = obj3;
    }
}

- (NSObject *)obj3 {
    return _obj3;
}

3默色、性能測(cè)試

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    int largeNumber = 1000 * 1000;
    NSLog(@"非原子屬性");

    CFAbsoluteTime start = CFAbsoluteTimeGetCurrent();

    for (int i = 0; i < largeNumber; i++) {
        self.obj1 = [[NSObject alloc] init];
    }

    NSLog(@"%f", CFAbsoluteTimeGetCurrent() - start);

    NSLog(@"原子屬性");

    start = CFAbsoluteTimeGetCurrent();

    for (int i = 0; i < largeNumber; i++) {
        self.obj2 = [[NSObject alloc] init];
    }

    NSLog(@"%f", CFAbsoluteTimeGetCurrent() - start);

    NSLog(@"模擬原子屬性");

    start = CFAbsoluteTimeGetCurrent();
    for (int i = 0; i < largeNumber; i++) {
        self.obj3 = [[NSObject alloc] init];
    }
    NSLog(@"%f", CFAbsoluteTimeGetCurrent() - start);
}

原子屬性內(nèi)部的鎖是自旋鎖,自旋鎖的執(zhí)行效率比互斥鎖高
atomic:原子屬性.內(nèi)部也會(huì)有一把鎖,叫做自旋鎖. 效率比互斥鎖高

4.7.2狮腿、自旋鎖&互斥鎖

1腿宰、共同點(diǎn)
都能夠保證同一時(shí)間,只有一條線程執(zhí)行鎖定范圍的代碼

2缘厢、不同點(diǎn)
互斥鎖:如果發(fā)現(xiàn)有其他線程正在執(zhí)行鎖定的代碼吃度,線程會(huì)進(jìn)入休眠狀態(tài),等待其他線程執(zhí)行完畢贴硫,打開(kāi)鎖之后椿每,線程會(huì)被喚醒
自旋鎖:如果發(fā)現(xiàn)有其他線程正在執(zhí)行鎖定的代碼,線程會(huì)以死循環(huán)的方式英遭,一直等待鎖定代碼執(zhí)行完成间护。

3、結(jié)論
自旋鎖更適合執(zhí)行非常短的代碼
無(wú)論什么鎖挖诸,都是要付出代價(jià)

4.8汁尺、線程安全

多個(gè)線程進(jìn)行讀寫(xiě)操作時(shí),仍然能夠得到正確結(jié)果多律,被稱為線程安全
要實(shí)現(xiàn)線程安全痴突,必須要用到鎖
為了得到更佳的用戶體驗(yàn),UIKit 不是線程安全的

約定:所有更新 UI 的操作都必須主線程上執(zhí)行菱涤!因此,主線程又被稱為UI 線程洛勉。

  • iOS 開(kāi)發(fā)建議
    所有屬性都聲明為 nonatomic
    盡量避免多線程搶奪同一塊資源
    盡量將加鎖粘秆、資源搶奪的業(yè)務(wù)邏輯交給服務(wù)器端處理,減小移動(dòng)客戶端的壓力

4.9收毫、線程間通訊

主線程實(shí)現(xiàn)
1攻走、定義屬性

/// 根視圖是滾動(dòng)視圖
@property (nonatomic, strong) UIScrollView*scrollView;
/// 圖像視圖
@property (nonatomic, weak) UIImageView *imageView;
/// 網(wǎng)絡(luò)下載的圖像
@property (nonatomic, weak) UIImage *image;

2、loadView 方法
加載視圖層次結(jié)構(gòu)
用純代碼開(kāi)發(fā)應(yīng)用程序時(shí)使用
功能和 Storyboard & XIB 是等價(jià)的

- (void)loadView {
    _scrollView = [[UIScrollView alloc] init];
    _scrollView.backgroundColor = [UIColor orangeColor];
    self.view = _scrollView;
    UIImageView *iv = [[UIImageView alloc] init];
    [self.view addSubview:iv];
    _imageView = iv;
}

3此再、viewDidLoad 方法
視圖加載完成后執(zhí)行
可以做一些數(shù)據(jù)初始化的工作
如果用純代碼開(kāi)發(fā)昔搂,不要在此方法中設(shè)置界面 UI

- (void)viewDidLoad {
    [super viewDidLoad];
    // 下載圖像
    [self downloadImage];
}

4、下載網(wǎng)絡(luò)圖片

- (void)downloadImage{
    // 1. 網(wǎng)絡(luò)圖片資源路徑
    NSURL *url = [NSURL URLWithString:@"[http://c.hiphotos.baidu.com/image/pic/item/4afbfbedab64034f42b14da1aec379310a551d1c.jpg](http://c.hiphotos.baidu.com/image/pic/item/4afbfbedab64034f42b14da1aec379310a551d1c.jpg)"];
    // 2. 從網(wǎng)絡(luò)資源路徑實(shí)例化二進(jìn)制數(shù)據(jù)(網(wǎng)絡(luò)訪問(wèn))
    NSData *data = [NSData dataWithContentsOfURL:url];
    // 3. 將二進(jìn)制數(shù)據(jù)轉(zhuǎn)換成圖像
    UIImage *image = [UIImage imageWithData:data];
    // 4. 設(shè)置圖像
    self.image = image;
}

5输拇、設(shè)置圖片

- (void)setImage:(UIImage *)image {
    // 1. 設(shè)置圖像視圖的圖像
    self.imageView.image = image;
    // 2. 按照?qǐng)D像大小設(shè)置圖像視圖的大小
    [self.imageView sizeToFit];
    // 3. 設(shè)置滾動(dòng)視圖的 contentSize
    self.scrollView.contentSize = image.size;
}

6摘符、設(shè)置滾動(dòng)視圖的縮放
設(shè)置滾動(dòng)視圖縮放屬性

// 1> 最小縮放比例
self.scrollView.minimumZoomScale = 0.5;
// 2> 最大縮放比例
self.scrollView.maximumZoomScale = 2.0;
// 3> 設(shè)置代理
self.scrollView.delegate = self;

實(shí)現(xiàn)代理方法 - 告訴滾動(dòng)視圖縮放哪一個(gè)視圖

#pragma mark - UIScrollViewDelegate 代理方法
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView {
    return self.imageView;
}

7、線程間通訊
在后臺(tái)線程下載圖像

[self performSelectorInBackground:@selector(downloadImage) withObject:nil];

在主線程設(shè)置圖像

// waitUntilDone:是否等待主線程執(zhí)行完畢 setImage:方法。
// YES:等待  NO:不等待
// 一般不用等待逛裤,直接設(shè)置 NO 即可
[self performSelectorOnMainThread:@selector(setImage:) withObject:image waitUntilDone:NO];
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末瘩绒,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子带族,更是在濱河造成了極大的恐慌锁荔,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,270評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蝙砌,死亡現(xiàn)場(chǎng)離奇詭異阳堕,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)择克,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,489評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)恬总,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人祠饺,你說(shuō)我怎么就攤上這事越驻。” “怎么了道偷?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,630評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵缀旁,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我勺鸦,道長(zhǎng)并巍,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,906評(píng)論 1 295
  • 正文 為了忘掉前任换途,我火速辦了婚禮懊渡,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘军拟。我一直安慰自己剃执,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,928評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布懈息。 她就那樣靜靜地躺著肾档,像睡著了一般。 火紅的嫁衣襯著肌膚如雪辫继。 梳的紋絲不亂的頭發(fā)上怒见,一...
    開(kāi)封第一講書(shū)人閱讀 51,718評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音姑宽,去河邊找鬼遣耍。 笑死,一個(gè)胖子當(dāng)著我的面吹牛炮车,可吹牛的內(nèi)容都是我干的舵变。 我是一名探鬼主播酣溃,決...
    沈念sama閱讀 40,442評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼棋傍!你這毒婦竟也來(lái)了救拉?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,345評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤瘫拣,失蹤者是張志新(化名)和其女友劉穎亿絮,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體麸拄,經(jīng)...
    沈念sama閱讀 45,802評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡派昧,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,984評(píng)論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了拢切。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蒂萎。...
    茶點(diǎn)故事閱讀 40,117評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖淮椰,靈堂內(nèi)的尸體忽然破棺而出五慈,到底是詐尸還是另有隱情,我是刑警寧澤主穗,帶...
    沈念sama閱讀 35,810評(píng)論 5 346
  • 正文 年R本政府宣布泻拦,位于F島的核電站,受9級(jí)特大地震影響忽媒,放射性物質(zhì)發(fā)生泄漏争拐。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,462評(píng)論 3 331
  • 文/蒙蒙 一晦雨、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧闹瞧,春花似錦、人聲如沸奥邮。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,011評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)靡砌。三九已至已脓,卻和暖如春度液,著一層夾襖步出監(jiān)牢的瞬間厕宗,已是汗流浹背堕担。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,139評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留佑惠,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,377評(píng)論 3 373
  • 正文 我出身青樓膜楷,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親贞奋。 傳聞我的和親對(duì)象是個(gè)殘疾皇子赌厅,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,060評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容