目录
一. 多线程的概念(程序, 进程, 线程)
二. 为什么使用多线程
三. 怎样创建线程(2种方法)
四. 怎样监听线程的结束, 取消线程
五. 线程的优先级
六. 线程的同步锁
- MyAccount类
- ViewController.m(此时执行就会出现取款超出余额的问题, 需要添加同步锁)
- 第一种添加线程锁的方法(在MyAccount类中的- (void)withDraw:(float)tmpMoney方法中用@synchronized(self)添加锁)
- 第二种添加线程锁的方法(设一个成员变量:NSLock的对象)
七. NSOperation创建线程
- NSInvocationOperation
- NSBlockOperation
- 自定义NSBlockOperation的子类
八. Block
- 没有返回值的block
- 有返回值, 返回值类型是基本类型(int为例)
- 有返回值, 返回值类型是基本类型(int为例)
- 测试
一. 多线程的概念
程序: 一段代码, 是一个静态的文件
进程: 一个运行起来的程序, 进程实惠占用内存的
线程: 进程的组成部分, 一个进程至少需要一个线程, 是进程处理逻辑的基本单元, iOS程序运行起来后, 默认创建一个主线程, 自动维护这个主线程; 如果需要使用多线程, 必须自己手动创建和维护
二. 为什么使用多线程
将下载或者数据库操作等放在了主线程里面, 会阻塞主线程, 造成一种假死的现象
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
UIButton *button1 = [MyUtility createButtonWithFrame:CGRectMake(100, 100, 80, 40) title:@"下载数据" backgroundImageName:nil target:self action:@selector(downloadAction)];
[self.view addSubview:button1];
// 按钮2: 点击打印一条信息
UIButton *button2 = [MyUtility createButtonWithFrame:CGRectMake(100, 200, 80, 40) title:@"点击" backgroundImageName:nil target:self action:@selector(clickButton)];
[self.view addSubview:button2];
}
- (void)downloadAction
{
NSLog(@"%s", __func__);
}
- (void)clickButton
{
// 让当前的线程睡眠
// 模拟下载数据需要的时间
[NSThread sleepForTimeInterval:10];
// 将下载放在了主线程里面, 会阻塞主线程
// 造成了一种假死的现象
// 类似下载或者数据库操作的逻辑, 占用时间加长, 需要在主线程以外的线程中去处理
NSLog(@"%s", __func__);
}
三. 怎样创建线程
1. 使用NSThread来创建线程1
// 1. 点击按钮, 创建一个线程
UIButton *btn1 = [MyUtility createButtonWithFrame:CGRectMake(100, 100, 80, 40) title:@"创建线程1" backgroundImageName:nil target:self action:@selector(createThreadOne)];
[self.view addSubview:btn1];
// 创建线程1
- (void)createThreadOne
{
// detachNewThreadSelector:<#(SEL)#> toTarget:<#(id)#> withObject:<#(id)#>
/*
创建了一个线程, 同时将线程启动
第一个参数: 线程的执行体方法
第二个参数: 线程执行体方法所属的对象
第三个参数: 线程的执行体方法的参数
*/
NSNumber *n = @100;
[NSThread detachNewThreadSelector:@selector(threadOne:) toTarget:self withObject:n];
}
// 线程1的执行体方法
- (void)threadOne:(NSNumber *)n
{
for (int i = 0; i < n.intValue; i++) {
NSLog(@"线程1:%d", i);
// 让当前线程睡眠
[NSThread sleepForTimeInterval:5];
}
}
2. 使用NSThread来创建线程2
// 2. 创建线程的第二种方式
UIButton *btn2 = [MyUtility createButtonWithFrame:CGRectMake(100, 200, 80, 40) title:@"创建线程2" backgroundImageName:nil target:self action:@selector(createThreadTwo)];
[self.view addSubview:btn2];
NSLog(@"%@", [NSThread currentThread].name);
- (void)createThreadTwo
{
/*
创建了一个线程, 线程没有自动启动
第一个参数: 线程执行体方法所属的对象
第二个参数: 线程的执行体方法
第三个参数: 线程的执行体方法的参数
*/
NSThread *thread2 = [[NSThread alloc] initWithTarget:self selector:@selector(threadTwo) object:nil];
// 设置线程的名字
thread2.name = @"下载线程";
// 手动启动线程
[thread2 start];
}
// 线程2的执行体方法
- (void)threadTwo
{
for (int i = 0; i < 100; i++) {
// 获取当前的线程对象
NSThread *currentThread = [NSThread currentThread];
NSLog(@"%@ %d", currentThread.name, i+1);
}
}
四. 怎样监听线程的结束, 取消线程
1. 第1种取消线程方法
#import "ViewController.h"
@interface ViewController ()
{
// 线程2是否取消
BOOL _isThreadTwoCancel;
}
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
// 创建2个线程, 并启动
// 第一个线程执行for循环, 第二个线程执行死循环
// 第一个线程结束后取消第二个线程
// 程序接收到第二个线程结束的信息后, 停止第二个线程的执行
// 创建第一个线程
[NSThread detachNewThreadSelector:@selector(threadOne) toTarget:self withObject:nil];
// 创建第二个线程
NSThread *t2 = [[NSThread alloc] initWithTarget:self selector:@selector(threadTwo) object:nil];
[t2 start];
}
- (void)threadOne
{
for (int i = 0; i < 1000; i++) {
NSLog(@"执行线程1");
if (i == 999) {
NSLog(@"线程1执行完毕");
// 取消线程2
_isThreadTwoCancel = YES;
}
}
}
- (void)threadTwo
{
int i = 0;
while (true) {
// 退出线程的方法需要放到循环里面
if (_isThreadTwoCancel) {
// 结束当前的线程
[NSThread exit];
}
NSLog(@"执行线程2: %d", i+1);
i++;
}
}
2. 第2种取消线程方法
@interface ViewController ()
{
…………………………………………………………………………………………
NSThread *_t2;
}
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
…………………………………………………………………………………………
// 创建第二个线程
// NSThread *t2 = [[NSThread alloc] initWithTarget:self selector:@selector(threadTwo) object:nil];
// [t2 start];
_t2 = [[NSThread alloc] initWithTarget:self selector:@selector(threadTwo) object:nil];
[_t2 start];
}
- (void)threadOne
{
for (int i = 0; i < 1000; i++) {
NSLog(@"执行线程1");
// if (i == 999) {
// NSLog(@"线程1执行完毕");
// // 取消线程2
// _isThreadTwoCancel = YES;
// }
if (i == 999) {
NSLog(@"线程1执行完毕");
// 取消线程2
[_t2 cancel];
}
}
}
- (void)threadTwo
{
int i = 0;
while (true) {
if ([_t2 isCancelled]) {
// 结束当前的线程
[NSThread exit];
}
NSLog(@"执行线程2: %d", i+1);
i++;
}
}
3. 通过通知中心监听线程的结束(给线程1和线程2先设置name属性)
- (void)viewDidLoad {
[super viewDidLoad];
…………………………………………………………………………………………
_t2.name = @"II";
// 监听线程是否结束
// NSThreadWillExitNotification
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(threadWillExit:) name:NSThreadWillExitNotification object:nil];
}
// 在线程结束的时候执行一些操作
- (void)threadWillExit:(NSNotification *)n
{
NSThread *t = [n object];
NSLog(@"线程%@结束", t.name);
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)threadOne
{
[NSThread currentThread].name = @"I";
…………………………………………………………………………………………
}
五. 线程的优先级
每个线程创建之后都有优先级
优先级在0-1之间, 值越大, 优先级越高
默认优先级: 0.5
优先级高的线程执行的机会更大
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
// 每个线程创建之后都有优先级
// 优先级在0-1之间, 值越大, 优先级越高
// 默认优先级: 0.5
// 优先级高的线程执行的机会更大
NSThread *t1 = [[NSThread alloc] initWithTarget:self selector:@selector(threadOne) object:nil];
t1.name = @"线程I";
[t1 start];
t1.threadPriority = 0;
NSThread *t2 = [[NSThread alloc] initWithTarget:self selector:@selector(threadTwo) object:nil];
t2.name = @"线程II";
[t2 start];
t2.threadPriority = 1;
}
- (void)threadOne
{
for (int i = 0; i < 1000; i++) {
NSLog(@"执行了%@: %d", [NSThread currentThread].name, i);
}
}
- (void)threadTwo
{
for (int i = 0; i < 1000; i++) {
NSLog(@"执行了%@: %d", [NSThread currentThread].name, i);
}
}
六. 线程的同步锁
模拟银行账户取钱
创建一个账户对象, 模拟同时有2个人取钱
1. MyAccount类
#import <Foundation/Foundation.h>
@interface MyAccount : NSObject
/*
accountNo: 账户的号码
money: 账户的余额
*/
- (instancetype)initWithAccountNo:(NSString *)accountNo money:(float)money;
- (void)withDraw:(float)tmpMoney;
@end
#import "MyAccount.h"
@implementation MyAccount
{
// 账户的号码
NSString *_accountNo;
// 余额
float _money;
}
- (instancetype)initWithAccountNo:(NSString *)accountNo money:(float)money
{
self = [super init];
if (self) {
// 给成员变量赋值
_accountNo = accountNo;
_money = money;
}
return self;
}
// 取钱的操作
- (void)withDraw:(float)tmpMoney
{
if (_money >= tmpMoney) {
// 模拟取钱操作时需要一些时间
[NSThread sleepForTimeInterval:0.01];
// 取钱
_money -= tmpMoney;
NSLog(@"%@取了%f元钱", [NSThread currentThread].name, tmpMoney);
} else {
NSLog(@"余额不足");
}
}
@end
2. ViewController.m(此时执行就会出现取款超出余额的问题, 需要添加同步锁)
#import "ViewController.h"
#import "MyAccount.h"
// 系统还有NSCondition 实现多线程的方式
@interface ViewController ()
{
// 公共的账户
MyAccount *_account;
}
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
// 创建一个账户
_account = [[MyAccount alloc] initWithAccountNo:@"Yuen" money:9000000];
// 创建两个线程
NSThread *t1 = [[NSThread alloc] initWithTarget:self selector:@selector(threadOne) object:nil];
[t1 start];
NSThread *t2 = [[NSThread alloc] initWithTarget:self selector:@selector(threadTwo) object:nil];
[t2 start];
}
- (void)threadOne
{
[NSThread currentThread].name = @"Yank";
[_account withDraw:8000000];
}
- (void)threadTwo
{
[NSThread currentThread].name = @"Alfred";
[_account withDraw:2000000];
}
@end
3. 第一种添加线程锁的方法(在MyAccount类中的- (void)withDraw:(float)tmpMoney
方法中用@synchronized(self)添加锁)
- (void)withDraw:(float)tmpMoney
{
// @synchronized(self)添加锁
// 保证_money成员变量在同一时刻只有一个线程修改
@synchronized(self) {
if (_money >= tmpMoney) {
// 模拟取钱操作时需要一些时间
[NSThread sleepForTimeInterval:0.01];
// 取钱
_money -= tmpMoney;
NSLog(@"%@取了%f元钱", [NSThread currentThread].name, tmpMoney);
} else {
NSLog(@"余额不足");
}
}
}
4. 第二种添加线程锁的方法(设一个成员变量:NSLock的对象)
#import "MyAccount.h"
@implementation MyAccount
{
…………………………………………………………………………………………
// 线程锁
NSLock *_lock;
}
- (instancetype)initWithAccountNo:(NSString *)accountNo money:(float)money
{
self = [super init];
if (self) {
…………………………………………………………………………………………
// 初始化线程锁
_lock = [[NSLock alloc] init];
}
return self;
}
// 取钱的操作
- (void)withDraw:(float)tmpMoney
{
[_lock lock];
…………………………………………………………………………………………
// 释放锁
[_lock unlock];
}
七. NSOperation创建线程
1. NSInvocationOperation
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
// 创建线程的队列
_queue = [[NSOperationQueue alloc] init];
// 1. NSInvoationOperation
/*
第一个参数: 线程的执行体由哪个对象执行
第二个参数: 线程的执行体对象的方法
第三个参数: 线程的执行体方法需要传递的实参
*/
NSInvocationOperation *op1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(threadOne) object:nil];
// 线程执行完成后调用的block
[op1 setCompletionBlock:^{
NSLog(@"线程I执行完成");
}];
// 把线程添加到队列中
[_queue addOperation:op1];
}
- (void)threadOne
{
for (int i = 0; i < 100; i++) {
NSLog(@"线程一:%d", i);
}
}
- (void)dealloc
{
// 取消所有线程
[_queue cancelAllOperations];
}
2. NSBlockOperation
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
// 创建线程的队列
_queue = [[NSOperationQueue alloc] init];
// 2. NSBlockOperation
// 参数是一个代码块, 是线程的执行体
NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 100; i++) {
NSLog(@"执行了线程II:%d", i);
}
}];
// 线程执行完成后调用的block
[op1 setCompletionBlock:^{
NSLog(@"线程II执行完成");
}];
[_queue addOperation:op2];
}
- (void)dealloc
{
// 取消所有线程
[_queue cancelAllOperations];
}
3. 自定义NSBlockOperation的子类
-
ImageOperation类
#import <Foundation/Foundation.h> #import <UIKit/UIKit.h> @interface ImageOperation : NSOperation // 图片的URL字符串 @property (nonatomic, strong) NSString *urlString; // 图片视图对象 @property (nonatomic, strong) UIImageView *imageView; @end #import "ImageOperation.h" @implementation ImageOperation // 自定义NSOperation类型, 需要实现main方法 // 这个方法是线程的执行体 - (void)main { NSURL *url = [NSURL URLWithString:self.urlString]; NSData *data = [NSData dataWithContentsOfURL:url]; // 在主线程刷新UI [self performSelectorOnMainThread:@selector(refreshUI:) withObject:data waitUntilDone:YES]; } - (void)refreshUI:(NSData *)data { self.imageView.image = [UIImage imageWithData:data]; } @end
-
ViewController.m
#import "ViewController.h" #import "ImageOperation.h" @interface ViewController () { NSOperationQueue *_queue; UIImageView *_myImageView; } @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view, typically from a nib. // 创建线程的队列 _queue = [[NSOperationQueue alloc] init]; _myImageView = [[UIImageView alloc] initWithFrame:CGRectMake(50, 100, 240, 320)]; [self.view addSubview:_myImageView]; // 第三种创建线程的方式 // 创建一个线程, 下载一张图片, 显示到视图上 UIButton *btn = [UIButton buttonWithType:UIButtonTypeSystem]; btn.frame = CGRectMake(100, 40, 80, 40); [btn setTitle:@"下载" forState:UIControlStateNormal]; [self.view addSubview:btn]; [btn addTarget:self action:@selector(downloadAction) forControlEvents:UIControlEventTouchUpInside]; } - (void)downloadAction { // 创建线程下载图片 ImageOperation *op = [[ImageOperation alloc] init]; // 图片的URL op.urlString = @"http://img3.3lian.com/2006/027/08/007.jpg"; // 图片视图 op.imageView = _myImageView; // 添加到队列里面 [_queue addOperation:op]; } @end
八. Block
1. 没有返回值的block
- (void)testNoReturnBlock
{
// 1. 没有参数的
// 声明
void (^block1)(void);
// 赋值
block1 = ^{
NSLog(@"没有返回值没有参数");
};
// 使用
block1();
// 2. 有一个参数, 参数是基本类型
// 声明
void (^block2)(int a);
// 赋值
block2 = ^(int a){
NSLog(@"a = %d", a);
};
// 使用
block2(10);
// 3. 有一个参数, 参数是对象类型
// 声明
void (^block3)(NSString *);
// 赋值
block3 = ^(NSString *str){
NSLog(@"%@", str);
};
// 使用
block3(@"张三");
// 4. 两个参数, 一个基本类型, 一个对象类型
// 声明
void (^block4)(int age, NSString *name);
// 赋值
block4 = ^(int age, NSString *name){
NSLog(@"%@'s %d years old", name, age);
};
// 使用
block4(54, @"Lau");
}
2. 有返回值, 返回值类型是基本类型(int为例)
- (void)testIntValueReturn
{
// 1. 没有参数
// 声明
int (^block1)(void);
// 赋值
block1 = ^{
return 10;
};
// 使用
NSLog(@"%d", block1());
// 2. 有一个参数, 参数是基本类型
// 声明
int (^block2)(int);
// 赋值
block2 = ^(int a){
return a;
};
// 使用
NSLog(@"%d", block2(5));
// 3. 有一个参数, 参数是对象类型
// 声明
int (^block3)(NSString *);
block3 = ^(NSString *str){
return (int)str.length;
};
NSLog(@"%d", block3(@"Sunshine"));
// 4. 有两个参数, 都是对象类型
// 声明
int (^block4)(NSString *str1, NSString *str2);
// 赋值
block4 = ^(NSString *str1, NSString *str2){
return (int)(str1.length + str2.length);
};
NSLog(@"%d", block4(@"Sunshine", @"Rain"));
}
3. 有返回值, 返回值类型是基本类型(int为例)
- (void)testNSStringValueReturn
{
// 1. 没有参数
// 声明
NSString *(^block1)(void);
// 赋值
block1 = ^{
return @"Nexus";
};
// 使用
NSLog(@"%@",block1());
// 2. 有一个参数, 参数是基本类型
// 声明
NSString *(^block2)(int);
// 赋值
block2 = ^(int a){
return [NSString stringWithFormat:@"%d", a];
};
// 使用
NSLog(@"%@", block2(5));
// 3. 有一个参数, 参数是对象类型
// 声明
NSString *(^block3)(NSString *);
block3 = ^(NSString *str){
return str;
};
NSLog(@"%@", block3(@"Sunshine"));
// 4. 有两个参数, 都是对象类型
// 声明
NSString *(^block4)(NSString *str1, NSString *str2);
// 赋值
block4 = ^(NSString *str1, NSString *str2){
return [NSString stringWithFormat:@"%@ %@", str1, str2];
};
NSLog(@"%@", block4(@"Sunshine", @"Rain"));
}
4. 测试
- (void)test
{
// 1. 没有返回值, 有2个参数, 一个基本类型, 一个对象类型
// 声明并赋值
void (^block1)(int age, NSString *name) = ^(int age, NSString *name){
NSLog(@"%@'s %d years old", name, age);
};
// 使用
block1(23, @"Yuen");
// 2. 返回值为int类型, 有2个参数, 参数是NSString类型
// 声明并赋值
int (^block2)(NSString *, NSString *) = ^(NSString *str1, NSString *str2){
return (int)(str1.length + str2.length);
};
// 使用
NSLog(@"%d", block2(@"Sunshine", @"Rain"));
// 3. 返回值是NSString类型, 有2个参数, 参数是NSString类型
NSString *(^block3)(NSString *,NSString *) = ^(NSString *str1, NSString *str2){
return [NSString stringWithFormat:@"%@%@", str1, str2];
};
// 使用
NSLog(@"%@", block3(@"Sunshine", @"Rain"));
}