多線程初識

線程概述

有些程序是一條直線阔涉,起點到終點;有些程序是一個圓蛾默,不斷循環(huán)懦铺,直到將它切斷
一個運行著的程序就是一個進程或者叫做一個任務,一個進程至少包含一個線程支鸡,線程就是程序的執(zhí)行流冬念。Mac和iOS中的程序啟動,創(chuàng)建好一個進程的同時牧挣, 一個線程便開始運行急前,這個線程叫主線程。主線程在程序中的地位和其他線程不同瀑构,它是其他線程最終的父線程裆针,且所有界面的顯示操作即AppKit或 UIKit的操作必須在主線程進行。
系統(tǒng)中的每一個進程都有自己獨立的虛擬內(nèi)存空間寺晌,而同一個進程中的多個線程則共用進程的內(nèi)存空間世吨。每創(chuàng)建一個新的線程,都需要一些內(nèi)存(如每個線程有自己的Stack空間)和消耗一定的CPU時間呻征。另外當多個線程對同一個資源出現(xiàn)爭奪的時候需要注意線程安全問題
多線程的實現(xiàn)原理:雖然在同一時刻耘婚,CPU只能處理1條線程,但是CPU可以快速地在多條線程之間調(diào)度(切換)陆赋,造成了多線程并發(fā)執(zhí)行的假象沐祷。

多線程的優(yōu)點

能適當提高程序的執(zhí)行效率嚷闭。
能適當提高資源利用率(CPU、內(nèi)存利用率)戈轿。

多線程的缺點

創(chuàng)建線程是需要成本的:iOS下主要成本包括:在椓枋埽空間的子線程512KB、主線程1MB思杯,創(chuàng)建線程大約需要90毫秒的創(chuàng)建時間胜蛉。
線程越多,CPU在調(diào)度線程上的開銷就越大色乾。
線程越多誊册,程序設計就越復雜:因為要考慮到線程之間的通信,多線程的數(shù)據(jù)共享暖璧。

下面開始擼代碼:
------------------------------------------------------------------華麗的分割線

1.耗時操作的問題演示

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    [self longOperation];
}
- (void)longOperation
{
    NSLog(@"start");

    NSTimeInterval start = CACurrentMediaTime();

    for (int i = 0; i < 10000000; i++) {

        // 存儲在棧區(qū)
        //  int num = 10;

        // 存儲在常量區(qū)
        //  NSString *str1 = @"hello";

        // 存儲在堆區(qū)
        //  NSString *str2 = [NSString stringWithFormat:@"hello_%d",i];

        // I/O操作 : 把數(shù)據(jù)從內(nèi)存輸出到外接設備,或者由外接設備輸入到內(nèi)存;
        NSLog(@"%d",i);
    }

    NSLog(@"over %f", CACurrentMediaTime() - start);
}

結(jié)論

  1. 空的for循環(huán)不耗時
  2. 操作內(nèi)存的棧區(qū)速度很快;棧區(qū)存儲空間地址是連續(xù)的;
  3. 操作內(nèi)存的常量區(qū)速度很快;內(nèi)存空間只開辟一次;
  4. 操作內(nèi)存的堆區(qū)速度相對棧區(qū)和常量區(qū)要慢些;堆區(qū)內(nèi)存空間不連續(xù),需要尋址;
  5. I/O操作是很耗時的; (把數(shù)據(jù)從內(nèi)存輸出到外接設備,或者由外接設備輸入到內(nèi)存)
  6. 耗時操作對UI交互的影響 : 卡死了主屏幕,直到耗時操作執(zhí)行完,屏幕的交互才能正常進行;
  7. 解決耗時操作卡頓UI的辦法 : 多線程技術(shù);
  8. 學習多線程的目的 : 把耗時操作放在后臺執(zhí)行,不讓耗時操作卡頓UI;

2.解決耗時操作卡頓UI的辦法

使用多線程技術(shù) : 解決屏幕卡死的問題

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
//    [self longOperation];

    // 使用多線程技術(shù)
    [self performSelectorInBackground:@selector(longOperation) withObject:nil];
}

3.多線程基本概念

同步 & 異步

  • 同步異步任務執(zhí)行的兩種方式

  • 同步

    • 我們之前寫程序的時候代碼都是從上往下,順序執(zhí)行的,就叫做同步執(zhí)行.
    • 1個人執(zhí)行多個任務,是要依次執(zhí)行的.因為1個人同一時間只能執(zhí)行1個任務.
    • 多個任務按序依次執(zhí)行,就是同步執(zhí)行.
  • 異步

    • 多個任務同時執(zhí)行,就是異步執(zhí)行.
    • 異步是多線程的代名詞.
    • 我們學習多線程就是為了實現(xiàn)如何讓任務異步執(zhí)行.

