iOS 多线程之 NSThread的基本使用

一个NSThread对象就代表一条线程 下面是NSThread开启线程的方法

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    [self openThreadWithNSThread];
    [NSThread mainThread];//获取主线程
    [NSThread currentThread]; //获取当前线程
}

- (void) openThreadWithNSThread {
    /*
     *第一个参数 目标对象 self
     *第二个参数 方法选择器 调用的方法
     *第三个参数 前面调用方法需要传递的参数 可以为nil
     */
    //第一种方式
    NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(threadAction:) object:@"ABC"];
    thread.name = @"线程1";
    //0.0-1.0之间 1.0最高 0.0最低 默认0.5 越高线程被调用的频率越大
    thread.threadPriority = 1.0;
    [thread start];//需要手动调用启动线程
    //第二种方式
    [NSThread detachNewThreadSelector:@selector(threadAction:) toTarget:self withObject:@"abc2"];//自动启动线程

    //第三种方式
    [self performSelectorInBackground:@selector(threadAction:) withObject:@"开启一条后台线程"];//开启一条后台线程
    
    //block方式
    NSThread *thread1 = [[NSThread alloc] initWithBlock:^{
      NSLog(@"%@",[NSThread currentThread]);
    }];
    thread1.name = @"线程2";
    thread1.threadPriority = 0.5;
    [thread start];
    
    //或者
    [NSThread detachNewThreadWithBlock:^{
        NSLog(@"%@",[NSThread currentThread]);
    }];
    
}

- (void) threadAction:(NSString *)params {
    NSLog(@"%@ and %@",params,[NSThread currentThread].name);
}

NSThread 创建线程的生命周期

当线程中的任务执行完毕后 线程被释放掉 可以继承NSThread创建一个新类 重写dealloc方法来验证

线程的状态

当线程处于就绪状态时线程会被移到可调度线程池里面(CPU只调度此线程池里面的线程),当处于阻塞状态时,线程会被移出可调度线程池,当处于死亡状态时 先移出线程池,再从内存中释放。

- (void)threadStatus {
    //创建线程 在内存中创建一个线程 (新建状态)
    NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(threadAction1) object:nil];
    [thread start]; //就绪状态 会被放倒可调度线程池里面 只有在可调度线程池里面的线程才是可以被CPU调度的
    
}
//一旦线程死亡了 就不能再次开启任务
- (void)threadAction1 {
    //运行状态
    for (NSInteger i = 0; i < 10000; i ++) {
        if (i == 5000) {
            //[NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:2.0]];
            [NSThread sleepForTimeInterval:2.0];//阻塞状态 移出线程池
            //两秒钟过后 再次进入就绪状态 等待调度进入运行状态
        }
        if (i == 8000) {
            //break;//任务完成 自然死亡
            [NSThread exit]; //废除线程强制进入死亡状态
        }
        NSLog(@"%@",[NSThread currentThread]);
    }
    //执行完方法之后死亡状态
}

线程的安全 也非常重要 这里介绍一种方法 后面会着重介绍

多线程的安全隐患

资源共享

1块资源可能会被多个线程共享,也就是多个线程可能会访问同一块资源 比如多个线程访问同一个对象 同一个变量 同一个文件 此时就很容易引发数据错乱和数据安全问题.

安全隐患原因分析

安全隐患的解决

问题代码

#import "ViewController.h"

@interface ViewController ()

@property (nonatomic,strong) NSThread *threadA;//售票员A
@property (nonatomic,strong) NSThread *threadB;//售票员B
@property (nonatomic,strong) NSThread *threadC;//售票员C
@property (nonatomic,assign) NSInteger totalCount;

@end

@implementation ViewController

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    //设置总票数
    self.totalCount = 1000;
    self.threadA = [[NSThread alloc] initWithTarget:self selector:@selector(saleTicket) object:nil];
    self.threadA.name = @"售票员A";
    [self.threadA start];
    self.threadB = [[NSThread alloc] initWithTarget:self selector:@selector(saleTicket) object:nil];
    self.threadB.name = @"售票员B";
    [self.threadB start];
    self.threadC = [[NSThread alloc] initWithTarget:self selector:@selector(saleTicket) object:nil];
    self.threadC.name = @"售票员C";
    [self.threadC start];
}

