一. 线程的创建
-
创建线程并且手动开启, 同时在这条线程执行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];
-
分离出一条子线程, 同时在这条线程执行selector的任务
// 2. 分离子线程 [NSThread detachNewThreadSelector:@selector(task1:) toTarget:self withObject:@"创建线程方式2"];
-
开启一条后台线程, 同时在这条线程执行selector的任务(注意, 该方法是由当前控制器调用的)
// 3. 创建后台线程 [self performSelectorInBackground:@selector(task1:) withObject:@"创建线程方式3"];
-
自定义线程
- 创建一个NSThread的子类
- 在类中重写
-(void)main
方法, 将这个线程需要执行的任务, 在这个方法中实现 - 使用
alloc/init
创建这个自定义线程, 当使用start
执行这个线程的时候, 系统会自动调用main方法来执行其中的任务
-
创建线程方法的优缺点
- 手动创建线程, 可以获取线程对象, 可以对这个线程对象进行详细的设置, 如标识/优先级等
- 而采用分离子线程或者开启后台线程的方法, 使用方法简单, 可以直接开启一个子线程去执行耗时任务, 但是由于无法获取到这个线程对象, 因此无法对其进行设置
二. 线程的生命周期
- 线程的五种状态
- New: 一个刚刚新建出来的线程, 此时没有任何任务在执行当中
- Runable: 处于可调度线程池的线程, 只有在线程池中的线程, 才可以被CPU调度
- Running: 使用了
-(void)start
方法, 被激活工作, 且正在被CPU调度的线程, 一个线程如果还未完成它所有的任务, 那么他就会一直在Runable和Running两个状态中相互切换 - Blocked: 调用了
[NSThread sleepForTimeInterval]
方法或等待同步锁的放行时的状态, 此时线程暂时被移除可调度线程池, 但是并没有被销毁掉, 当sleep的时间到了或同步锁放行, 就会恢复Runable状态 - Dead: 当一条线程的任务执行完毕/异常或强制退出
exit方法
时, 这条线程就会被销毁, 销毁后的线程与对象一样, 不能再次使用
三. 线程安全
- 多线程应用时的安全隐患
-
当多个线程, 出现访问同一块资源的时候, 这样会导致在存取的过程中, 被取资源中保存的值由于可能同时在修改, 导致值发生错误
// 例子: 三个售票员同时售票 - (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作为对象的类型
- 注意点
- 一份代码, 只能使用同一把互斥锁
- 锁对象本身有两种状态: 打开/关闭
- 当互斥锁关闭的时候, 队列中的线程就会进入Blocked状态, 直到互斥锁打开, 该线程才会进入运行状态
- 锁对象可以使用当前的控制器, 也就是self
- 加锁的位置需要注意, 位置不同, 执行的代码也会不同
- 加锁的前提条件: 只有出现多个线程同时访问同一块资源的时候, 才需要使用互斥锁进行保护
- 互斥锁的使用, 会增加额外的资源消耗, 所以能不使用就不要使用, 尽量避免出现多线程抢夺资源
- 互斥锁会造成线程同步: 每个线程会在锁的外面排队执行任务
- 但是队列中的线程是异步的: 到底是哪条线程访问锁内的资源, 顺序是不确定的
-
四. 原子和非原子性
-
atomic:
- 属性为原子性, 该关键字会为setter方法增加一个互斥锁, 因此它是线程安全的
- 但是他会消耗大量的内存资源, 并且在我们的开发过程中, 很少发生多线程访问同一个属性的情况, 因此基本不会使用这个关键字
-
nonatomic:
- 属性为非原子性, 此关键字不会为setter方法增加互斥锁, 因此是非线程安全的, 适合内存小的移动设备, 即iOS开发中属性主要使用的关键字,
- 因此, 在日常开发中, 尽量要避免多线程抢夺资源的情况, 加锁/资源抢夺的业务逻辑通常会交由服务器端来处理, 即每次只能接收/发送一份网络请求
五. 线程间的通信
- 需求:
-
开启条子线程, 并且下载一张图片
-
将获得到的图片, 在主线程中设置给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); }
-
网友评论