進程 & 線程

  • 進程

    • 在系統(tǒng)中正在運行的一個應用程序叫進程.
    • 通過活動監(jiān)視器可以查看MAC系統(tǒng)中正在運行的所有應用程序.
    • 每個進程之間都是獨立的,均運行在其專用受保護的內(nèi)存空間內(nèi).
    • 兩個進程之間是無法通信的,迅雷無法幫助酷我下載正在播放的音樂.
    • 進程可以類比成正在正常運營公司.
  • 線程

    • 線程可以類比成公司中的員工.
    • 進程要想執(zhí)行任務,必須要有線程,且每個進程至少有一條線程.
    • 線程是進程的基本執(zhí)行單元,進程中的所有任務都在線程中執(zhí)行.
    • 程序啟動(進程開啟)會默認開啟一條線程.
    • 1個進程中可以有多個線程.

多線程

  • 多線程 : 一個進程中可以開啟多條線程,多條線程可以**同時**執(zhí)行不同的任務.
  • 進程-公司案怯,線程-員工,老板是什么澎办?
  • 多線程可以解決程序阻塞的問題
  • 多線程可以提高程序的執(zhí)行效率,給用戶良好的使用體驗.
  • 比如,酷我音樂的邊下載邊聽歌,迅雷的邊下載邊播放.

4.多線程執(zhí)行原理

  • 單核CPU同一時間,CPU只能處理1個線程,只有1個線程在執(zhí)行任務.
  • 多線程的同時執(zhí)行 : 其實是CPU在多條線程之間快速切換(調(diào)度任務).
  • 如果CPU調(diào)度線程的速度足夠快,就造成了多線程**同時**執(zhí)行的**假象**
  • 如果線程非常多,CPU會在多條線程之間不斷的調(diào)度任務,結(jié)果就是消耗了大量的CPU資源,CPU會累趴下.
    • 每個線程調(diào)度的頻率會降低
    • 線程的執(zhí)行效率會下降

5.多線程優(yōu)缺點

優(yōu)點

  • 能"適當"提高程序的執(zhí)行效率.
  • 能"適當"提高CPU和內(nèi)存的利用率.
  • 線程上的任務執(zhí)行完成后,線程會自動銷毀,節(jié)省內(nèi)存.

缺點

  • 開啟線程需要占用一定的內(nèi)存空間,如果開啟的線程過多,會占用大量的CPU資源,降低程序的性能
  • 占用內(nèi)存空間:默認情況下,子線程512KB,主線程1M.PS:iOS8中,主線程512KB.
  • 線程越多,CPU調(diào)度線程的開銷就越大.
    • 時間開銷
    • 空間開銷
  • 程序設計更加復雜:比如線程之間的通信,多線程的數(shù)據(jù)共享

6.主線程

  • 一個程序運行后,默認會開啟1個線程,稱為主線程UI線程.

    • 關(guān)注main函數(shù)的執(zhí)行
  • 主線程一般用來刷新UI界面,處理UI事件.

  • 處理UI事件 : 點擊嘲碱、滾動拖拽等事件

  • 主線程使用注意

    • 別將耗時的操作放到主線程中
    • 耗時操作會卡住主線程,嚴重影響UI的流暢度,給用戶一種卡的壞體驗,影響UI交互質(zhì)量.

7.多線程的實現(xiàn)方案

(二)創(chuàng)建線程三種方式

1.準備新線程執(zhí)行的方法

- (void)demo:(id)obj
{
    NSLog(@"傳入?yún)?shù) => %@",obj);
    NSLog(@"hello %@",[NSThread currentThread]);
}

2.對象方法創(chuàng)建

  • 實例化線程對象的同時指定線程執(zhí)行的方法@selector(demo:).
  • 需要手動開啟線程.
- (void)threadDemo1
{
    NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(demo:) object:@"alloc"];
    // 手動啟動線程
    [thread start];
}

3.類方法創(chuàng)建

  • 分離出一個線程,并且自動開啟線程執(zhí)行@selector(demo:).
  • 無法獲取到線程對象
- (void)threadDemo2
{
    [NSThread detachNewThreadSelector:@selector(demo:) toTarget:self withObject:@"detach"];
}

