二. NSThread基本使用

2016-05-25  本文已影响21人  面糊

一. 线程的创建

  1. 创建线程并且手动开启, 同时在这条线程执行selector的任务

     // 1. 创建线程对象
     NSThread *thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(task1:) object:@"创建线程方式1"];
     // 2. 为线程添加标识   
     [thread1 setName:@"thread1"];           
     // 3. 设置线程的优先级(优先级高的线程,会优先执行)       
     [thread1 setThreadPriority:0.5];       
     // 4. 开始执行
     [thread1 start];
    
  2. 分离出一条子线程, 同时在这条线程执行selector的任务

     // 2. 分离子线程
     [NSThread detachNewThreadSelector:@selector(task1:) toTarget:self withObject:@"创建线程方式2"];           
    
  3. 开启一条后台线程, 同时在这条线程执行selector的任务(注意, 该方法是由当前控制器调用的)

       // 3. 创建后台线程
       [self performSelectorInBackground:@selector(task1:) withObject:@"创建线程方式3"];
    
  4. 自定义线程

    • 创建一个NSThread的子类
    • 在类中重写-(void)main方法, 将这个线程需要执行的任务, 在这个方法中实现
    • 使用alloc/init创建这个自定义线程, 当使用start执行这个线程的时候, 系统会自动调用main方法来执行其中的任务
  5. 创建线程方法的优缺点

    • 手动创建线程, 可以获取线程对象, 可以对这个线程对象进行详细的设置, 如标识/优先级等
    • 而采用分离子线程或者开启后台线程的方法, 使用方法简单, 可以直接开启一个子线程去执行耗时任务, 但是由于无法获取到这个线程对象, 因此无法对其进行设置

二. 线程的生命周期


三. 线程安全

  1. 多线程应用时的安全隐患
    • 当多个线程, 出现访问同一块资源的时候, 这样会导致在存取的过程中, 被取资源中保存的值由于可能同时在修改, 导致值发生错误

      // 例子: 三个售票员同时售票
        - (void)sale {
           while (1) {
               @synchronized(self) {  // 如果这里不增加线程锁, 就会导致存取错误
                   // 1. 检查余票
                   int count = self.count;
                   if (count > 0) {
                       // 演示耗时操作
                       for (int i = 0; i < 100000; i++) {
                           // 如果线程的任务中有耗时操作,就有可能引起共同访问一块资源导致数据错误
                           // 因此这时需要加入线程锁
                       }
                       // 访问属性
                       self.count = count - 1;
                       NSLog(@"%@卖出去了一张票,还剩%d张", [NSThread currentThread], self.count);
                       
                   } else {
                       NSLog(@"票已经卖完");
                       [NSThread exit];
                   }
               }
           }
        }
      
    • 解决方法: 在线程要访问资源之前, 增加一个互斥锁
      @synchronized(锁对象){ 需要锁定的代码 }

    • 互斥锁:

      • 通常锁对象, 是全局唯一的一个对象, 一般使用NSObject作为对象的类型
      • 注意点
        1. 一份代码, 只能使用同一把互斥锁
        2. 锁对象本身有两种状态: 打开/关闭
        3. 当互斥锁关闭的时候, 队列中的线程就会进入Blocked状态, 直到互斥锁打开, 该线程才会进入运行状态
        4. 锁对象可以使用当前的控制器, 也就是self
        5. 加锁的位置需要注意, 位置不同, 执行的代码也会不同
        6. 加锁的前提条件: 只有出现多个线程同时访问同一块资源的时候, 才需要使用互斥锁进行保护
        7. 互斥锁的使用, 会增加额外的资源消耗, 所以能不使用就不要使用, 尽量避免出现多线程抢夺资源
        8. 互斥锁会造成线程同步: 每个线程会在锁的外面排队执行任务
        9. 但是队列中的线程是异步的: 到底是哪条线程访问锁内的资源, 顺序是不确定的

四. 原子和非原子性

  1. atomic:

    • 属性为原子性, 该关键字会为setter方法增加一个互斥锁, 因此它是线程安全的
    • 但是他会消耗大量的内存资源, 并且在我们的开发过程中, 很少发生多线程访问同一个属性的情况, 因此基本不会使用这个关键字
  2. nonatomic:

    • 属性为非原子性, 此关键字不会为setter方法增加互斥锁, 因此是非线程安全的, 适合内存小的移动设备, 即iOS开发中属性主要使用的关键字,
    • 因此, 在日常开发中, 尽量要避免多线程抢夺资源的情况, 加锁/资源抢夺的业务逻辑通常会交由服务器端来处理, 即每次只能接收/发送一份网络请求

五. 线程间的通信

  1. 需求:
    • 开启条子线程, 并且下载一张图片

    • 将获得到的图片, 在主线程中设置给imageView

    • 注意点: 遵循耗时操作交给子线程, UI操作回到主线程

        // 0. 创建子线程执行下载任务线程
        NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(download) object:nil];
        [thread start];
        
        - (void)download {
        // 计算时间(绝对时间)
        CFTimeInterval start = CFAbsoluteTimeGetCurrent();    
        // 1. 创建url路径
        NSURL *url = [NSURL URLWithString:@"http://dimg07.c-ctrip.com/images/tg/946/212/497/81b56770ed4544a6a8a1125fb381753d_C_640_640.jpg"];
        // 2. 将图片转换为二进制数据
        NSData *data = [NSData dataWithContentsOfURL:url];    
        // 3. 转换格式(二进制 -> UIImage)
        UIImage *image = [UIImage imageWithData:data];    
        // 4. 回主线程设置图片
        //    [self performSelectorOnMainThread:@selector(showImage:) withObject:image waitUntilDone:NO]; // 该方法可以直接使用主线程去执行任务
        [self performSelector:@selector(showImage:) onThread:[NSThread mainThread] withObject:image waitUntilDone:NO];    
        // 获取绝对时间
        CFTimeInterval end = CFAbsoluteTimeGetCurrent();    
        NSLog(@"%f", end - start);
        }
上一篇下一篇

猜你喜欢

热点阅读