iOS 多线程(二)NSThread

iOS 使用NSThread来代表线程,创建新线程也就是创建一个NSThread对象。

1 创建和启动线程

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

2 常用方法

主线程相关:

+ (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;


3 线程的状态

NSThread *thread1 = [[NSThreadalloc]initWithTarget:selfselector:@selector(run)object:nil];

//启动线程

[thread1 start];

iOS 多线程(二)NSThread_第1张图片

五种状态:新建、就绪、运行、阻塞、死亡

新建:当程序建立一个线程后,该线程就处于新建状态,此时它和其他OC对象一样,仅仅有系统分配了内存,初始化了成员变量。

就绪:当线程对象调用start方法后,该线程处于就绪状态。处于该状态的线程并没有开始运行,至于何时运行,取决于系统的调度。

运行:当系统调度到该线程时,进入运行状态。调用到其他线程则该线程就又重新回到就绪状态。

阻塞:当线程调用了sleep方法或等待同步锁时进入阻塞状态。

死亡:当线程执行完毕,或异常/强制退出,则该线程进入死亡状态。

启动线程

- (void)start; //进入就绪状态-> 运行状态。当线程任务执行完毕,自动进入死亡状态

阻塞(暂停)线程

+ (void)sleepUntilDate:(NSDate *)date;

+ (void)sleepForTimeInterval:(NSTimeInterval)ti;

// 进入阻塞状态


强制停止线程

+ (void)exit;

// 进入死亡状态

注意:一旦线程停止(死亡)了,就不能再次开启任务


4 停止线程

有时我们需要将子线程结束,这该怎么办呢?

线程结束方式又三种

1 线程执行体方法执行完成,线程正常结束。

2 线程执行过程中发生了异常。

3 调用NSThread的exit方法类停止当前正在执行的线程。

由于exit方法为类方法,所以只能停止正在执行的线程。

我们可以在主线程或者其他线程中调用[thread cancel]。而子线程的执行体中可以判断是否cancel来停止线程。

//结束线程
    if([[NSThread currentThread] isCancelled]){
        [NSThread exit];
    }

5 多线程安全

在多线程中,必然会涉及到线程安全点问题,比如资源共享(多个线程访问同一个对象、同一个变量、同一个文件),这时很容易发生数据错乱、数据安全问题。

如下经典问题,取钱

iOS 多线程(二)NSThread_第2张图片

如上图: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

1 @synchronized实现同步:

-(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,余额不足!

这样就合理了,当余额不足时就不再让取钱,并且余额也没有问题。

2 NSLock 同步锁

使用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,余额不足!


你可能感兴趣的:(iOS开发)