NSThread
每个iOS应用程序都有个专门用来更新显示UI界面、处理用户触摸事件的主线程,因此不能将其他太耗时的操作放在主线程中执行,不然会造成主线程堵塞
创建一条线程
方式一 动态方法
// 1.创建子线程
/*
Target: 子线程需要调用谁的方法
selector: 被子线程调用的方法
object: 调用方法时, 给方法传递的参数
*/
// 注意: 如果线程正在执行, 那么系统会自动强引用NSThread 当线程中的任务执行完毕, 系统会自动释放线程, 对线程进行一次release操作
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(demo:) object:@"lnj"];
// 给线程取名字
thread.name = @"子线程1";
// 设置线程的优先级, 取值范围0.0~1.0, 1.0是最高, 默认就是0.5
// 注意点: 线程的优先级越高, 那么被CPU调度的可能性就越大 但是并不代表着, 优先级高的一定会先执行
thread.threadPriority = 0.0;
// 2.启动线程 如果通过alloc/init创建NSThread, 那么需要手动启动线程
[thread start];
方式二 静态方法
/*
优点:
- 使用简便
- 如果通过detach方法创建线程, 不需要手动调用start启动线程 \ 系统会自动启动
缺点:
- 不可以进行其它设置
应用场景:
- 如果仅仅需要简单的开启一个子线程执行一些操作, 不需要对子线程进行其它设置, 那么推荐使用detach方法创建线程
*/
[NSThread detachNewThreadSelector:@selector(demo:) toTarget:self withObject:@"canshu"];
方式三 隐式创建线程
// 系统会自动在后台给我们开启一个子线程, 并且会自动启动该线程执行任务
[self performSelectorInBackground:@selector(demo:) withObject:@"oooo"];
线程通信
场景:从网络下载一张图片,然后返回主线程更新UI
开启一个子线程下载图片
[self performSelectorInBackground:@selector(downlod) withObject:nil];
实现下载图片方法
- (void)downlod
{
NSLog(@"%@", [NSThread currentThread]); // 打印当前线程
// 1.下载图片
NSURL *url = [NSURL URLWithString:@"http://pic.4j4j.cn/upload/pic/20130531/07ed5ea485.jpg"];
NSData *data = [NSData dataWithContentsOfURL:url];
// 2.将二进制转换为图片
UIImage *image = [UIImage imageWithData:data];
}
拿到图片后 返回主线程更新UI 方式一
/*
waitUntilDone:
YES: 如果传入YES, 那么会等待updateImage方法执行完毕, 才会继续执行后面的代码
NO: 如果传入NO, 那么不会等待updateImage方法执行完毕, 就可以继续之后后面的代码
*/
// 可以在指定的线程中, 调用指定对象的指定方法
[self performSelector:@selector(updateImage:) onThread:[NSThread mainThread] withObject:image waitUntilDone:YES];
拿到图片后 返回主线程更新UI 方式二 工作中常用
// 这个方法也就不需要再重新实现更新UI的方法了
[self.imageView performSelectorOnMainThread:@selector(setImage:) withObject:image waitUntilDone:YES];
实现更新UI的方法
- (void)updateImage:(UIImage *)image
{
NSLog(@"%@", [NSThread currentThread]);
self.imageView.image = image;
}
在当前线程执行操作
[self performSelector:@selector(run) withObject:nil];
多条线程在同一时间访问同一块资源容易出现安全隐患
模拟场景:12306服务器设置了20张从北京到上海火车票,有三个售票员用他们的电脑进行售票,这里要需要用到互斥锁 它的作用就是锁住这块资源让它在同一时间只能让一条线程访问 接下来先测试加锁与不加锁的区别
创建3条线程代表3个售货员
- (void)viewDidLoad {
[super viewDidLoad];
// 0 初始化票数
self.totalCount = 100;
// 1 创建3个售票员
NSThread *thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(saleTicket) object:nil];
thread1.name = @"售票员1";
self.thread1 = thread1;
NSThread *thread2 = [[NSThread alloc] initWithTarget:self selector:@selector(saleTicket) object:nil];
thread2.name = @"售票员2";
self.thread2 = thread2;
NSThread *thread3 = [[NSThread alloc] initWithTarget:self selector:@selector(saleTicket) object:nil];
thread3.name = @"售票员3";
self.thread3 = thread3;
}
开始售票
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
// 2.让3个售票员同事售票
[self.thread1 start];
[self.thread2 start];
[self.thread3 start];
}
售票进行中 不加锁
- (void)saleTicket
{
while (1) {
NSLog(@"欢迎光临");
// 1.查询剩余的票数
NSUInteger count = self.totalCount;
// 2.判断是否还有余票
if (count > 0) {
// 线程1 让线程处于休眠状态 模拟售票时间为0.1秒
[NSThread sleepForTimeInterval:0.1];
// 2.1卖票
self.totalCount = count - 1; // 99
NSLog(@"%@卖了一张票, 还剩%zd票", [NSThread currentThread].name, self.totalCount);
}else
{
// 3.提示客户, 没有票了
NSLog(@"对不起, 没有票了");
break;
}
}
}
看打印结果
明显线程没有获取到最新的值 造成数据访问错误
Paste_Image.png
售票进行中 加锁
// 售票方法
- (void)saleTicket
{
while (1) {
NSLog(@"欢迎光临");
@synchronized (self) { //@synchronized 为互斥锁 self可以为任何对象 但必须是同一对象 保证单例
// 1.查询剩余的票数
NSUInteger count = self.totalCount;
// 2.判断是否还有余票
if (count > 0) {
// 线程1 让线程处于休眠状态 模拟售票时间为0.1秒
[NSThread sleepForTimeInterval:0.1];
// 2.1卖票
self.totalCount = count - 1; // 99
NSLog(@"%@卖了一张票, 还剩%zd票", [NSThread currentThread].name, self.totalCount);
}else
{
// 3.提示客户, 没有票了
NSLog(@"对不起, 没有票了");
break;
}
}
}
}
这下就没有问题了
Paste_Image.png