一.线程基础概念
1.线程生命周期
- 线程生命周期 新建-就绪-运行-死亡,运行中可以进入阻塞状态
- 线程执行完任务会自动销毁(死亡),当然可以手动让线程强制退出
2.线程安全
解决卖票问题:
- @synchronize包裹代码块或者atomic修饰公用属性
- @synchronized(self)包裹的代码块只允许一个线程执行,当线程执行到synchronized,使用self加锁,其他线程不能进入。执行完之后从排队的线程中唤醒第一个线程执行。
- (void)viewDidLoad {
[super viewDidLoad];
self.tickets=10000;
NSThread * thread1=[[NSThread alloc]initWithTarget:self selector:@selector(saleTicket) object:nil];
thread1.name=@"t1";
NSThread * thread2=[[NSThread alloc]initWithTarget:self selector:@selector(saleTicket) object:nil];
thread2.name=@"t2";
NSThread * thread3=[[NSThread alloc]initWithTarget:self selector:@selector(saleTicket) object:nil];
thread3.name=@"t3";
[thread1 start];
[thread2 start];
[thread3 start];
}
-(void)saleTicket
{
while (YES) {
@synchronized(self){
if(self.tickets>0)
{
self.tickets--;
NSLog(@"%@:%d",[NSThread currentThread].name,self.tickets);
}else
{
NSLog(@"卖完了 %@:%d",[NSThread currentThread].name,self.tickets);
break;
}
}
}
}
GCD和NSOperation是IOS最常用的两种多线程方式,这里跟大家分享一下使用中的心得。
二.GCD学习笔记
1.dispatch_sync—同步操作
dispatch_queue_t concurrentQueue =dispatch_queue_create("my.concurrent.queue”,DISPATCH_QUEUE_CONCURRENT);
NSLog(@"1");
dispatch_sync(concurrentQueue, ^(){
NSLog(@"2");
[NSThread sleepForTimeInterval:10];
NSLog(@"3");
});
NSLog(@"4”);
输出信息:12(10s后)34
注:sync包裹的代码块阻塞住当前线程执行代码,包裹的代码块本身依然是顺序执行的
2.dispatch_async—异步操作
dispatch_queue_t concurrentQueue = dispatch_queue_create("my.concurrent.queue", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"1");
dispatch_async(concurrentQueue, ^(){
NSLog(@"2");
[NSThread sleepForTimeInterval:5];
NSLog(@"3");
});
NSLog(@"4”);
输出 1 4 2 (5s后)3
注:async不会阻塞当前线程的代码执行,包裹的代码块的本身依然是顺序执行的
特别注意:不能理解为同步是在主线程队列去执行的,而异步是开一个新的线程队列去执行代码!!同步方法调用就是在占用当前线程(可以是主线程也可以是子线程)资源去执行代码,执行完同步方法才可以执行后面代码;异步方法就是在当前线程(可以是主线程也可以是子线程)基础上开辟新的执行分支,让异步方法之后的代码不被异步方法代码阻塞掉。
3.GCD处理费时操作的方式
常见问题分析思路:主观感觉就是与UI处理相关的操作,比如说点击滑动如果出现卡顿,可能是主线程执行耗时操作导致。
dispatch_async(dispatch_get_global_queue(0, 0), ^{
//处理耗时操作的代码块...
//通知主线程刷新
dispatch_async(dispatch_get_main_queue(), ^{
//回调或者说是通知主线程刷新,
});
});
4.队列的概念(GCD除了线程同步异步还有队列的概念)
同步异步是线程概念,串行并行是队列概念。
使用GCD的时候,我们会把需要处理的任务放到Block里面,然后讲任务追加到相应的队列里面,这个队列,叫Dispatch Queue
。然而,存在于两种Dispatch Queue
,一种等待上一个任务执行完毕再执行下一个任务的是串行队列(如果上面任务没有执行完毕,下面的任务就不能执行,造成死锁的现象),一种是不需要等待上一个任务执行完毕,就能执行下一个的并行队列。GCD为我们提供两个队列,
dispatch_get_global_queue//全局并行队列
dispatch_get_main_queue//主串行队列
dispatch_queue_t concurrentQueue =dispatch_queue_create("my.concurrent.queue”,DISPATCH_QUEUE_SERIAL);//—串行队列
dispatch_queue_t concurrentQueue =dispatch_queue_create("my.concurrent.queue”,DISPATCH_QUEUE_CONCURRENT);//并行队列
那么,这两种队列和全局并行对列以及主线程串行队列的区别在哪呢,举例说明。
a.定义同步异步两种方法,循环打印3次当前线程
-(void)async:(dispatch_queue_t)queue
{
NSLog(@"begin %@",[NSThread currentThread]);
for (int i=0; i<3; i++) {
dispatch_async(queue, ^{
NSLog(@"async:%@",[NSThread currentThread]);
});
}
NSLog(@"end %@",[NSThread currentThread]);
}
-(void)sync:(dispatch_queue_t)queue
{
NSLog(@"begin %@",[NSThread currentThread]);
for (int i=0; i<3; i++) {
dispatch_sync(queue, ^{
NSLog(@"sync:1 %@",[NSThread currentThread]);
});
}
NSLog(@"end %@",[NSThread currentThread]);
}
b.创建四种队列,在主线程中分别调用同步异步方法,查看打印结果。
//1.系统主线程串行队列
dispatch_queue_t mainQueue = dispatch_get_main_queue();
//2.系统全局并行队列
dispatch_queue_t globelQueue = dispatch_get_global_queue(0, 0);
//3.自定义串行队列
dispatch_queue_t serialQueue = dispatch_queue_create("SERIAL", DISPATCH_QUEUE_SERIAL);
//4.自定义并行队列
dispatch_queue_t concurrentQueue = dispatch_queue_create("CONCURRENT", DISPATCH_QUEUE_CONCURRENT);
/*在主线程调用*/
//[self sync:mainQueue]; //begin 1
//[self sync:globelQueue]; //begin 1 sync 1 sync 1 sync 1 end 1
//[self sync:serialQueue]; //begin 1 sync 1 sync 1 sync 1 end 1
//[self sync:concurrentQueue]; //begin 1 sync 1 sync 1 sync 1 end 1
//[self async:mainQueue]; //begin 1 end 1 async 1 async 1 async 1
//[self async:globelQueue]; //begin 1 end 1 async 3 async 5 async 4
//[self async:serialQueue]; //begin 1 end 1 async 4 async 4 async 4
//[self async:concurrentQueue]; //begin 1 end 1 async 6 async 4 async 5
此时的结论是(此时):
1.主线程中调用同步方法,不开辟新线程。如果调用同步主线程,则会形成死锁(第一种情况)。
2.主线程中调用异步方法,除调用异步主线程外,都开辟新线程。
加工一下同步异步方法,让该同步异步方法在非主线程队列中执行。
-(void)async2:(dispatch_queue_t)queue
{
dispatch_async(queue, ^{
NSLog(@"begin %@",[NSThread currentThread]);
for (int i=0; i<3; i++) {
dispatch_async(queue, ^{
NSLog(@"async:%@",[NSThread currentThread]);
});
}
NSLog(@"end %@",[NSThread currentThread]);
});
}
-(void)sync2:(dispatch_queue_t)queue
{
dispatch_async(queue, ^{
NSLog(@"begin %@",[NSThread currentThread]);
for (int i=0; i<3; i++) {
dispatch_sync(queue, ^{
NSLog(@"sync:1 %@",[NSThread currentThread]);
});
}
NSLog(@"end %@",[NSThread currentThread]);
});
}
运行的结果是:
/*在子线程中调用*/
//[self sync2:mainQueue]; //begin 1
//[self sync2:globelQueue]; //begin 3 sync 3 sync 3 sync 3 end 3
//[self sync2:serialQueue]; //begin 4
//[self sync2:concurrentQueue]; //begin 3 sync 3 sync 3 sync 3 end 3
//[self async2:mainQueue]; //begin 1 end 1 async 1 async 1 async 1
//[self async2:globelQueue]; //begin 3 end 3 async 4 async 5 async 6
//[self async2:serialQueue]; //begin 4 end 4 async 4 async 4 async 4
//[self async2:concurrentQueue]; //begin 3 end 3 async 6 async 4 async 5
此时结论是:
1.子线程中调用同步方法,不开辟新线程。如果调用同步当前线程队列,则会形成死锁(第一种情况)。
2.子线程中调用异步方法,除调用异步当前队列外,都开辟新线程。
最终结论是:
1.在当前线程下(主线程或子线程),sync同步当前线程所在队列,如果是该队列是串行队列,一定会死锁,如果是并行队列,则不开辟新线程;async异步当前线程所在队列,如果是该队列是串行队列,不开辟新线程,如果是并行队列,则开辟新线程;async异步非当前线程所在队列,如果是该队列是串行队列,开辟一条新线程,如果是并行队列,则可能开辟多条新线程。
- 所以分析这类型问题的步骤是:
A.先看当前队列是什么队列;
比如主线程队列,串行子线程队列或者并行子线程队列。
B.再看是同步还是异步方式;
不管在什么队列执行同步,都不会开辟新线程;异步可能会开辟新线程。
C.再看同步异步的任务是在哪个队列;
如果异步在并行队列,可能由多条线程执行任务;异步在新的串行队列,只开辟一条新线程;异步到当前串行线程,不开辟新线程,但不会产生该线程阻塞;同步到当前串行线程,不开辟新线程,但发生死锁;同步到当前并行线程,不开辟新线程,但会产生线程阻塞。
WX20180611-152757.png
2.可以理解为全局并行队列是自定义并行队列的实例,主线程队列是自定义串行队列的实例,之所以在例1中展示不同是因为当前运行程序的线程就是主线程队列而已。
补充:全局队列和自定义并行队列的区别:
全局队列是系统为我们创建好的并行队列;
使用dispatch_barrier的地方只能使用自定义并行队列。
3.串行和并行是指存放任务的队列,区别在于,如果每个队列里面有多个任务,开辟新线程去执行任务时,并行开辟多个线程,串行只开辟一个线程。
理解了这点,那么,我们看几个多线程的例子就很容易理解输出结果了。
例子1
NSLog(@"1"); // 任务1
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"2"); // 任务2
});
NSLog(@"3"); // 任务3
结果,控制台输出:
1
例子2
NSLog(@"1"); // 任务1
dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
NSLog(@"2"); // 任务2
});
NSLog(@"3"); // 任务3
结果,控制台输出
1
2
3
例子3
dispatch_queue_t queue = dispatch_queue_create("com.demo.serialQueue", DISPATCH_QUEUE_SERIAL);
NSLog(@"1"); // 任务1
dispatch_async(queue, ^{
NSLog(@"2"); // 任务2
dispatch_sync(queue, ^{
NSLog(@"3"); // 任务3
});
NSLog(@"4"); // 任务4
});
NSLog(@"5"); // 任务5
结果,控制台输出:
1
5
2
// 5和2的顺序不一定
例子4
NSLog(@"1"); // 任务1
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"2"); // 任务2
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"3"); // 任务3
});
NSLog(@"4"); // 任务4
});
NSLog(@"5"); // 任务5
结果,控制台输出:
1
2
5
3
4
// 5和2的顺序不一定
5.dispatch_after 延后执行
dispatch_after 只是延时提交到任务队列,不是提交后延时队列执行。
6.dispatch_apply
快速迭代,可以进行相互不关联耗时的多个相同操作
//创建多个线程(包含主线程)进行遍历,不能使用dispatch_get_main_queue,否则死锁
dispatch_apply(10, dispatch_get_global_queue(0, 0), ^(size_t i) {
NSLog(@"i:%zd %@",i,[NSThread currentThread]);
});
7.dispatch_group
队列组,比队列强大的是可以监听队列的任务状态,比如下载两张图合成一张图片,必须监听任务完成状态。
dispatch_group_t group=dispatch_group_create();
dispatch_queue_t queue1=dispatch_queue_create("a", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t queue2=dispatch_queue_create("b", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_async(group, queue1, ^{
NSLog(@"下载1");
});
dispatch_group_async(group, queue1, ^{
NSLog(@"下载2");
});
dispatch_group_async(group, queue2, ^{
NSLog(@"下载3");
});
dispatch_group_async(group, queue2, ^{
NSLog(@"下载4");
});
//dispatch_get_main_queue表示执行完毕后在主线程执行代码
dispatch_group_notify(group,dispatch_get_main_queue(), ^{
NSLog(@"完成队列任务:合成图片");
});
8.单例模式
注意点:
1.static 修饰_instance,防止对象释放
2.使用dispatch_once重写allocWithZone,防止多线程创建不同对象
3.重写copyWithZone,mutableCopyWithZone,保证copy的时候也是同一个对象
4.写一个类方法获得实例
static JSXCacheTool *_instance;
+(instancetype)allocWithZone:(struct _NSZone *)zone
{
// @synchronized (self) {
// // 为了防止多线程同时访问对象,造成多次分配内存空间,所以要加上线程锁
// if (_instance == nil) {
// _instance = [super allocWithZone:zone];
// }
// return _instance;
// }
// 也可以使用一次性代码
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
if (_instance == nil) {
_instance = [super allocWithZone:zone];
}
});
return _instance;
}
// 为了使实例易于外界访问 我们一般提供一个类方法
// 类方法命名规范 share类名|default类名|类名
+(instancetype)shareCacheTool
{
//return _instance;
// 最好用self 用Tools他的子类调用时会出现错误
return [[self alloc]init];
}
// 为了严谨,也要重写copyWithZone 和 mutableCopyWithZone
-(id)copyWithZone:(NSZone *)zone
{
return _instance;
}
-(id)mutableCopyWithZone:(NSZone *)zone
{
return _instance;
}
三.NSOperation学习笔记
NSOperation是基于GCD的延伸。
NSOperation做GCD的事情:
//主线程队列
NSOperationQueue * queue = [NSOperationQueue mainQueue]
//自定义并行队列
NSOperationQueue * queue = [[NSOperationQueue alloc]init];
//自定义串行队列
NSOperationQueue * queue = [[NSOperationQueue alloc]init];
queue.maxConcurrentOperationCount=1;
默认添加到队列的操作都会开辟新线程。
创建NSOperation的方法可以继承NSOperation类,重新main方法,也可以使用NSBlockOperation,NSInvocationOperation。
NSOperation做GCD以外的事情:
1.给NSOperation添加依赖,让任务可以按照自己想要的优先级顺序执行。
2.设置NSOperationQueue最大并发数。
3.控制NSOperation的状态(暂停,恢复,取消)
网友评论