在iOS中实现多线程有三种方法NSThread、NSOperation、GCD,下面我们先来看一下 NSThread
一、NSThread中的操作
创建、启动线程
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];
// 线程一启动,就会在线程thread中执行self的run方法
[thread start];
[NSThread detachNewThreadSelector:@selector(run) toTarget:self withObject:nil]; //创建线程后自动启动线程
[self performSelectorInBackground:@selector(run) withObject:nil]; //隐式创建并启动线程
/**
上述2种创建线程方式的优缺点
优点:简单快捷
缺点:无法对线程进行更详细的设置
*/
//主线程相关用法
+ (NSThread *)mainThread; // 获得主线程
- (BOOL)isMainThread; // 是否为主线程
+ (BOOL)isMainThread; // 是否为主线程
//获得当前线程
NSThread *current = [NSThread currentThread];
//线程的调度优先级:调度优先级的取值范围是0.0 ~ 1.0,默认0.5,值越大,优先级越高
+ (double)threadPriority;
+ (BOOL)setThreadPriority:(double)p;
//设置线程的名字
- (void)setName:(NSString *)n;
- (NSString *)name;
二、下面我们看一下举例
首先我们看一下古老的方法创建thread
//古老的方式创建,要引入
- (void)test{
UITextView *text = [[UITextView alloc] initWithFrame:CGRectMake(20, 130, 280, 80)];
text.layer.borderWidth = .5;
text.layer.borderColor = [UIColor blueColor].CGColor;
text.layer.cornerRadius = 4;
[self.view addSubview:text];
UIButton *button = [[UIButton alloc] initWithFrame:CGRectMake(20, 64, 280, 50)];
button.backgroundColor = [UIColor greenColor];
[button addTarget:self action:@selector(btnClick) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:button];
}
- (void)btnClick{
//1.获取当前线程
NSThread *current=[NSThread currentThread];
//主线程
NSLog(@"btnClick----%@",current);
//2.使用for循环执行一些耗时操作
pthread_t thread;
pthread_create(&thread, NULL, run, NULL);
}
void *run(void *data){
//获取当前线程,是新创建出来的线程
NSThread *current=[NSThread currentThread];
for (int i=0; i<10000; i++) {
NSLog(@"btnClick---%d---%@",i,current);
}
return NULL;
}
这里没有造成线程阻塞,在打印出线程数据的同时还可以对textView进行操作。
下面看一下NSThread创建的过程
//使用NSThread创建
- (void)test{
UITextView *text = [[UITextView alloc] initWithFrame:CGRectMake(20, 130, 280, 80)];
text.layer.borderWidth = .5;
text.layer.borderColor = [UIColor blueColor].CGColor;
text.layer.cornerRadius = 4;
[self.view addSubview:text];
UIButton *button = [[UIButton alloc] initWithFrame:CGRectMake(20, 64, 280, 50)];
button.backgroundColor = [UIColor greenColor];
[button addTarget:self action:@selector(btnClick) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:button];
}
-(void)btnClick{
//1.获取当前线程
NSThread *current=[NSThread currentThread];
//主线程
NSLog(@"btnClick----%@",current);
//获取主线程的另外一种方式
NSThread *main=[NSThread mainThread];
NSLog(@"主线程-------%@",main);
//2.执行一些耗时操作
NSThread *thread1=[[NSThread alloc]initWithTarget:self selector:@selector(run:) object:@"线程A"];
//为线程设置一个名称
thread1.name=@"线程A";
//开启线程
[thread1 start];
NSThread *thread2=[[NSThread alloc]initWithTarget:self selector:@selector(run:) object:@"线程B"];
//为线程设置一个名称
thread2.name=@"线程B";
//开启线程
[thread2 start];
//创建完线程直接(自动)启动
[NSThread detachNewThreadSelector:@selector(run:) toTarget:self withObject:@"创建完线程直接(自动)启动"];
//在后台线程中执行===在子线程中执行
[self performSelectorInBackground:@selector(run:) withObject:@"隐式创建"];
}
- (void)run:(NSString *)string{
//获取当前线程
NSThread *current=[NSThread currentThread];
//打印输出
for (int i=0; i<10; i++) {
NSLog(@"run---%@---%@",current,string);
}
}
三、线程间的通信
线程间通信:在1个进程中,线程往往不是孤立存在的,多个线程之间需要经常进行通信,1个线程传递数据给另1个线程,在1个线程中执行完特定任务后,转到另1个线程继续执行任务。
线程间通信常用方法
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait;
下面我们看一下一般图片下载的操作,当我们加载图片的时候都比较耽误时间,那我们就新建一个线程,然后在新线程中下载图片,回到主线程中刷新页面。
- (void)viewDidLoad{
[super viewDidLoad];
self.imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 320, 480)];
[self.view addSubview:imageView];
[NSThread detachNewThreadSelector:@selector(loadImage) toTarget:self withObject:nil];
NSLog(@"11111111111");
}
- (void)loadImage{
// 代码在主线程中执行的
// 把网络上的图片 拿到本地data
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:@"http://f.hiphotos.baidu.com/image/pic/item/29381f30e924b8990c9439396c061d950a7bf603.jpg"]];
// 是否等 主线程的方法执行过之后 再继续执行 分线程的代码
[self performSelectorOnMainThread:@selector(showImage:) withObject:data waitUntilDone:YES];
NSLog(@"222222222 %d", [NSThread isMainThread]);
}
- (void)showImage:(NSData *)data{
self.imageView.image = [UIImage imageWithData:data];
NSLog(@"33333333 %d", [NSThread isMainThread]);
}
四、多线程的安全隐患
资源共享,1块资源可能会被多个线程共享,也就是多个线程可能会访问同一块资源,比如多个线程访问同一个对象、同一个变量、同一个文件,当多个线程访问同一块资源时,很容易引发数据错乱和数据安全问题。
比如我们在银行存钱的同时取钱,再比如我们买票的时候几个人同时买可能就会出现这种问题,下面我们看一下问题代码。
- (void)test{
a = 5;
//开启多个线程,模拟售票员售票
[NSThread detachNewThreadSelector:@selector(buyTickets1) toTarget:self withObject:nil];
[NSThread detachNewThreadSelector:@selector(buyTickets2) toTarget:self withObject:nil];
}
// 线程同步
// 甲买5张票
- (void)buyTickets1{
NSLog(@"甲 准备买5张票");
if (a >= 5){
NSLog(@"票至少有5张,可以给甲办理买票");
a = a - 5;
NSLog(@"甲成功购买5张票,还剩%d张",a);
}else{
NSLog(@"票不够5张,不能买");
}
}
// 乙 买1 张票
- (void)buyTickets2{
// 线程同步块
NSLog(@"乙 准备买1张票");
if (a >= 1){
NSLog(@"票至少有1张,可以给乙办理买票");
a = a - 1;
NSLog(@"乙成功购买1张票,还剩%d张",a);
}else{
NSLog(@"票不够1张,不能买");
}
}
来看打印结果,很多数都被买了多次,这怎么可能!!!
五、多线程的安全隐患的解决办法:线程锁
那么我们怎么来解决这个问题呢?这里就为我们提供了一个工具---线程锁,当一个线程在对一个数据进行操作的时候会给这个线程添加一个“锁”锁住,不允许别的线程进行操作,等到这次的操作完成之后再解锁。其实线程锁有很多种,但是只能添加一把锁,我们这里就做一下演示。
- (void)test{
a = 5;
//开启多个线程,模拟售票员售票
[NSThread detachNewThreadSelector:@selector(buyTickets1) toTarget:self withObject:nil];
[NSThread detachNewThreadSelector:@selector(buyTickets2) toTarget:self withObject:nil];
// 要用同一把锁
// _lock = [[NSLock alloc] init];
}
// 线程同步
// 甲买5张票
- (void)buyTickets1{
// 锁是一个标志
// [_lock lock];
@synchronized (self){
NSLog(@"甲 准备买5张票");
if (a >= 5){
NSLog(@"票至少有5张,可以给甲办理买票");
a = a - 5;
NSLog(@"甲成功购买5张票,还剩%d张",a);
}else{
NSLog(@"票不够5张,不能买");
}
}
// [_lock unlock];
}
// 乙 买1 张票
- (void)buyTickets2{
// [_lock lock];
@synchronized (self){
// 线程同步块
NSLog(@"乙 准备买1张票");
if (a >= 1){
NSLog(@"票至少有1张,可以给乙办理买票");
a = a - 1;
NSLog(@"乙成功购买1张票,还剩%d张",a);
}else{
NSLog(@"票不够1张,不能买");
}
}
// [_lock unlock];
}
这里介绍了两种方法,一种是NSLock,另一种是自动的锁 synchronized,得到的结果都是一样的