4.NSObject(NSThreadPerformAdditions) 的分類創(chuàng)建

  • 方便任何繼承自NSObject的對象,都可以很容易的調(diào)用線程方法
  • 無法獲取到線程對象
  • 自動開啟線程執(zhí)行@selector(demo:).
- (void)threadDemo3
{
    [self performSelectorInBackground:@selector(demo:) withObject:@"perform"];
}

5.總結(jié)

  • 以上三種創(chuàng)建線程的方式,各有不同.隨意選擇.
  • 使用哪種方式需要根據(jù)具體的需求而定.比如 : 如果需要線程對象,就使用對象方法創(chuàng)建.

(三)target和selector的關(guān)系

1.target和selector的關(guān)系分析

  • target : 指方法從屬于的對象.
    • 比如 : 本對象--self;其他對象--self.person.
  • @selector : 指對象里面的方法.
    • 比如 : 要執(zhí)行的是self中或者self.person中的哪個方法.
  • 提示 : 不要看見 target 就寫 self.
  • target@selector的關(guān)系 : 執(zhí)行哪個對象上的哪個方法.

2.代碼演練

準備Person對象

@interface Person : NSObject

/// 人名
@property (nonatomic,copy) NSString *name;
/// 創(chuàng)建人的構(gòu)造方法
+ (instancetype)personWithDict:(NSDictionary *)dict;
/// 人有個方法
- (void)personDemo:(id)obj;

@end

@implementation Person

+ (instancetype)personWithName:(NSString *)name
{
    Person *person = [[Person alloc] init];
    person.name = name;
    return person;
}

- (void)personDemo:(id)obj
{
    NSLog(@"創(chuàng)建的人名 => %@",self.name);
    NSLog(@"hello %@",[NSThread currentThread]);
}

@end

控制器中的使用

定義屬性

@interface ViewController ()
@property (nonatomic,strong) Person *person;
@end

懶加載Person

@implementation ViewController

- (Person *)person
{
    if (_person==nil) {
        _person = [Person personWithName:@"zhangjie"];
    }
    return _person;
}

新的實例化方法

  • 使用self調(diào)用@selector(personDemo:)就會崩潰.因為self中沒有@selector(personDemo:).

  • 分類方法

// 崩潰
[self performSelectorInBackground:@selector(personDemo:) withObject:@"perform"];
// 正確的調(diào)用方式
[self.person performSelectorInBackground:@selector(personDemo:) withObject:@"perform"];
  • 類方法
// 崩潰
[NSThread detachNewThreadSelector:@selector(personDemo:) toTarget:self withObject:@"detach"];

// 正確的調(diào)用方式
[NSThread detachNewThreadSelector:@selector(personDemo:) toTarget:self.person withObject:@"detach"];
  • 對象方法
// 崩潰
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(personDemo:) object:@"alloc"];
// 手動開啟線程
[thread start];
// 正確的調(diào)用方式
NSThread *thread = [[NSThread alloc] initWithTarget:self.person selector:@selector(personDemo:) object:@"alloc"];
// 手動開啟線程
[thread start];

(四)線程狀態(tài)-生命周期

線程生命周期的控制

  • 新建
    • 內(nèi)存中創(chuàng)建了一個線程對象
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(threadDemo) object:nil];
  • 就緒
    • 將線程放進可調(diào)度線程池,等待被CPU調(diào)度
[thread start];
  • 運行

    • CPU負責調(diào)度 可調(diào)度線程池 中的處于 就緒狀態(tài) 的線程
    • 線程執(zhí)行結(jié)束之前,狀態(tài)可能會在 就緒狀態(tài)運行狀態(tài) 之間來回的切換
    • 就緒狀態(tài)運行狀態(tài) 之間的狀態(tài)切換由CPU來完成,程序員無法干涉
  • 阻塞

    • 正在運行的線程,當滿足某個條件時,可以用休眠或者來阻塞線程的執(zhí)行

      • sleepForTimeInterval:休眠指定時長
      [NSThread sleepForTimeInterval:1.0];
      
      • sleepUntilDate:休眠到指定日期
      [NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:1.0]];
      
      • 互斥鎖
      @synchronized(self)
      
  • 死亡

    • 正常死亡:線程執(zhí)行結(jié)束
    • 非正常死亡
      • 程序突然崩潰
      • 當滿足某個條件后局蚀,在線程內(nèi)部強制線程退出,調(diào)用exit方法

代碼演練

