zoukankan      html  css  js  c++  java
  • 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

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

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

     

  • 相关阅读:
    NABCD项目分析
    周总结6
    移动端展示
    暑期周进度报告(四)
    暑期周进度报告(三)
    暑期周进度报告(二)
    暑期周进度报告(一)
    《人件》阅读笔记02
    周学习进度报告(2020/06/05)
    2020春季软件工程课程总结
  • 原文地址:https://www.cnblogs.com/huanying2000/p/8511844.html
Copyright © 2011-2022 走看看