iOS 使用NSThread来代表线程,创建新线程也就是创建一个NSThread对象。
在iOS10之前提供了两种方法开启线程。
- (instancetype)initWithTarget:(id)target selector:(SEL)selector object:(nullable id)argument NS_AVAILABLE(10_5, 2_0);
+ (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(nullable id)argument;
第一种方法是实例方法,返回一个NSThread对象,必须调用start方法来启动线程。
第二种方法是类方法,不会返回NSThread对象,直接创建并启动线程。
iOS10增加了两种创建启动线程的方法
- (instancetype)initWithBlock:(void (^)(void))block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));
+ (void)detachNewThreadWithBlock:(void (^)(void))block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));
这两个方法与上述类似,也是一个实例方法,一个类方法。只是执行方法体不一样了。
#import "ZPYViewController.h"
@interface ZPYViewController ()
@end
@implementation ZPYViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[self makeThread1beforeIOS10];
[self makeThread2beforeIOS10];
[self makeThread1FromIOS10];
[self makeThread2FromIOS10];
}
-(void)run{
for(int i=0;i<10;i++){
NSLog(@"%@,----i=%d",[NSThread currentThread],i);
NSLog(@"isMainThread=%d",[NSThread isMainThread]);
}
}
//iOS 10 之前NSThread两种方法创建线程。如下:
-(void) makeThread1beforeIOS10{
//第一种创建方式 实例方法 最多可以传一个参数
NSThread *thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];
[thread1 setName:@"fisrt blood"];
//启动线程
[thread1 start];
BOOL isMain = [NSThread isMainThread];
NSLog(@"isMain=%d,thread1=%d",isMain,[thread1 isMainThread]);
}
//第二种创建方式 类方法。 最多可以传一个参数
-(void) makeThread2beforeIOS10{
//类方法,不会返回NSThread对象,直接启动线程
[NSThread detachNewThreadSelector:@selector(run) toTarget:self withObject:nil];
}
//iOS 10又添加了两个方法创建线程。
// 需要iOS 10 才可以运行下面两个方法。否则会出错。
//第三种创建方式 实例方法
-(void) makeThread1FromIOS10{
NSThread *thread3 = [[NSThread alloc] initWithBlock:^{
for(int i=0;i<10;i++){
NSLog(@"%@,第三种 i=%d",[NSThread currentThread],i);
}
}];
thread3.name = @"triple kill";
//调用start方法启动线程
[thread3 start];
}
//第四种创建方式 类方法
-(void) makeThread2FromIOS10{
//类方法,不会返回NSThread对象,直接启动线程
[NSThread detachNewThreadWithBlock:^{
for(int i=0;i<10;i++){
NSLog(@"%@,第四种 i=%d",[NSThread currentThread],i);
}
}];
}
@end
主线程相关:
+ (NSThread *)mainThread; // 获得主线程
- (BOOL)isMainThread; // 是否为主线程
+ (BOOL)isMainThread; // 是否为主线程
其他方法:
获得当前线程
NSThread *current = [NSThreadcurrentThread];
线程的调度优先级
+ (double)threadPriority;
+ (BOOL)setThreadPriority:(double)p;
- (double)threadPriority;
- (BOOL)setThreadPriority:(double)p;
调度优先级的取值范围是0.0 ~ 1.0,默认0.5,值越大,优先级越高
自己开发时,建议一般不要修改优先级
线程的名字
- (void)setName:(NSString *)n;
- (NSString *)name;
NSThread *thread1 = [[NSThreadalloc]initWithTarget:selfselector:@selector(run)object:nil];
//启动线程
[thread1 start];
五种状态:新建、就绪、运行、阻塞、死亡。
新建:当程序建立一个线程后,该线程就处于新建状态,此时它和其他OC对象一样,仅仅有系统分配了内存,初始化了成员变量。
就绪:当线程对象调用start方法后,该线程处于就绪状态。处于该状态的线程并没有开始运行,至于何时运行,取决于系统的调度。
运行:当系统调度到该线程时,进入运行状态。调用到其他线程则该线程就又重新回到就绪状态。
阻塞:当线程调用了sleep方法或等待同步锁时进入阻塞状态。
死亡:当线程执行完毕,或异常/强制退出,则该线程进入死亡状态。
启动线程
- (void)start; //进入就绪状态-> 运行状态。当线程任务执行完毕,自动进入死亡状态
阻塞(暂停)线程
+ (void)sleepUntilDate:(NSDate *)date;
+ (void)sleepForTimeInterval:(NSTimeInterval)ti;
// 进入阻塞状态
强制停止线程
+ (void)exit;
// 进入死亡状态
注意:一旦线程停止(死亡)了,就不能再次开启任务
有时我们需要将子线程结束,这该怎么办呢?
线程结束方式又三种:
1 线程执行体方法执行完成,线程正常结束。
2 线程执行过程中发生了异常。
3 调用NSThread的exit方法类停止当前正在执行的线程。
由于exit方法为类方法,所以只能停止正在执行的线程。
我们可以在主线程或者其他线程中调用[thread cancel]。而子线程的执行体中可以判断是否cancel来停止线程。
//结束线程
if([[NSThread currentThread] isCancelled]){
[NSThread exit];
}
在多线程中,必然会涉及到线程安全点问题,比如资源共享(多个线程访问同一个对象、同一个变量、同一个文件),这时很容易发生数据错乱、数据安全问题。
如下经典问题,取钱
如上图:1500取出1600,还剩余700,这样显然是不合理的(虽然对于用户来说很乐意了)。
-(void)takeMoney:(NSNumber *)count{
[_account take:count.doubleValue];
}
- (IBAction)actionTest:(id)sender {
_account = [[ZPYAccount alloc] initWithCardId:@"1234567" andBalance:1500];
NSThread *thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(takeMoney:) object:[NSNumber numberWithInt:800]];
[thread1 setName:@"first thread"];
NSThread *thread2 = [[NSThread alloc] initWithTarget:self selector:@selector(takeMoney:) object:[NSNumber numberWithInt:800]];
[thread2 setName:@"second thread"];
[thread1 start];
[thread2 start];
}
两个线程去做取钱的操作。
ZPYAccount中的take方法:
-(void)take:(double)count{
double money = _balance;
if(count <= money){
[NSThread sleepForTimeInterval:0.001];
_balance = money - count;
NSLog(@"thread:%@,取钱成功,余额为%f",[[NSThread currentThread] name],_balance);
}else{
NSLog(@"thread:%@,余额不足!",[[NSThread currentThread] name]);
}
}
输出如下:
2016-10-11 11:40:09.052 NSThread使用[553:386755] thread:second thread,取钱成功,余额为700.000000
2016-10-11 11:40:09.051 NSThread使用[553:386754] thread:first thread,取钱成功,余额为700.000000
-(void)take:(double)count{
@synchronized (self) {
//将需要同时执行的放入同步代码块中。查钱、取钱。
double money = _balance;
if(count <= money){
[NSThread sleepForTimeInterval:0.001];
_balance = money - count;
NSLog(@"thread:%@,取钱成功,余额为%f",[[NSThread currentThread] name],_balance);
}else{
NSLog(@"thread:%@,余额不足!",[[NSThread currentThread] name]);
}
}
}
打印:
2016-10-11 18:45:52.135 NSThread使用[556:387839] thread:first thread,取钱成功,余额为700.000000
2016-10-11 18:45:52.137 NSThread使用[556:387840] thread:second thread,余额不足!
使用NSLock也可以保证线程安全。
_lock = [[NSLockalloc]init];
-(void)takelock:(double)count{
[_lock lock];
double money = _balance;
if(count <= money){
[NSThread sleepForTimeInterval:0.001];
_balance = money - count;
NSLog(@"thread:%@,取钱成功,余额为%f",[[NSThread currentThread] name],_balance);
}else{
NSLog(@"thread:%@,余额不足!",[[NSThread currentThread] name]);
}
[_lock unlock];
}
打印:
2016-10-11 18:52:09.216 NSThread使用[559:388860] thread:first thread,取钱成功,余额为700.000000
2016-10-11 18:52:09.217 NSThread使用[559:388861] thread:second thread,余额不足!