創(chuàng)建線程對象和就緒狀態(tài)

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    // 新建狀態(tài)
    NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(threadDemo) object:nil];
    // 就緒狀態(tài) : 將線程放進"可調(diào)度線程池",等待被CPU調(diào)度.
    [thread start];
}

新線程執(zhí)行的方法

- (void)threadDemo
{
    // 提示 : 能執(zhí)行到這里說明線程是運行狀態(tài)
    NSLog(@"%@",[NSThread currentThread]);

    // 使當前線程休眠2秒鐘 : 休眠指定時長
    [NSThread sleepForTimeInterval:2.0];

    NSLog(@"第一次睡醒");

    // 使當前線程休眠到指定日期
    [NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:2.0]];

    NSLog(@"第二次睡醒");

    // 使當前線程退出 : 當前線程一旦退出,后續(xù)的所有代碼都不會執(zhí)行
    // 注意 : 該方法不能在主線程使用,會使主線程退出
    [NSThread exit];

    NSLog(@"沒戲了?");
}

關(guān)于exit的結(jié)論

  • 使當前線程退出.
  • 不能在主線程中調(diào)用該方法.會使主線程退出.
  • 當前線程死亡之后,這個線程中的剩下的所有代碼都不會被執(zhí)行.
  • 在調(diào)用此方法之前一定要注意釋放之前由C語言框架創(chuàng)建的對象.
  • 調(diào)用exit方法屬于在線程內(nèi)部取消線程,有時候需要在線程外部,當某一條件滿足時就取消線程

線程的取消 (在線程執(zhí)行的方法的外部取消)

  • 取消線程的方法
- (void)cancel NS_AVAILABLE(10_5, 2_0);
  • 使用
[thread cancel]
  • 這個方法只是修改了線程的狀態(tài)而已,并沒有真正的取消線程
  • 如果想真正的取消線程需要在線程執(zhí)行的過程中判斷線程的狀態(tài)是否是已取消
  • 如果該線程已經(jīng)被取消,就直接返回,不再執(zhí)行后面的代碼
if ([NSThread currentThread].isCancelled) {
    NSLog(@"該線程已經(jīng)被取消");
    return;
}

(五)線程屬性

1.常用屬性

  • name - 線程名稱

    • 設置線程名稱可以當線程執(zhí)行的方法內(nèi)部出現(xiàn)異常時麦锯,記錄異常和當前線程
  • stackSize - 棧區(qū)大小

    • 默認情況下,無論是主線程還是子線程琅绅,棧區(qū)大小都是 512K
    • 棧區(qū)大小可以設置 [NSThread currentThread].stackSize = 1024 * 1024;
    • 必須是 4KB 的倍數(shù)
  • isMainThread - 是否主線程

  • threadPriority - 線程優(yōu)先級

    • 優(yōu)先級扶欣,是一個浮點數(shù),取值范圍從 0~1.0
    • 1.0 表示優(yōu)先級最高
    • 0.0 表示優(yōu)先級最低
    • 默認優(yōu)先級是 0.5
    • 優(yōu)先級高只是保證 CPU 調(diào)度的可能性會高
  • qualityOfService - 服務質(zhì)量(iOS 8.0 推出)

    • NSQualityOfServiceUserInteractive - 用戶交互千扶,例如繪圖或者處理用戶事件
    • NSQualityOfServiceUserInitiated - 用戶需要
    • NSQualityOfServiceUtility - 實用工具料祠,用戶不需要立即得到結(jié)果
    • NSQualityOfServiceBackground - 后臺
    • NSQualityOfServiceDefault - 默認,介于用戶需要和實用工具之間

關(guān)于優(yōu)先級和服務質(zhì)量

  • 多線程的目的:是將耗時的操作放在后臺澎羞,不阻塞主線程和用戶的交互髓绽!
  • 多線程開發(fā)的原則:簡單
  • 在開發(fā)時,最好不要修改優(yōu)先級妆绞,不要相信 用戶交互 服務質(zhì)量
  • 內(nèi)核調(diào)度算法在決定該運行哪個線程時梧宫,會把線程的優(yōu)先級作為考量因素
* 較高優(yōu)先級的線程會比較低優(yōu)先級的線程具有更多的運行機會
* 較高優(yōu)先級不保證你的線程具體執(zhí)行的時間,只是相比較低優(yōu)先級的線程摆碉,更有可能被調(diào)度器選擇執(zhí)行而已

