NSThread
使用NSThread有多种方式:
- 第一种直接alloc创建线程,需要手动调用start启动线程
/*
第一个参数:目标对象
第二个参数:方法选择器,调用的方法
第三个参数:前面调用的方法需要传递的参数,可不传
*/
NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(cycleLog:) object:@"acb"];
//启动线程
[thread start];
- 通过类方法创建
[NSThread detachNewThreadSelector:@selector(cycleLog:) toTarget:self withObject:@"dfdf"];
//使用block方式
[NSThread detachNewThreadWithBlock:^{
[self cycleLog:@"dsfds"];
}];
3.通过使用performSelectorInBackground
开启后台线程执行任务
[self performSelectorInBackground:@selector(cycleLog:) withObject:@"bacground"];
NSThread相关重要属性及方法
- name线程名称用于区分不同线程
threadPriority
设置线程优先级取值范围0-1,1优先级最高,默认优先级为0.5+(double)threadPriority
获取线程优先级+ (BOOL)setThreadPriority:(double)p
设置线程优先级,优先级越大则cpu调度的机会越大
NSThread *threada = [[NSThread alloc]initWithTarget:self selector:@selector(cycleLog:) object:@"acb"];
threada.name=@"A线程";
//取值范围0-1,1优先级最高
threada.threadPriority=0.5;
创建NSThred线程三种方式总结
- 创建NSThread对象后,手动启动线程
- 通过NSThread类方法detachNewThreadSelector创建线程后自动启动线程
- 通过NSObject的NSThread分类方法performSelectorInBackground隐式创建并启动线程。
上述2、3两种创建线程方式的优缺点;
优点:简单快捷
缺点:没法拿到线程对象没法设置线程名称和线程优先级等详细设置
NSThread 的生命周期。
前面章节说过,NSThread是半自动管理,程序员创建,系统销毁的。那是什么时候销毁的呢?
通常在方法中创建一个对象,没有外部的强引用的话,方法运行完成则改对象将被销毁。而NSThread销毁时机为任务执行完毕后。如下示例
@interface TDThread : NSThread
@end
@implementation TDThread
-(void)dealloc{
NSLog(@"%@--%s",[self class],__func__);
}
@end
-(void)threadLifeCycle{
TDThread *thread = [[TDThread alloc]initWithTarget:self selector:@selector(cycleLog:) object:nil];
[thread start];
}
-(void)cycleLog:(NSString *)param{
NSThread *thread=[NSThread currentThread];
for (int i=0; i<1000; i++) {
NSLog(@"index = %d, thread--%@",i,thread);
}
}
通过日志打印可看出线程是任务执行完成后销毁的。
线程的状态
线程的状态有,新建、就绪、运行、挂起、死亡;各状态将关系如下
创建NSThread对象的时候,thread是新建状态,调用start方法时,系统将其加入可调度线程池中,thread变为就绪状态,但cpu调度该线程时,thread变为运行状态,前面说过线程运行是cpu快速切换调度的(单核)当cpu调用其他线程时,改thread变为就绪状态,再次进入可调度线程池中,如果线程调用sleep或等待同步锁时线程进入阻塞状态,thread被移除可调度线程池,单sleep到时,或已获得同步锁时,thread再次进入到就绪转态,再次被加入可调度线程池中。如果thread任务执行完毕或遇到异常或调用了强制退出是线程进入死亡转态;
注意:一点线程进入停止(死亡状态)了,就不能再次开启任务
线程安全
当线程之间资源共享时是有安全风险的
- 资源共享
- 一块资源可能被多个线程共享,也就是多个线程可能会访问同一块资源;比如多个线程访问同一个对象,同一个变量、同一个文件等;
当多个线程访问同一块资源是,很容易引发数据错乱和数据安全问题;
如下图为apple提供是示例图:
两个线程threadA、threadB都读取数据17进行操作引起的线程安全问题。
当threadA读取数据17时进行加操作,threadB也同时读取了数据17进行加操作。thread将加后的数据赋回原变量时为18,而threadA也完成操作时赋值回去时数据还是18,做了两次加操作还是18是不对的。苹果给出了一种解决方案,加互斥锁:
threadA读取数据17时对其加锁,如果同时threadB也来读取数据17时,看到有锁则挂起等待锁的释放,ThreadA执行完成后释放同步锁,这时ThreadB对数据读取并加锁这时读取到的数据未18ThreadB完成执行后最终数据未19
卖票例子如下:
-(void)threadSafeThread{
self.ticketCount = 100;
NSThread *threadA=[[NSThread alloc]initWithTarget:self selector:@selector(saleTicket) object:nil];
threadA.name = @"窗口1";
NSThread *threadB=[[NSThread alloc]initWithTarget:self selector:@selector(saleTicket) object:nil];
threadB.name = @"窗口2";
NSThread *threadC=[[NSThread alloc]initWithTarget:self selector:@selector(saleTicket) object:nil];
threadC.name = @"窗口3";
[threadA start];
[threadB start];
[threadC start];
}
-(void)saleTicket{
//锁不能加在该位置,如果在该位置则一个窗口全部卖完。可以试试看效果
while (1) {
//锁:必须全局唯一
@synchronized (self) {
NSInteger count = self.ticketCount;
if (count>0) {
//耗时操作
for (NSInteger i=0; i<10000; i++) {
}
self.ticketCount =count -1;
// for (int i=0; i<100; i++) {
//
// }
NSLog(@"%@卖了一张票,还剩%zd张票",[NSThread currentThread].name,self.ticketCount);
}else{
NSLog(@"票卖完了");
break;//任务执行完成退出。区别于[NSThread exit]强制退出程序
}
}
}
}
注意:设置锁对象必须全局唯一,通常直接将当前对象self
注意点:
- 注意加锁位置
- 注意加锁的前提条件,多线程共享同一块资源
- 注意加锁是需要代价的,需要耗费性能
- 加锁的结果:照成线程同步
- 互斥锁使用格式
@synchronized (锁对象){//需要锁定的代码}
注意:锁定一份代码只用一把锁,用多把锁是无效的
互斥锁的优缺点
- 有点:能有效防止因多线程抢夺资源造成的数据安全问题
- 确定:需要消耗大量cpu资源
- 互斥锁使用前提:多条线程访问统一块资源
- 线程同步:
线程同步的意思是,多条线程在同一条线上执行(按顺序的执行任务)