NSThread
创建线程
- 代码实现(创建线程的四种方法)
- 创建线程的第一种方法:
- 1.创建线程对象
- [[NSThread alloc]initWithTarget:selector:object:]
- target:目标对象,一般传self
- selector:方法选择器,线程创建好之后,调用哪个方法,要执行的任务(run方法)
- object:argument传给目标对象的参数,可以为nil
- 传递给run方法的参数
- run方法最多只能接受一个参数
- 比如object:@"创建线程方法"
- [[NSThread alloc]initWithTarget:selector:object:]
- 2.启动线程
- [thread start]
- 1.创建线程对象
- 第二种创建线程的方法:分离出一条子线程
- 类方法
- NSThread detachNewThreadSelector:toTarget:withObject:
- 第一个参数:要调用的方法
- 第二个参数:目标对象self(run方法属于谁?如果当前方法在另一个类就不能传self)
- 第三个参数:run需要接受的参数
- NSThread detachNewThreadSelector:toTarget:withObject:
- 类方法
- 第三种方法:创建一条后台线程
- self performSelectorInBackground:withObject:
- 三种方法区别
- 第一种方法:优点:能拿到线程对象,缺点:需要手动启动线程
- 第二种方法:优点:自动启动线程,缺点:拿不到线程对象
- 第三种方法:优点:自动启动线程,缺点:拿不到线程对象
- 第四种方法(了解)
- 创建线程对象
- alloc]init];
- start
- 开了子线程,但是没有任务,需要告诉它任务是什么
- 自定义类,继承NSThread
- 重写main方法,在main方法里面封装任务
- 创建线程对象
- 创建线程的第一种方法:
设置线程的属性
- 线程启动之前设置属性
- name线程的名称
- 如何判断不同的线程对象
- 地址
- number(分配的id)
- name
- threadA.name = @"线程A";
- [threadB setName:@"线程B"]
- 如何判断不同的线程对象
- threadPriority设置线程优先级
- double类型
- 取值范围:0.0~1.0之间,最高是1.0,如果不设置,默认是0.5
- 优先级越高,被调度的概率越高,执行速度越快
- 以后用
qualityOfService
替换(ios8.0)枚举值,不是设置数值了
NSThread创建线程对象的生命周期
- 生命周期:当任务结束的时候,线程进入到死亡状态,就会被销毁
- 验证:自定义一个类,继承NSThread,重写dealloc方法验证
线程的状态(比较重要)
- 1.alloc]initWithTarget:self selector: @selector(run) object:nil
- 创建线程
- 内存里面分配存储空间给线程对象
- 2.thread start
- 内存里面的线程对象会被添加到可调度线程池里面
- 只有在可调度线程池里面的线程才可以被调度,执行
- 线程处于就绪状态
- 3.CPU调度当前线程
- 线程进入运行状态
- 4.CPU调度其他线程
- 线程进入就绪状态
- 5.当调用了sleep方法,或者等待同步锁的时候,线程会进入到阻塞状态
- 线程进入阻塞状态
- 不能做任何事情
- 从可调度线程池中移除
- 6.sleepUntilDate:/sleepForTimeInterval:方法时间到了,或取消同步锁
- 阻塞状态进入到就绪状态
- 把线程对象放入可调度线程池中
- 7.任务执行完毕后或者强制退出
- 线程进入死亡状态
- 不能再重新调用start方法,不能执行任务了
- 把线程对象从可调度线程池中移除
- 把线程对象从内存中销毁掉
- 退出线程exit
- 代码实现
- 创建线程对象
- alloc]initWithTarget:selector:object:
- 线程处于新建状态
- 启动线程
- start
- 新建 ——>就绪 <=====>运行
- 让线程进入到阻塞状态(执行当前任务的线程,并不是主线程)
- 类方法
- sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:3.0]阻塞三秒钟
- [NSDate distantFuture]睡到遥远的未来2000年以后
- sleepForTimeInterval:2.0]线程阻塞两秒
- sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:3.0]阻塞三秒钟
- 类方法
- 退出线程
- 退出当前任务
- for循环可以用return
- 任务已经结束,是正常退出
- 强制退出
- exit
- 强制退出线程
- 退出当前任务
- 死亡状态
- 创建线程对象
多线程的安全隐患
-
资源共享
- 一个资源可能会被多个线程共享(多个线程可能会访问同一块资源)
- 比如多个线程访问同一个对象,同一个变量、同一个文件
- 多个线程访问同一块资源,容易引发数据错乱和数据安全问题
-
示例1:存钱取钱
- 两个人共用一个银行账号1000元
- 女生存钱|男生取钱
- 女生存1000
- 同一时间,男生取1000,花500,又存500
- 银行账号里500
-
示例2:买票
- 售票员A\B
- 票数1000
- A卖出一张999
- 同一时间B卖出一张999
- 票数999
-
安全隐患分析
- 线程A/B
- A读取变量 17 + 1 = 18
- A做回写之前,B读取变量 17+1 = 18
- 两次回写后 变量18
- 解决:当线程A写完之后,B再读取变量,加互斥锁
-
代码演示线程安全问题(卖票)
-
定义属性
- threadA ,threadB,threadC
- 总票数totalCount
-
创建三个线程对象
- thread.name设置名称
启动线程
-
卖票
- 判断余票
- count>0,卖出去一张 count - 1
- 添加一个耗时操作
- 记录谁卖出去一张票,还剩多少张票
- currentThread].name
- tatalCount
- count>0,卖出去一张 count - 1
- 搞一个死循环,让他们把票都卖完,票卖完之后break
- 判断余票
-
安全问题
- 一张票卖了很多次
-
解决:互斥锁,在死循环里面加锁
- token:锁对象,全局唯一的对象
- 创建一个锁对象strong修饰
- 启动完成之后,做一个初始化处理
- token - self.lock
- viewController是控制器,self就是全局唯一的
- token - self
- token:锁对象,全局唯一的对象
-
@synchronized(token){
给哪一段代码加锁
}
- 线程对象检查锁对象的状态,如果是锁起来的,就会在外面等,A第一次来是打开的,线程对象A进入之后,就会关闭锁,BC就要在外面等,当执行完代码,会打开锁,BC才能进入
-
注意点
:- 加锁是需要耗费性能的
- 注意加锁的位置,在不同的位置加锁效果是不一样的
- 注意加锁的条件,并不是什么时候都可以加锁
- 多线程访问同一块资源的时候才需要加锁
- 互斥锁 = 同步锁
- 锁定一份代码只用一把锁,用多把锁是无效的
- 优点
- 能有效防止因多线程抢夺资源造成的数据安全问题
- 缺点
- 需要消耗大量的CPU资源
- 使用前提:多线程抢夺同一块资源
- 相关专业术语 :
线程同步
- 多条线程在同一条线上执行(按顺序地执行任务)
- 只要给一段代码加互斥锁,就会造成线程同步
原子属性|非原子属性
- 原子属性
- atomatic
- 线程安全,需要消耗大量的资源
- 非原子属性
- nonatomatic
- 非线程安全,适合内存小的移动设备
- 在开发中真正设计多线程的代码并不多,建议开发中声明成非原子属性
- 开发建议
- 所有属性都声明为nonatomic
- 尽量避免多线程抢夺同一块资源
- 尽量将加锁、资源抢夺的业务逻辑交给服务器端处理,减小移动客户端的压力
线程间通信
- 在一个进程中,线程往往不是孤立存在的,多个线程之间需要经常进行通信
- 体现
- 一个线程传递数据给另一个线程
- 在一个线程中执行完特定任务后,转到另一个线程继续执行任务
- 方法
- 回到主线程performSelectorOnMainThread:withObject:waitUntilDone:
- 回到指定的线程performSelector:onThread:withObject:waitUntilDone:
- 示例:图片下载
- 耗时操作
- 主线程 添加UIImage
- 开一个子线程,在子线程里面下载图片
- 下载完毕把图片设置到主线程的控件上面去,显示图片
- 线程间通信
- 凡是跟UI相关的操作必须在主线程中执行
- 代码演示
-
下载网络图片
- 确定资源路径
- urlWithString:
- 下载图片的二进制数据到本地
- dataWithContentsOfURL:
- 最耗时
- 转换格式
- imageWithData:
- 设置显示图片
- self.imageV.image = image;
- 确定资源路径
-
发送网络请求,需要使用https协议
- info.plist文件配置
- 点击加号
- AppTransportSecurity
- 点击小箭头,点击加号
- allow arbitrary loads YES
- info.plist文件配置
-
怎么获得图片下载的时间?
- 获得执行当前代码行的时间
- NSDate date 开始
- 获得执行当前代码行的时间
- NSDate date 结束
- 时间差
[end timeIntervalSinceDate:start]
- 获得执行当前代码行的时间
-
补充:计算时间的第二种方法
- CFTimeInterval - C语言的
- CFAbsoluteTimeGetCurrent()绝对时间
- 时间差 = 绝对时间差(结束和开始的差)
- 绝对时间
- 2001年开始算起
-
线程间通信
- 开子线程下载图片
- NSThread detachNewThreadSelector:@selector(download) toTarget:withObject:
- 显示图片要在主线程中处理
- 在download方法里面回到主线程设置图片
- 方法一:performSelectorOnMainThread:@selector(showImage:)withObject:waitUntilDone:
- 第一个参数:回到主线程要执行的任务
- 第二个参数:调用方法需要传递的参数
- 第三个参数:是否要等调用的方法任务执行完毕才继续执行后面的任务,YES,要等showImage执行完毕之后再执行后面的任务;NO,不需要showImage执行完就可以执行后面的任务
- 设置图片
- 开子线程下载图片
-
showImage:(UIImage *)image{
self.imageV.image = image;
}
- 方法二: performSelector:onThread:withObject:waitUntilDone:
- `简便做法`performSeletor是给NSObject写的分类
- self.imageV performSelectorOnMainSelector:withObject:waitUntilDone:
- 直接调用image的setImage:方法
- 传个image参数