IOS_多线程_售票
时间:2014-06-15 20:14:40
收藏:0
阅读:250
H:/1007/01_多线程_大任务_MainViewController.m
H:/1007/02_多线程_加载图片_MainViewController.m
H:/1007/03_多线程_卖票_MainViewController.m
H:/1007/03_多线程_卖票_单例_Ticket.h
H:/1007/03_多线程_卖票_单例_Ticket.m
// MainViewController.m
// 多线程-01.大任务
// Created by apple on 13-10-7.
#import "MainViewController.h"
@interface MainViewController ()
@property (weak, nonatomic) UIImageView *imageView;
@end
@implementation MainViewController
/*
NSObject多线程方法
1. [NSThread currentThread] 可以返回当前运行的线程
num = 1 说明是主线程
在任何多线程技术中(NSThread,NSOperation,GCD),
均可以使用此方法,查看当前的线程情况。
2. 新建后台线程,调度任务
[self performSelectorInBackground:@selector(bigTask)
withObject:nil]
使用performSelectorInBackground是可以修改UI的,
但是,强烈不建议如此使用。
3. 更新界面
使用performSelectorOnMainThread可以在主线程上执行任务。
绝大多数最后一个参数是YES,即等待,直到它执行完
提示:NSObject对象均可以调用此方法。
4. 内存管理
线程任务要包在@autoreleasepool(自动释放池)中,
否则容易引起内存泄露,而且非常难发现。
*/
- (void)viewDidLoad
{
[super viewDidLoad];
// 创建第1个按钮,并为其添加点击事件
UIButton *btn1 = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[btn1 setFrame:CGRectMake(110, 100, 100, 40)];
[btn1 setTitle:@"大任务" forState:UIControlStateNormal];
[btn1 addTarget:self action:@selector(btnClick_1)
forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:btn1];
// 创建第2个按钮,并为其添加点击事件
UIButton *btn2 = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[btn2 setFrame:CGRectMake(110, 200, 100, 40)];
[btn2 setTitle:@"小任务" forState:UIControlStateNormal];
[btn2 addTarget:self action:@selector(btnClick_2)
forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:btn2];
NSLog(@"%@", [NSThread currentThread]);
// 当前主线程中设置头像
UIImageView *imageView = [[UIImageView alloc]initWithFrame:
CGRectMake(110, 260, 100, 100)];
UIImage *image = [UIImage imageNamed:@"头像1.png"];
[imageView setImage:image];
[self.view addSubview:imageView];
self.imageView = imageView;
}
// 响应按钮1的点击事件
- (void)btnClick_1
{
// 在后台调用耗时操作
// performSelectorInBackground会新建一个后台线程,
// 并在该线程中执行调用的方法
[self performSelectorInBackground:@selector(btn_1_bigTask)
withObject:nil];
NSLog(@"大任务按钮: %@", [NSThread currentThread]);
}
#pragma mark 耗时操作
- (void)btn_1_bigTask
{
@autoreleasepool {
for (NSInteger i = 0; i < 300; i++) {
NSString *str = [NSString stringWithFormat:@"i = %i", i];
NSLog(@"%@", str);
}
NSLog(@"大任务 - %@", [NSThread currentThread]);
UIImage *image = [UIImage imageNamed:@"头像2.png"];
// 在主线程中修改self.imageView的image
// 调用self即当前控制器的方法,在主线程中设置头像
[self performSelectorOnMainThread:@selector(changeImage:)
withObject:image waitUntilDone:YES];
// 直接调用self.imageView自己的setImage:方法,并传递参数image
[self.imageView performSelectorOnMainThread:@selector(setImage:)
withObject:image waitUntilDone:YES];
}
}
// 自定义方法,设置头像
- (void)changeImage:(UIImage *)image
{
NSLog(@"修改头像 %@", [NSThread currentThread]);
[self.imageView setImage:image];
}
// 响应按钮2的点击,调用自定义方法
- (void)btnClick_2
{
NSLog(@"小任务按钮:%@", [NSThread currentThread]);
[self btn_2_smallTask];
}
// 自定义方法
- (void)btn_2_smallTask
{
NSString *str = nil;
for (NSInteger i = 0; i < 30000; i++) {
str = [NSString stringWithFormat:@"i = %i", i];
}
NSLog(@"%@", str);
NSLog(@"小任务 - %@", [NSThread currentThread]);
}
@end
H:/1007/02_多线程_加载图片_MainViewController.m
// MainViewController.m
// 多线程-02.加载图片
// Created by apple on 13-10-7.
#import "MainViewController.h"
@interface MainViewController ()
// 图像集合
@property (strong, nonatomic) NSSet *imageViewSet;
// 定义操作队列,NSOperationQueue
@property (strong, nonatomic) NSOperationQueue *operationQueue;
@end
@implementation MainViewController
/*
1. NSThread
1> 类方法 detachNewThreadSelector
直接启动线程,调用选择器方法
2> 成员方法 initWithTarget
需要使用start方法,才能启动实例化出来的线程
优点:简单
缺点:
* 控制线程的生命周期比较困难
* 控制并发线程数
* 存在死锁隐患
* 先后顺序困难
例如:下载图片(后台线程) -> 滤镜美化(后台线程) -> 更新UI(主线程)
2. NSOperation
1> NSInvocationOperation
2> NSBlockOperation
定义完Operation之后,将操作添加到NSOperationQueue即可启动线程,执行任务
使用:
1> setMaxConcurrentOperationCount 可以控制同时并发的线程数量
2> addDependency 可以指定线程之间的依赖关系,
从而达到控制线程执行顺序的目的,如先下载,再渲染图片
提示:
要更新UI,需要使用[NSOperationQueue mainQueue]addOperationWithBlock:
在主操作队列中更新界面
3. GCD
1) 全局global队列,如果是同步,则不开线程,在主队列中执行
方法:dispatch_get_global_queue(获取全局队列)
优先级:DISPATCH_QUEUE_PRIORITY_DEFAULT
所有任务是并发(异步)执行的
2) 串行队列 必须开一条新线程,全是顺序执行的
方法:dispatch_queue_create(创建串行队列,串行队列不能够获取)
提示:队列名称可以随意,不过不要使用@
3) 主队列
主线程队列
方法:dispatch_get_main_queue(获取主队列)
在gcd中,同步还是异步取决于任务执行所在的队列,更方法名没有关系
具体同步、异步与三个队列之间的关系,一定要反复测试,体会!
4.全局队列(可能会开启多条线程,如果是同步方法,则不开线程,只在主线程里)
dispatch_queue_t queue = dispatch_get_global_queue(
DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
串行队列(只可能会开启一条线程)
dispatch_queue_t queue = dispatch_queue_create("myQueue",
DISPATCH_QUEUE_SERIAL);
主队列
dispatch_get_main_queue();
异步操作
dispatch_async 异步方法无法确定任务的执行顺序
同步操作
dispatch_sync 同步方法会依次执行,能够决定任务的执行顺序
同步操作与队列无关,所有的队列的同步,都是顺序执行的
更新界面UI时,最好使用同步方法
GCD的优点:
充分利用多核
所有的多线程代码集中在一起,便于维护
GCD中无需使用@autoreleasepool
如果要顺序执行,可以使用dispatch_sync同步方法
dispatch_async无法确定任务的执行顺序
调用主线程队列任务更新UI时,最好使用同步方法
单例:
保证在内存中永远只有类的单个实例
建立方法:
1,声明一个静态成员变量,记录唯一实例
2,重写allocWithZone方法
allocWithZone方法是对象分配内存空间时,最终会调用的方法,
重写该方法,保证只会分配一个内存空间
3,建立sharedXXX类方法,便于其他类访问
*/
- (void)viewDidLoad
{
[super viewDidLoad];
// 自定义方法,设置UI界面
[self setupUI];
// 实例化操作队列,NSOperationQueue
self.operationQueue = [[NSOperationQueue alloc]init];
}
// 自定义方法,设置UI界面
- (void)setupUI
{
// 实例化图像视图集合
NSMutableSet *imageSet = [NSMutableSet setWithCapacity:28];
// 虽然只有17张图片,但是每行显示4张,一共显示7行(重复使用)
// 每张小图片宽 80,高 50
NSInteger w = 80;
NSInteger h = 50;
// 弄出7行 X 4列图片
for (NSInteger row = 0; row < 7; row++) {
for (NSInteger col = 0; col < 4; col++) {
// 计算图片的位置
// 第几列 即所在的列数 决定了x坐标
// 第几行 即所在的行数 决定了y坐标
NSInteger x = col * w;
NSInteger y = row * h;
UIImageView *imageView = [[UIImageView alloc]initWithFrame:CGRectMake(x, y, w, h)];
/* 下面是不使用多线程,设置图片
NSInteger index = (row * 4 + col) % 17 + 1;
NSString *imageName = [NSString stringWithFormat:@"NatGeo%02d.png", index];
UIImage *image = [UIImage imageNamed:imageName];
[imageView setImage:image];
*/
// 因为要使用多线程设置图片,所以这只添加imageView,并不设置图片
[self.view addSubview:imageView];
[imageSet addObject:imageView];
}
}
// 成员变量 NSSet,记住所有的imageSet,方便多线程方法中为其设置图片
self.imageViewSet = imageSet;
// 添加按钮
UIButton *btn = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[btn setFrame:CGRectMake(110, 385, 100, 40)];
[btn setTitle:@"设置图片" forState:UIControlStateNormal];
[btn addTarget:self action:@selector(click)
forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:btn];
}
#pragma mark 按钮的监听方法,设置图片
- (void)click
{
// 调用GCD 设置图片
[self gcdLoad];
}
// 多线程之NSThread,类方法detach线程,或者alloc创建线程,并手动start线程
- (void)threadLoad
{
// 遍历成员变量NSSet 为每一个imageView设置图片
for (UIImageView *imageView in self.imageViewSet) {
// 方式1,类方法,detach分离,新建28条线程,并自动调用threadLoadImage方法
[NSThread detachNewThreadSelector:@selector(threadLoadImage:)
toTarget:self withObject:imageView];
// 方式2,alloc创建线程,并需要手动开启线程,才会执行threadLoadImage方法
NSThread *thread = [[NSThread alloc]initWithTarget:self
selector:@selector(threadLoadImage:) object:imageView];
// alloc出来的线程,必须手动start才有效
[thread start];
}
}
// NSThread线程的任务:加载图片方法
- (void)threadLoadImage:(UIImageView *)imageView
{
// 设置imageView的图片
// 线程方法一定要加autoreleasepool
@autoreleasepool {
// 28条线程,都在一开始睡眠1秒
[NSThread sleepForTimeInterval:1.0f];
NSInteger index = arc4random_uniform(17) + 1;
NSString *imageName = [NSString stringWithFormat:@"NatGeo%02d.png",
index];
UIImage *image = [UIImage imageNamed:imageName];
// imageView直接在主线程上执行setImage方法,更新UI,参数就是image
[imageView performSelectorOnMainThread:@selector(setImage:)
withObject:image waitUntilDone:YES];
}
}
#pragma mark NSOperation方法
// 多线程之2,NSXxxOperation依赖关系演示
- (void)operationDemo
{
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"下载 %@", [NSThread currentThread]);
}];
NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"美化 %@", [NSThread currentThread]);
}];
NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"更新 %@", [NSThread currentThread]);
}];
// Dependency依赖,1完成,再执行2,最后执行3
// 提示:依赖关系可以多重依赖
// 注意:不要建立循环依赖,嵌套依赖
[op2 addDependency:op1];
[op3 addDependency:op2];
// 将NSOperation添加到成员变量NSOperationQueue,操作队列中
[self.operationQueue addOperation:op3];
[self.operationQueue addOperation:op1];
[self.operationQueue addOperation:op2];
}
// 多线程之2,NSBlockOperation
- (void)operationBlockLoad
{
// 遍历成员变量NSSet,为每一个imageView设置图片
for (UIImageView *imageView in self.imageViewSet) {
// 创建一个操作 NSOperation
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
// 执行 当前控制器的 自定义方法,,operationLoadImage
[self operationLoadImage:imageView];
}];
// 必须将操作添加到操作队列之中,才有效,注:会自动启动~
[self.operationQueue addOperation:op];
}
}
// 多线程之2,NSInvocationOperation
-(void)operationLoad
{
// NSOperationQueue优点:可以设置同时并发执行的线程的数量
// 即使开了20条线程,同一时刻也只会执行其中的4条线程
[self.operationQueue setMaxConcurrentOperationCount:4];
// 遍历成员变量NSSet,为每一个imageView设置图片
for (UIImageView *imageView in self.imageViewSet) {
// 创建一个操作 NSOperation
NSInvocationOperation *op = [[NSInvocationOperation alloc]
initWithTarget:self selector:@selector(operationLoadImage:)
object:imageView];
// 如果直接调用operation的start方法,是在主线程队列上运行的,不会开启新的线程
[op start];
// 必须将操作Operation添加到操作队列,才会开启新的线程执行任务,注:自动开启
[self.operationQueue addOperation:op];
}
}
// 多线程之2,NSXxxOperation任务的具体代码,加载图片方法
- (void)operationLoadImage:(UIImageView *)imageView
{
// 线程方法一定要加autoreleasepool
@autoreleasepool {
// 设置imageView的内容
// 模拟网络延时
[NSThread sleepForTimeInterval:1.0f];
NSInteger index = arc4random_uniform(17) + 1;
NSString *imageName = [NSString stringWithFormat:@"NatGeo%02d.png",
index];
UIImage *image = [UIImage imageNamed:imageName];
// 必须在主线程队列上更新UI,必须使用NSOperationQueue mainQueue
[[NSOperationQueue mainQueue]addOperationWithBlock:^{
[imageView setImage:image];
}];
}
}
// 多线程之3,GCD,抽象程度最高,用户主要精力只要放在业务上即可
- (void)gcdDemo
{
/*
1. 全局global队列
方法:dispatch_get_global_queue(获取全局队列)
优先级:DISPATCH_QUEUE_PRIORITY_DEFAULT
所有任务是并发(异步)执行的,会创建N条线程
2. 串行队列
方法:dispatch_queue_create(创建串行队列,串行队列不能够获取)
提示:队列名称可以随意,不过不要使用@
任务是同步,只会创建一个线程
3. 主队列
主线程队列
方法:dispatch_get_main_queue(获取主队列)
在gcd中,同步还是异步取决于任务执行所在的队列,更方法名没有关系
如果是全局队列就是异步,会创建多条线程
如果是自创建的队列,则是串行,同步的,且只会创建一条线程
派发dispatch_
异步async不执行,并发执行
优先级priority,使用默认优先级即可
1. 在全局队列中调用异步任务
1) 全局队列,全局调度队列是有系统负责的,开发时不用考虑并发线程数量问题
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
串行队列,需要创建,不能够get
DISPATCH_QUEUE_SERIAL串行队列
dispatch_queue_t queue = dispatch_queue_create("myQueue",
DISPATCH_QUEUE_SERIAL);
GCD是基于C语言的框架
工作原理:
让程序平行排队的特定任务,根据可用的处理资源,安排它们在任何可用的处理器上执行任务
要执行的任务可以是一个函数或者一个block
底层是通过线程实现的,不过程序员可以不必关注实现的细节
GCD中的FIFO队列称为dispatch queue,可以保证先进来的任务先得到执行
dispatch_notify 可以实现监听一组任务是否完成,完成后得到通知
GCD队列:
全局队列:所有添加到全局队列中的任务都是并发执行的
串行队列:所有添加到串行队列中的任务都是顺序执行的
主队列:所有添加到主队列中的任务都是在主线程中执行的
*/
// 主队列,即在主线程上运行
dispatch_queue_t queue = dispatch_get_main_queue();
// 2) 在主队列中没有异步和同步,所有都是在主线程中完成的
dispatch_async(queue, ^{
NSLog(@"任务1 %@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"任务2 %@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"任务3 %@", [NSThread currentThread]);
});
}
// 多线程之3, GCD加载图像,具体的核心业务,为NSSet中每个imageView设置图片
- (void)gcdLoad
{
// 1) 获取全局队列,可以执行异步
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 遍历成员变量NSSet,为每一个imageView设置图片
for (UIImageView *imageView in self.imageViewSet) {
// 2) 在全局队列上执行异步方法,开启N条线程,加载并设置图像
dispatch_async(queue, ^{
NSLog(@"GCD- %@", [NSThread currentThread]);
NSInteger index = arc4random_uniform(17) + 1;
NSString *imageName = [NSString stringWithFormat:@"NatGeo%02d.png",
index];
// 通常此异步方法里面获取的image是网络上的
UIImage *image = [UIImage imageNamed:imageName];
// 3) 最后必须在主线程队列中设置图片,异步
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"更新图片- %@", [NSThread currentThread]);
[imageView setImage:image];
});
});
}
}
@end
H:/1007/03_多线程_卖票_MainViewController.m
// MainViewController.m
// 多线程-03.卖票
// Created by apple on 13-10-7.
/*
系统预设
共有30张票可以销售(开发时可以少一些,专注实现)
售票工作由两个线程并发进行
没有可出售票据时,线程工作停止
两个线程的执行时间不同,模拟售票人员效率不同
使用一个多行文本框公告售票进度(主线程更新UI)
线程工作安排
主线程:负责更新UI
线程1:模拟第1名卖票员
线程2:模拟第2名卖票员
两个线程几乎同时开始卖票
*/
#import "MainViewController.h"
#import "Ticket.h"
@interface MainViewController ()
@property (weak, nonatomic) UITextView *textView;
@property (strong, nonatomic) NSOperationQueue *operationQueue;
@end
@implementation MainViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// 建立多行文本框
UITextView *textView = [[UITextView alloc]initWithFrame:self.view.bounds];
// 禁止编辑
[textView setEditable:NO];
[self.view addSubview:textView];
self.textView = textView;
// 预设可以卖30张票
[Ticket sharedTicket].tickets = 30;
// 实例化操作队列,NSOperationQueue
self.operationQueue = [[NSOperationQueue alloc]init];
// 调用自定义方法,开始卖票
[self operationSales];
}
// 多线程卖票之一:NSOperation
- (void)operationSales
{
// 提示,operation中没有群组任务完成通知功能
// 设置操作队列,最大同时并发线程数:两个线程卖票
[self.operationQueue setMaxConcurrentOperationCount:2];
[self.operationQueue addOperationWithBlock:^{
[self operationSaleTicketWithName:@"op-1"];
}];
[self.operationQueue addOperationWithBlock:^{
[self operationSaleTicketWithName:@"op-2"];
}];
[self.operationQueue addOperationWithBlock:^{
[self operationSaleTicketWithName:@"op-3"];
}];
}
// 多线程卖票之一:NSOperation 核心卖票代码
- (void)operationSaleTicketWithName:(NSString *)name
{
while (YES) {
// 同步锁synchronized要锁的范围,对被抢夺资源修改/读取的代码部分
@synchronized(self) {
// 判断是否还有票
if ([Ticket sharedTicket].tickets > 0) {
[Ticket sharedTicket].tickets--;
// 提示,涉及到被抢夺资源的内容定义方面的操作,千万不要跨线程去处理
NSString *str = [NSString stringWithFormat:
@"剩余票数 %d 线程名称 %@",
[Ticket sharedTicket].tickets, name];
// 在mainQueue主线程中更新UI
[[NSOperationQueue mainQueue]addOperationWithBlock:^{
// 调用自定义方法,更新编辑框的内容,并滚动至最后一行
[self appendContent:str];
}];
} else {
NSLog(@"卖票完成 %@ %@", name, [NSThread currentThread]);
break;
}
}
// 模拟卖票休息,不同的窗口,工作效率不同
if ([name isEqualToString:@"op-1"]) {
[NSThread sleepForTimeInterval:0.6f];
} else {
[NSThread sleepForTimeInterval:0.4f];
}
}
}
#pragma mark 更新UI,追加当前余票数,到多行文本框
- (void)appendContent:(NSString *)text
{
// 1. 取出多行文本框里面原来的内容
NSMutableString *str = [NSMutableString
stringWithString:self.textView.text];
// 2. 将text追加至textView内容的末尾
[str appendFormat:@"%@\n", text];
// 3. 使用追加后的文本,替换textView中的内容
[self.textView setText:str];
// 4. 将textView滚动至视图底部,保证能够及时看到新追加的内容
// 参数1是index,参数2是截取的长度
NSRange range = NSMakeRange(str.length - 1, 1);
// 5.滚动到编辑框的最后一行
[self.textView scrollRangeToVisible:range];
}
// 多线程卖票之二:NSThread
- (void)threadSales
{
// 类方法创建新线程,并且直接运行
[NSThread detachNewThreadSelector:@selector(threadSaleTicketWithName:)
toTarget:self withObject:@"thread-1"];
// 类方法创建新线程,并且直接运行
[NSThread detachNewThreadSelector:@selector(threadSaleTicketWithName:)
toTarget:self withObject:@"thread-2"];
}
// 多线程卖票之二:NSThread 核心卖票代码
- (void)threadSaleTicketWithName:(NSString *)name
{
// 使用NSThread时,线程调用的方法千万要使用@autoreleasepool
@autoreleasepool {
while (YES) {
// 同步锁synchronized要锁的范围,对被抢夺资源修改/读取的代码部分
@synchronized(self) {
if ([Ticket sharedTicket].tickets > 0) {
[Ticket sharedTicket].tickets--;
NSString *str = [NSString stringWithFormat:
@"剩余票数 %d 线程名称 %@",
[Ticket sharedTicket].tickets, name];
// 在MainThread主线程中更新UI
// 调用自定义方法,更新编辑框的内容,并滚动至最后一行
[self performSelectorOnMainThread:
@selector(appendContent:) withObject:str
waitUntilDone:YES];
} else {
break;
}
}
// 模拟卖票休息,不同的窗口,工作效率不同
if ([name isEqualToString:@"thread-1"]) {
[NSThread sleepForTimeInterval:1.0f];
} else {
[NSThread sleepForTimeInterval:0.1f];
}
}
}
}
// 多线程卖票之三:GCD,单纯只创建三个异步任务分别卖票
- (void)gcdSales_without_group
{
// 1) 创建全局队列
dispatch_queue_t queue = dispatch_get_global_queue(
DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 2) 创建三个异步任务分别卖票
dispatch_async(queue, ^{
[self gcdSaleTicketWithName:@"gcd-1"];
});
dispatch_async(queue, ^{
[self gcdSaleTicketWithName:@"gcd-2"];
});
dispatch_async(queue, ^{
[self gcdSaleTicketWithName:@"gcd-3"];
});
}
// 多线程卖票之三:GCD,创建组,将三个异步任务添加到组
- (void)gcdSales_with_group
{
// 1) 创建全局队列
dispatch_queue_t queue = dispatch_get_global_queue(
DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 2. GCD中可以将一组相关联的操作,定义到一个群组中
// 定义到群组中之后,当所有线程完成时,可以获得通知
// 3. 定义群组
dispatch_group_t group = dispatch_group_create();
// 4. 定义群组的异步任务
dispatch_group_async(group, queue, ^{
[self gcdSaleTicketWithName:@"gcd-1"];
});
dispatch_group_async(group, queue, ^{
[self gcdSaleTicketWithName:@"gcd-2"];
});
// 3) 群组任务完成通知,当前组中的三个线程全部完成的时候,会调用~
dispatch_group_notify(group, queue, ^{
NSLog(@"卖完了");
});
}
// 多线程卖票之三:GCD 核心卖票代码
- (void)gcdSaleTicketWithName:(NSString *)name
{
while (YES) {
// 同步锁synchronized要锁的范围,对被抢夺资源修改/读取的代码部分
@synchronized(self) {
if ([Ticket sharedTicket].tickets > 0) {
[Ticket sharedTicket].tickets--;
// 提示内容
NSString *str = [NSString stringWithFormat:@"剩余票数 %d,
线程名称 %@", [Ticket sharedTicket].tickets, name];
// 在dispatch_get_main_queue主线程中更新UI
dispatch_sync(dispatch_get_main_queue(), ^{
// 调用自定义方法,更新编辑框的内容,并滚动至最后一行
[self appendContent:str];
});
} else {
break;
}
}
// 模拟卖票休息,不同的窗口,工作效率不同
if ([name isEqualToString:@"gcd-1"]) {
[NSThread sleepForTimeInterval:1.0f];
} else {
[NSThread sleepForTimeInterval:0.2f];
}
}
}
@end
H:/1007/03_多线程_卖票_单例_Ticket.h
// Ticket.h // 多线程-03.卖票 // Created by apple on 13-10-7. // Copyright (c) 2013年 itcast. All rights reserved. #import <Foundation/Foundation.h> @interface Ticket : NSObject // 实例化票据的单例 + (Ticket *)sharedTicket; // 在多线程应用中,所有被抢夺资源的属性需要设置为原子属性 // 系统会在多线程抢夺时,保证该属性有且仅有一个线程能够访问 // 注意:使用atomic属性,会降低系统性能,在开发多线程应用时,尽量不要资源 // 另外,atomic属性,必须与@synchronized(同步锁)一起使用 // 票数 atomic @property (assign, atomic) NSInteger tickets; @end
H:/1007/03_多线程_卖票_单例_Ticket.m
// Ticket.m
// 多线程-03.卖票
// Created by apple on 13-10-7.
// Copyright (c) 2013年 itcast. All rights reserved.
#import "Ticket.h"
static Ticket *SharedInstance;
@implementation Ticket
/**
实现单例模型需要做三件事情
1. 使用全局静态变量记录住第一个被实例化的对象
static Ticket *SharedInstance
2. 重写allocWithZone方法,并使用dispatch_once_t,从而保证在多线程情况下,
同样只能实例化一个对象副本
3. 建立一个以shared开头的类方法实例化单例对象,便于其他类调用,同时不容易引起歧义
同样用dispatch_once_t确保只有一个副本被建立
关于被抢夺资源使用的注意事项
在多线程应用中,所有被抢夺资源的属性需要设置为原子属性
系统会在多线程抢夺时,保证该属性有且仅有一个线程能够访问
注意:使用atomic属性,会降低系统性能,在开发多线程应用时,尽量不要抢资源
另外,atomic属性,必须与@synchronized(同步锁)一起使用
*/
// 使用内存地址实例化对象,所有实例化方法,最终都会调用此方法
// 要实例化出来唯一的对象,需要一个变量记录住第一个实例化出来的对象
+ (id)allocWithZone:(NSZone *)zone
{
// 解决多线程中,同样只能实例化出一个对象副本
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
SharedInstance = [super allocWithZone:zone];
});
return SharedInstance;
}
// 建立一个单例对象,便于其他类调用
+ (Ticket *)sharedTicket
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
SharedInstance = [[Ticket alloc]init];
});
return SharedInstance;
}
@end
评论(0)