2.代碼演示

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    NSLog(@"主線程棧區(qū)空間大小 == %tu KB 是否是主線程 %zd",[NSThread currentThread].stackSize / 1024,[NSThread currentThread].isMainThread);

    NSThread *thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(demo) object:nil];

    // 給線程起名字
    thread1.name = @"download A";

    // 設置線程優(yōu)先級
    thread1.threadPriority = 1.0;

    // 線程就緒
    [thread1 start];

    NSThread *thread2 = [[NSThread alloc] initWithTarget:self selector:@selector(demo) object:nil];
    thread2.name = @"download B";
    thread2.threadPriority = 0;
    [thread2 start];
}

- (void)demo
{
    NSLog(@"子線程棧區(qū)空間大小 == %tu KB 是否是主線程 %zd",[NSThread currentThread].stackSize / 1024,[NSThread currentThread].isMainThread);

    for (int i = 0; i < 10; i++) {
        NSLog(@"%@",[NSThread currentThread]);
    }
}

3.補充

  • NSInteger 有符號整數(shù)(有正負數(shù))用 %zd
  • NSUInteger 無符號整數(shù)(沒有負數(shù))用 %tu
  • 是為了自適應32位和64位CPU的架構(gòu).

(六)線程安全-資源共享

1.多線程操作共享資源的問題

  • 共享資源

    • 資源 : 一個全局的對象、一個全局的變量脓豪、一個文件.
    • 共享 : 可以被多個對象訪問.
    • 共享資源 :可以被多個對象訪問的資源.比如全局的對象,變量,文件.
  • 多線程的環(huán)境下,共享的資源可能會被多個線程共享巷帝,也就是多個線程可能會操作同一塊資源.

  • 當多個線程操作同一塊資源時,很容易引發(fā)數(shù)據(jù)錯亂和數(shù)據(jù)安全問題,數(shù)據(jù)有可能丟失,有可能增加,有可能錯亂.

  • 經(jīng)典案例 : 賣票.

  • 線程安全

    • 同一塊資源,被多個線程同時讀寫操作時,任然能夠得到正確的結(jié)果,稱之為線程是安全的.

開發(fā)提示

  • 實際開發(fā)中確定開發(fā)思路邏輯比及時的寫代碼更重要.
  • 多線程開發(fā)的復雜度相對較高,在開發(fā)時可以按照以下套路編寫代碼
    • 首先確保單個線程執(zhí)行正確
    • 然后再添加線程

代碼實現(xiàn)賣票邏輯

  • 先定義共享資源
@interface ViewController ()

/// 總票數(shù)(共享的資源)
@property (nonatomic,assign) int tickets;

@end
  • 初始化余票數(shù)共享資源
- (void)viewDidLoad {
    [super viewDidLoad];

    // 設置余票數(shù)
    self.tickets = 20;
}
  • 賣票邏輯實現(xiàn)
-  (void)saleTickets
{
    // while 循環(huán)保證每個窗口都可以單獨把所有的票賣完
    while (YES) {

        // 判斷是否有票
        if (self.tickets>0) {

            // 模擬網(wǎng)絡延遲 : 放大出錯時的效果,沒有實際意義
            [NSThread sleepForTimeInterval:1.0];

            // 有票就賣一張
            self.tickets--;
            // 賣完一張票就提示用戶余票數(shù)
            NSLog(@"剩余票數(shù) => %zd %@",self.tickets,[NSThread currentThread]);
        } else {
            // 沒有就提示用戶
            NSLog(@"沒票了");
            // 此處要結(jié)束循環(huán),不然會死循環(huán)
            break;
        }
    }
}

單線程

  • 先確保單線程中運行正常
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    // 在主線程中賣票
    [self saleTickets];
}

多線程

  • 如果單線程運行正常,就修改代碼,實現(xiàn)多線程環(huán)境
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    // 在主線程中賣票
    // [self saleTickets];

    // 售票口 A
    NSThread *thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(saleTickets) object:nil];
    thread1.name = @"售票口 A";
    [thread1 start];

    // 售票口 B
    NSThread *thread2 = [[NSThread alloc] initWithTarget:self selector:@selector(saleTickets) object:nil];
    thread2.name = @"售票口 B";
    [thread2 start];
}

資源搶奪結(jié)果

  • 數(shù)據(jù)錯亂,數(shù)據(jù)增加.


出錯原因分析

2.解決多線程操作共享資源的問題

  • 解決辦法 : 使用互斥鎖/同步鎖.

添加互斥鎖

