ios多线程操作(2)—— NSThread的应用

ios多线程操作(二)—— NSThread的应用
一、基本使用
1、三种创建子线程的方法
(1)NSThread直接创建,一个NSThread对象就代表一条线程
// 实例化一个 NSThread 对象
   
NSThread *t1 = [[NSThread alloc] initWithTarget:self selector:@selector(longOperation:) object:@"NSThread"];
   
// 启动线程
    [t1 start];
自定义一个耗时操作:
// 耗时操作
- (
void)longOperation:(id)obj {
   
for (int i = 0; i < 10; ++i) {
       
NSLog(@"%@ %d -- %@", [NSThread currentThread], i, obj);
    }
}
(2)、创建线程后自动启动线程
 // "隐式"的多线程方法!跟 detach 类方法类似,可以直接开启线程执行方法!
    [self performSelectorInBackground:@selector(longOperation:) withObject:@"perform"];
该方法不需要调用start方法
(3)隐式创建并启动线程
// performSelectorInBackground 可以让任意一个 NSObject 都具有在后台执行线程的能力!
    // 会让代码写起来非常灵活!
NSObject *obj = [[NSObject alloc] init]
[obj performSelectorInBackground:@selector(loadData) withObject:nil];
2、主线程相关用法

+ (NSThread *)mainThread; // 获得主线程

- (BOOL)isMainThread; // 是否为主线程

+ (BOOL)isMainThread; // 是否为主线程

3、获得当前线程 
NSThread*current = [NSThread currentThread];
修改主线程的栈区大小:
     // 修改主线程的栈区大小 => 1M
    [NSThread currentThread].stackSize = 1024 * 1024;
4、线程的调度优先级

+ (double)threadPriority;

+ (BOOL)setThreadPriority:(double)p;

- (double)threadPriority;

- (BOOL)setThreadPriority:(double)p;
     优先级的取值范围为0.0-1.0,线程默认优先级是0.5,最高是1.0。
     优先级高只能说明 CPU 在调度的时候,会优先调度,并不意味着优先级低的就不被调用或者后调用!
      在多线程开发的时候,不要去做不同线程之间执行的比较!线程内部的方法都是各自独立执行的,如果设置了优先级,那么就会有可能出现低优先级的线程阻塞高优先级的线程,也就是优先级反转!在ios开发中,多线程最主要的目的就是把耗时操作放在后台执行
5、为线程设置名字

- (void)setName:(NSString *)n;

- (NSString*)name;


二、线程状态
     每一个新建出来的线程被添加到可调度线程池中时就会处于就绪(runnable)状态,当CPU调度当前线程时,该线程会处于运行(running)状态,如果调用了sleep方法或者是等待同步锁时,该线程就会被移出可调度线程池,此时该线程处于阻塞(blocked)状态,当该线程sleep时间到时或得到同步锁又会被移入可调度线程池,此时该线程又处于就绪状态,当线程任务执行完毕或者异常强制退出时,该线程会处于死亡(dead)状态,如下图
ios多线程操作(2)—— NSThread的应用

启动线程:
- (void)start;
此时线程从就绪状态到运行状态

阻塞线程

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

+ (void)sleepForTimeInterval:(NSTimeInterval)ti;
进入阻塞状态

强制停止线程

+ (void)exit;


此方法不会给任何机会去清理线程执行过程中分配的资源,也就是说一旦退出当前线程,后续所有代码不会执行(若后续代码中又释放内存的操作,那会相当的危险,会造成内存泄露),当使用C语言分配内存的时候要在该方法前适当的释放内存。

三、资源抢夺
     多线程会有安全隐患。1块资源可能会被多个线程共享,也就是多个线程可能会访问同一块资源,比如多个线程访问同一个对象、同一个变量、同一个文件,当多个线程访问同一块资源时,很容易引发数据错乱和数据安全问题。
     如何解决隐患问题——使用互斥锁
     假如有一个买票系统:
- (void)saleTickets {
    while (YES) {
        //  模拟延时
        [NSThread sleepForTimeInterval:1.0];
          @synchronized(self) {
            // 确认是否还有票(读取的动作)
            if (self.tickets > 0) {
                // 卖一张(写入动作)
                self.tickets--;
               
                //  输出剩余票数
                NSLog(@"剩余票数 %d %@", self.tickets, [NSThread currentThread]);
            }
else {
               
NSLog(@"没票了 %@", [NSThread currentThread]);
               
break;
            }
        }
    }
}

互斥锁的参数:
         1、self 本质上是任意一个 NSObject 都可以当成锁!
         2、 锁对象必须能够保证所有线程都能够访问(所以不可能是某个线程中的局部变量)
         3、 如果在程序中,只有一个位置需要加锁,可以使用self对象
使用互斥锁的效果就是卖票正确了,但效率却是下降了,在ios开发中尽量不要区抢夺资源,也不要区使用同步锁

四、原子属性,互斥锁与自旋锁
(1)原子属性
nonatomic : 非原子属性
atomic :原子属性,是默认属性
    * 是在多线程开发时,保证多个线程在"写入"的时候,能够保证只有一条线程执行写入操作!
    * 是一个单(线程)写多(线程)读的多线程技术
    * 原子属性,解决不了卖票问题,因为卖票的读写都需要锁定
    * 有可能会出现"脏数据",重新读取一下就可以!
    * 原子属性内部也有一把"锁"
    * 原子属性的锁的性能要比互斥锁高!
(2)自旋锁与互斥锁
每一个原子属性里面都会有一个锁,称之为自旋锁
共同点:
    都是保证同一时间,只有一条线程能够执行锁定范围的代码
区别:
    互斥锁:如果发现代码已经被(其他线程)锁定,当前线程会进入休眠状态,等锁解除之后,重新被唤醒
    自旋锁:如果发现代码已经被(其他线程)锁定,当前线程会以死循环的方式,一直判断锁是否解除,一旦接触立即执行!该锁,适合锁定非常短的代码,能够保证更高的执行性能!
互斥锁性能很差,在开发中很少用,只要是用到锁,性能都不高
(3) 线程安全
如果一个属性,在多个线程执行的情况下,仍然能够保证得到正确的结果,就叫做线程安全!要实现线程安全,就必须要使用到"锁"-> 性能不好!
 iso开发中UI 线程有个约定,所有UI更新,都需要在主线程上执行!
 原因:UIKit不是线程安全的!就是为了得到更高的性能!