首页 > 代码库 > IOS_多线程_售票

IOS_多线程_售票

H:/1007/01_多线程_大任务_MainViewController.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