- (void)saleTickets
{
    // while 循環(huán)保證每個窗口都可以單獨把所有的票賣完
    while (YES) {

        // 添加互斥鎖
        @synchronized(self) {
            // 判斷是否有票
            if (self.tickets>0) {

                // 模擬網(wǎng)絡延遲 : 放大出錯時的效果,沒有實際意義
                [NSThread sleepForTimeInterval:1.0];

                // 有票就賣一張
                self.tickets--;
                // 賣完一張票就提示用戶余票數(shù)
                NSLog(@"剩余票數(shù) => %zd",self.tickets);
            } else {
                // 沒有就提示用戶
                NSLog(@"沒票了");
                // 此處要結(jié)束循環(huán),不然會死循環(huán)
                break;
            }
        }
    }
}

互斥鎖小結(jié)

  • 互斥鎖,就是使用了線程同步技術(shù).
  • 同步鎖/互斥鎖:可以保證被鎖定的代碼,同一時間,只能有一個線程可以操作.
  • self :鎖對象,任何繼承自NSObject的對像都可以是鎖對象,因為內(nèi)部都有一把鎖,而且默認是開著的.
  • 鎖對象 : 一定要是全局的鎖對象,要保證所有的線程都能夠訪問,self是最方便使用的鎖對象.
  • 互斥鎖鎖定的范圍應該盡量小,但是一定要鎖住資源的讀寫部分.
  • 加鎖后程序執(zhí)行的效率比不加鎖的時候要低.因為線程要等待解鎖.
  • 犧牲了性能保證了安全性.

(七)原子屬性

1.原子屬性相關(guān)概念

  • nonatomic : 非原子屬性

  • atomic : 原子屬性

    • 線程安全的,針對多線程設計的屬性修飾符,是默認值.
    • 特點 : 單寫多讀
    • **單寫多讀 : **保證同一時間,只有一個線程能夠執(zhí)行setter方法,但是可以有多個線程執(zhí)行getter方法.
    • atomic 屬性的setter里面里面有一把鎖,叫做自旋鎖.
    • 原子屬性的setter方法是線程安全的;但是,getter方法不是線程安全的.
  • nonatomicatomic對比

    • nonatomic : 非線程安全,適合內(nèi)存小的移動設備.
    • atomic : 線程安全,需要消耗大量的資源.性能比非原子屬性要差一點兒點兒.

2.模擬原子屬性

  • 模擬原子屬性的核心思想 : 在屬性的setter方法里面加鎖.但是getter方法里面不加鎖;

  • 定義屬性

/// 非原子屬性
@property (nonatomic,strong) NSObject *obj1;
/// 原子屬性:內(nèi)部有"自旋鎖"
@property (atomic,strong) NSObject *obj2;
/// 用于模擬原子屬性
@property (atomic,strong) NSObject *obj3;
  • 重寫非原子屬性的settergetter方法
    • 重寫了原子屬性的setter方法之后,會覆蓋原子屬性內(nèi)部的自旋鎖,使其失效.然后我們加入互斥鎖,來模擬單寫多讀.
    • 重寫了屬性的settergetter方法之后,系統(tǒng)就不會再幫我們生成待下劃線的成員變量.使用合成指令@synthesize,就可以手動的生成帶下劃線的成員變量.

3.模擬原子屬性

// 合成指令
@synthesize obj3 = _obj3;

/// obj3的setter方法
- (void)setObj3:(NSObject *)obj3
{
    // 使用互斥鎖替代看不見的自旋鎖
    @synchronized(self) {
        _obj3 = obj3;
    }
}

/// obj3的getter方法
- (NSObject *)obj3
{
    return _obj3;
}

4.性能測試

/// 測試"非原子屬性","互斥鎖","自旋鎖"的性能
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    NSInteger largeNum = 1000*1000;

    NSLog(@"非原子屬性");
    CFAbsoluteTime start = CFAbsoluteTimeGetCurrent();
    for (int i = 0; i < largeNum; i++) {
        self.obj1 = [[NSObject alloc] init];
    }
    NSLog(@"非原子屬性 => %f",CFAbsoluteTimeGetCurrent()-start);

    NSLog(@"原子屬性");
    start = CFAbsoluteTimeGetCurrent();
    for (int i = 0; i < largeNum; i++) {
        self.obj2 = [[NSObject alloc] init];
    }
    NSLog(@"原子屬性 => %f",CFAbsoluteTimeGetCurrent()-start);

    NSLog(@"模擬原子屬性");
    start = CFAbsoluteTimeGetCurrent();
    for (int i = 0; i < largeNum; i++) {
        self.obj3 = [[NSObject alloc] init];
    }
    NSLog(@"模擬原子屬性 => %f",CFAbsoluteTimeGetCurrent()-start);
}

