多线程003------NSThread

1. NSThread

创建线程

  • 方式1
    //创建线程对象
    NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(demo) object:nil];
    //开启线程
    [thread start];
    
  • 方式2(类方法)

    [NSThread detachNewThreadSelector:@selector(demo) toTarget:self withObject:nil];
    
  • 方式3(隐式创建)

    [self performSelectorInBackground:@selector(demo) withObject:nil];

2. Thread Status(线程状态)

  • 新建 :线程对象在内存中被创建出来
  • 就绪 :线程除了CPU资源以外都已到位,等着被CPU调度执行
  • 运行 :CPU正在执行该线程
  • 阻塞 :除了CPU资源外还有其他的资源没有准备好
  • 死亡 :线程被销毁,内存释放
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
    //创建线程
    NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(demo) object:nil];
    //开启线程 -- 就绪状态
    [thread start];
}

- (void)demo{
    //睡眠 -- 阻塞状态
    [NSThread sleepForTimeInterval:1];

    //就绪
    for (int i = 0; i< 20; i++) {
        if (i == 5) {
            [NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:2]];
            NSLog(@"我睡醒了");
        }
        NSLog(@"hello %@ %d",[NSThread currentThread],i);

        if(i == 10){
            //退出线程 -- 死亡状态
            NSLog(@"ni死了");
            [NSThread exit];
        }
    }
}


3. Thread property(线程的常用属性)

  • 线程名字 name:为了调试方便
    thread.name = @"myThread";  //线程的名字
    
  • 线程的优先级 threadPriority:有更大几率被CPU执行到,不保证先被执行到

    thread.threadPriority = 1;  //线程的优先级 0.0-1.0 1.0的优先级最高
    
  • 判断当前是不是主线程

    [[NSThread currentThread] isMainThread]

4. 多线程安全问题

多线程访问共享资源的问题

多个线程同时对同一个变量进行读和写会出现数据问题

问题演示

卖票

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
    NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(buyTicket) object:nil];
    [thread start];


    NSThread *thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(buyTicket) object:nil];
    [thread1 start];
}
- (void)buyTicket{
    while (YES) {
        [NSThread sleepForTimeInterval:1];
            //进入 锁上1
            if (self.tickets > 0) {
                self.tickets--;
                NSLog(@"卖了一张票%@, %d",[NSThread currentThread],self.tickets);
                continue;
            }
            //出去 打开锁0
        NSLog(@"你来晚了卖晚了");
        break;
}

解决方案

  • 互斥锁:把读和写的操作当成不可分割的操作(也叫同步锁)
    @synchronized(NSObject){}
    
- (void)buyTicket{
    while (YES) {
        [NSThread sleepForTimeInterval:1];
        //每一个对象内部都有一把锁,默认锁是打开1 锁上0
        //互斥锁 ---> 线程同步
        @synchronized(self){
            //进入 锁上1
            if (self.tickets > 0) {
                self.tickets--;
                NSLog(@"卖了一张票%@, %d",[NSThread currentThread],self.tickets);
                continue;
            }
            //出去 打开锁0
        }
        NSLog(@"你来晚了卖晚了");
        break;
}
  • 原子属性atomic(自旋锁):多个线程可以同时读取变量,但是只有一个线程能够对该变量赋值
    @property(atomic) NSObject *obj;
    
  • 非原子属性nonatomic:多个线程可以同时对属性进行读取和赋值

互斥锁和自旋锁的异同

  • 自旋锁里面只锁住赋值操作(setter),互斥锁没有限制
  • 线程访问一个关闭着的锁的时候,互斥锁是线程休眠,自旋锁是无限判断锁的状态,比较耗资源
  • 锁一段比较耗时任务时,用互斥锁,不要用自旋锁
  • 对性能都有损耗

用锁的目的

保证数据的正确性为第一优先,可以损失点性能

线程安全

线程安全:在多个线程进行读写操作时,仍然能够保证数据的正确。 线程不安全:多个线程对同一个全局变量进行读写操作,会产生问题。

UI线程

  • 几乎所有UIKit的类都不是线程安全的
  • 在主线程上执行所有更新UI的操作

5. 下载图片demo

  • 步骤1:创建界面
//初始化控制器内部的视图 storyboard 或者xib
- (void)loadView{
    UIScrollView *scrollView = [[UIScrollView alloc] initWithFrame:[UIScreen mainScreen].bounds];
    scrollView.backgroundColor = [UIColor grayColor];
    self.scrollView = scrollView;
    self.view = scrollView;

    UIImageView *imageView =  [[UIImageView alloc] init];
    self.imageView = imageView;
    //addSubview 把控件添加到了self.view.subviews 数组中
    [self.view addSubview:self.imageView];
}
  • 步骤2:网络获取图片
- (void)downloadImage{
    //加载一张网络图片
    NSURL *url = [NSURL URLWithString:@"http://f.hiphotos.baidu.com/image/pic/item/4610b912c8fcc3ce6ea8e7d09045d688d53f20ec.jpg"];
    NSData *data = [NSData dataWithContentsOfURL:url];

    self.image = [[UIImage alloc] initWithData:data];

    NSLog(@"---%@",[NSThread currentThread]);
}
  • 步骤3:开启线程运行获取图片方法
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(downloadImage) object:nil];
    [thread start];
  • 步骤4:子线程通知主线程更新UI
[self performSelectorOnMainThread:@selector(updateUI) withObject:nil waitUntilDone:YES];
  • 步骤5:更新UI
- (void)updateUI{
    NSLog(@"%@",[NSThread currentThread]);
// [NSThread sleepForTimeInterval:1];
    NSLog(@"updateUI");
    //图片下载完成
    self.imageView.image = self.image;
    //让imageView的大小和它内部的image的大小一样
    [self.imageView sizeToFit];

    self.scrollView.contentSize = self.image.size;

}

6. 线程见的通信

定义:多个线程之间需要经常进行通信

使用场景

  1. 一个线程传递数据给另一个线程
  2. 一个线程执行完任务后,需要另一个线程继续执行任务

常用方法:

- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait;


你可能感兴趣的:(多线程,线程安全,NSThread,多线程安全问题)