//售票
- (void)saleTicket {
    while (1) {
        NSInteger count = self.totalCount;
        if (count > 0) {
            for (NSInteger i = 0; i < 100000; i ++) {
                
            }
            //卖出去一张票
            self.totalCount = count - 1;
            NSLog(@"%@ 卖出去一张票  还剩%zd张票",[NSThread currentThread].name,self.totalCount);
        }else {
            NSLog(@"票卖完了");
            break;
        }
    }
   
}

打印结果

2018-03-05 22:28:38.600491+0800 NSThreadDemo[1016:86284] 售票员A 卖出去一张票  还剩999张票
2018-03-05 22:28:38.600493+0800 NSThreadDemo[1016:86285] 售票员B 卖出去一张票  还剩999张票
2018-03-05 22:28:38.600519+0800 NSThreadDemo[1016:86286] 售票员C 卖出去一张票  还剩999张票

问题解决代码

#import "ViewController.h"

@interface ViewController ()

@property (nonatomic,strong) NSThread *threadA;//售票员A
@property (nonatomic,strong) NSThread *threadB;//售票员B
@property (nonatomic,strong) NSThread *threadC;//售票员C
@property (nonatomic,assign) NSInteger totalCount;

@end

@implementation ViewController

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    //设置总票数
    self.totalCount = 1000;
    self.threadA = [[NSThread alloc] initWithTarget:self selector:@selector(saleTicket) object:nil];
    self.threadA.name = @"售票员A";
    [self.threadA start];
    self.threadB = [[NSThread alloc] initWithTarget:self selector:@selector(saleTicket) object:nil];
    self.threadB.name = @"售票员B";
    [self.threadB start];
    self.threadC = [[NSThread alloc] initWithTarget:self selector:@selector(saleTicket) object:nil];
    self.threadC.name = @"售票员C";
    [self.threadC start];
}

//售票
- (void)saleTicket {
    while (1) {
        //锁必须是全局唯一的
        @synchronized(self) {
            NSInteger count = self.totalCount;
            if (count > 0) {
                for (NSInteger i = 0; i < 100000; i ++) {
                    
                }
                //卖出去一张票
                self.totalCount = count - 1;
                NSLog(@"%@ 卖出去一张票  还剩%zd张票",[NSThread currentThread].name,self.totalCount);
            }else {
                NSLog(@"票卖完了");
                break;
            }
        }
    }
       
   
}

互斥锁的优缺点

优点:能有效防止因多线程抢夺资源造成的数据安全问题

缺点:需要消耗大量的CPU资源

 

互斥锁的使用前提:多条线程抢夺同一块资源 

相关专业术语:线程同步,多条线程按顺序地执行任务

互斥锁,就是使用了线程同步技术

原子和非原子属性

OC在定义属性时有nonatomic和atomic两种选择

atomic:原子属性,为setter方法加锁(默认就是atomic)

nonatomic:非原子属性,不会为setter方法加锁

1 @property (assign, atomic) int age;
2 
3 - (void)setAge:(int)age
4 { 
5 
6     @synchronized(self) { 
7        _age = age;
8     }
9}

原子和非原子属性的选择

nonatomic和atomic对比

atomic:线程安全,需要消耗大量的资源(并非是真正的线程安全 更准确的说应该是读写安全,但并不是线程安全的,因为别的线程还能进行读写之外的其他操作。线程安全需要开发者自己来保证。)

nonatomic:非线程安全,适合内存小的移动设备

 

iOS开发的建议

所有属性都声明为nonatomic

尽量避免多线程抢夺同一块资源

尽量将加锁、资源抢夺的业务逻辑交给服务器端处理,减小移动客户端的压力

 

posted @ 2018-03-05 22:54  幻影-2000  阅读(946)  评论(0编辑  收藏  举报