測試結(jié)果

5.互斥鎖和自旋鎖對比

共同點

  • 都能夠保證同一時間,只有一條線程執(zhí)行鎖定范圍的代碼

不同點

  • 互斥鎖:如果發(fā)現(xiàn)有其他線程正在執(zhí)行鎖定的代碼,線程會進入休眠狀態(tài),等待其他線程執(zhí)行完畢,打開鎖之后,線程會重新進入就緒狀態(tài).等待被CPU重新調(diào)度.
  • 自旋鎖:如果發(fā)現(xiàn)有其他線程正在執(zhí)行鎖定的代碼,線程會以死循環(huán)的方式,一直等待鎖定代碼執(zhí)行完成.

6.開發(fā)建議

  • 所有屬性都聲明為nonatomic,原子屬性和非原子屬性的性能幾乎一樣.
  • 盡量避免多線程搶奪同一塊資源.
  • 要實現(xiàn)線程安全,必須要用到.無論什么鎖,都是有性能消耗的.
  • 自旋鎖更適合執(zhí)行非常短的代碼.死循環(huán)內(nèi)部不適合寫復雜的代碼.
  • 盡量將加鎖,資源搶奪的業(yè)務邏輯交給服務器端處理,減小移動客戶端的壓力.
  • 為了流暢的用戶體驗,UIKit類庫的線程都是不安全的,所以我們需要在主線程(UI線程)上更新UI.
  • 所有包含NSMutable的類都是線程不安全的.在做多線程開發(fā)的時候,需要注意多線程同時操作可變對象的線程安全問題.

(八)NSThread線程間通信

1.ATS

使用http地址時Xcode會認為不夠安全從而保存,為解決此問題需要在info文件的Xml文件內(nèi)添加下列代碼
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>

2.代碼實現(xiàn)

定義屬性

@interface ViewController ()

/// 滾動視圖
@property (nonatomic,strong) UIScrollView *scrollView;
/// 圖片視圖
@property (nonatomic,weak) UIImageView *imageView;

@end

loadView 方法復習

  • self.view == nil 時,會調(diào)用;
  • 先于 viewDidLoad 調(diào)用;
  • 一旦重寫了這個方法,storyboard里面就不會去加載根視圖了;

加載視圖層次

- (void)loadView
{
    // 創(chuàng)建滾動視圖
    self.scrollView = [[UIScrollView alloc] initWithFrame:[UIScreen mainScreen].bounds];
    // 將滾動視圖設置成根視圖
    self.view = self.scrollView;
    self.scrollView.backgroundColor = [UIColor redColor];

    // 創(chuàng)建圖片視圖
    UIImageView *imageView = [[UIImageView alloc] init];
    [self.view addSubview:imageView];
    self.imageView = imageView;
}

異步下載圖片

- (void)viewDidLoad {
    [super viewDidLoad];

    // 主線程中下載圖片
    // [self downloadImageData];

    // 開啟新線程異步下載圖片
    [self performSelectorInBackground:@selector(downloadImageData) withObject:nil];
}

下載圖片主方法

- (void)downloadImageData
{
    // 圖片資源地址
    NSURL *url = [NSURL URLWithString:@"http://h.hiphotos.baidu.com/image/pic/item/c995d143ad4bd1130c0ee8e55eafa40f4afb0521.jpg"];
    // 所有的網(wǎng)絡數(shù)據(jù)都是以二進制的形式傳輸?shù)?所以用NSData來接受
    NSData *data = [NSData dataWithContentsOfURL:url];
    UIImage *image = [UIImage imageWithData:data];

    // 回到主線程更新UI
    // waitUntilDone:是否等待主線程中的`updateUIwWithImage`方法執(zhí)行結(jié)束再執(zhí)行"下一行代碼",一般設置成NO,不用等待
    [self performSelectorOnMainThread:@selector(updateUIwWithImage:) withObject:image waitUntilDone:NO];

    // 測試 waitUntilDone:
    NSLog(@"下一行代碼");
}

刷新UI

- (void)updateUIwWithImage:(UIImage *)imgae
{
    NSLog(@"updateUIwWithImage");

    // 設置圖片視圖
    self.imageView.image = image;
    // 設置圖片視圖的大小跟圖片一般大
    [self.imageView sizeToFit];

    // 設置滾動視圖的滾動:滾動范圍跟圖片一樣大
    [self.scrollView setContentSize:image.size];
}

線程間通信

  • 因為多線程共享地址空間和數(shù)據(jù)空間

所以一個線程的數(shù)據(jù)可以直接提供給其他線程使用,叫做線程間通信;

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市扫夜,隨后出現(xiàn)的幾起案子楞泼,更是在濱河造成了極大的恐慌驰徊,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,123評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件堕阔,死亡現(xiàn)場離奇詭異棍厂,居然都是意外死亡,警方通過查閱死者的電腦和手機超陆,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,031評論 2 384
  • 文/潘曉璐 我一進店門牺弹,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人时呀,你說我怎么就攤上這事张漂。” “怎么了谨娜?”我有些...
    開封第一講書人閱讀 156,723評論 0 345
  • 文/不壞的土叔 我叫張陵航攒,是天一觀的道長。 經(jīng)常有香客問我趴梢,道長漠畜,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,357評論 1 283
  • 正文 為了忘掉前任坞靶,我火速辦了婚禮憔狞,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘滩愁。我一直安慰自己躯喇,他們只是感情好,可當我...
    茶點故事閱讀 65,412評論 5 384
  • 文/花漫 我一把揭開白布硝枉。 她就那樣靜靜地躺著廉丽,像睡著了一般。 火紅的嫁衣襯著肌膚如雪妻味。 梳的紋絲不亂的頭發(fā)上正压,一...
    開封第一講書人閱讀 49,760評論 1 289
  • 那天,我揣著相機與錄音责球,去河邊找鬼焦履。 笑死,一個胖子當著我的面吹牛雏逾,可吹牛的內(nèi)容都是我干的嘉裤。 我是一名探鬼主播,決...
    沈念sama閱讀 38,904評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼栖博,長吁一口氣:“原來是場噩夢啊……” “哼屑宠!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起仇让,我...
    開封第一講書人閱讀 37,672評論 0 266
  • 序言:老撾萬榮一對情侶失蹤典奉,失蹤者是張志新(化名)和其女友劉穎躺翻,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體卫玖,經(jīng)...
    沈念sama閱讀 44,118評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡公你,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,456評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了假瞬。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片陕靠。...
    茶點故事閱讀 38,599評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖笨触,靈堂內(nèi)的尸體忽然破棺而出懦傍,到底是詐尸還是另有隱情,我是刑警寧澤芦劣,帶...
    沈念sama閱讀 34,264評論 4 328
  • 正文 年R本政府宣布粗俱,位于F島的核電站,受9級特大地震影響虚吟,放射性物質(zhì)發(fā)生泄漏寸认。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,857評論 3 312
  • 文/蒙蒙 一串慰、第九天 我趴在偏房一處隱蔽的房頂上張望偏塞。 院中可真熱鬧,春花似錦邦鲫、人聲如沸灸叼。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,731評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽古今。三九已至,卻和暖如春滔以,著一層夾襖步出監(jiān)牢的瞬間捉腥,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,956評論 1 264
  • 我被黑心中介騙來泰國打工你画, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留抵碟,地道東北人。 一個月前我還...
    沈念sama閱讀 46,286評論 2 360
  • 正文 我出身青樓坏匪,卻偏偏與公主長得像拟逮,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子适滓,可洞房花燭夜當晚...
    茶點故事閱讀 43,465評論 2 348

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

  • Java8張圖 11唱歧、字符串不變性 12、equals()方法、hashCode()方法的區(qū)別 13颅崩、...
    Miley_MOJIE閱讀 3,696評論 0 11
  • 從三月份找實習到現(xiàn)在,面了一些公司蕊苗,掛了不少沿后,但最終還是拿到小米、百度朽砰、阿里尖滚、京東、新浪瞧柔、CVTE漆弄、樂視家的研發(fā)崗...
    時芥藍閱讀 42,209評論 11 349
  • Java-Review-Note——4.多線程 標簽: JavaStudy PS:本來是分開三篇的,后來想想還是整...
    coder_pig閱讀 1,639評論 2 17
  • 又來到了一個老生常談的問題造锅,應用層軟件開發(fā)的程序員要不要了解和深入學習操作系統(tǒng)呢撼唾? 今天就這個問題開始,來談談操...
    tangsl閱讀 4,098評論 0 23
  • 前幾天去另一座城市參見一位大學同學的婚禮哥蔚。這個同學經(jīng)過八年抗戰(zhàn)倒谷,終于把岳父岳母拿下。 我跟幾位同學提前一天到了那個...
    鄭子陵閱讀 698評論 0 2