假如我们在功能的实现过程中,类中有一个全局变量,我们创建了多个线程去同时改变或者使用这个变量,会出现什么问题?
线程锁就是用来解决多线程之间对资源共享的问题;
思路
在上文《多线程之三》的基础之上进行演示,模仿多个地点进行售票的案例。
代码展示
1:创建按钮
//问题 : 当多个线程执行某一块相同代码,需要线程锁进行保护
UIButton *btn3 = [UIButton buttonWithType:UIButtonTypeCustom];
btn3.frame = CGRectMake(40, 200, 100, 40);
[btn3 setTitle:@"线程锁" forState:UIControlStateNormal];
[btn3 setBackgroundColor:[UIColor blueColor]];
[btn3 addTarget:self action:@selector(click_lock) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:btn3];
2:创建售票的管理对象
TicketManager.h
@interface TicketManager : NSObject
- (void) startSale ;
@end
TicketManager.m
首先做一个扩展
\#define Total 100 //一共一百张票
@interface TicketManager ()
@property (nonatomic, assign) int whole; //总票数
@property (nonatomic, assign) int surplus; //剩余票数
@property (nonatomic, strong) NSThread *thread_SH; //子线程, 上海售票点
@property (nonatomic, strong) NSThread *thread_BJ; //子线程, 北京售票点
@property (nonatomic, strong) NSThread *thread_SZ; //子线程, 深圳售票点
@end
初始化售卖点
@implementation TicketManager
- (instancetype)init
{
self = [super init];
if (self) {
self.whole = Total;
self.surplus = Total;
self.thread_SH = [[NSThread alloc] initWithTarget:self selector:@selector(sale) object:nil];
self.thread_SH.name = @"上海售票点";
self.thread_BJ = [[NSThread alloc] initWithTarget:self selector:@selector(sale) object:nil];
self.thread_BJ.name = @"北京售票点";
self.thread_SZ = [[NSThread alloc] initWithTarget:self selector:@selector(sale) object:nil];
self.thread_SZ.name = @"深圳售票点";
}
return self;
}
实现售卖方法
//售票方法
- (void) sale {
while (true) {
if (self.surplus > 0) { //当还有余票时,就执行卖票
[NSThread sleepForTimeInterval:1];
self.surplus = self.surplus - 1;
NSLog(@"%@ 卖出一张票,剩余:%d",[NSThread currentThread].name, self.surplus);
}
}
}
三个地方开始同时售票
//开始卖票
- (void) startSale {
[self.thread_SZ start];
[self.thread_BJ start];
[self.thread_SH start];
}
3:在 viewController 里面创建和使用TicketMananger
@property (nonatomic, strong) TicketManager *ticketManager; //票务管理
在 viewDidLoad 中初始化
self.ticketManager = [[TicketManager alloc] init];
在 click_lock 中启动卖票
NSLog(@"开始卖票");
[self.ticketManager startSale];
4:开始执行,查看打印结果
根据日志分析:非常明显剩余票数不正确。
接下来我们引入锁的概念来解决这个问题。简单说明下锁是什么概念呢?咱们可以先这样理解:当我在独自使用一个房间的时候,不希望别人同时使用和打扰,这样呢,我们也就可以先对该房间加上锁,然后再使用,在使用完成之后,该房间不归我使用了,我就解开锁,以方便其他人使用、
线程锁有三种方式:
1 : @synchronized
使用方法:将要保护起来的代码块添加到 @synchronized 的括号中包裹起来
//线程锁的第一种方式 :@synchronized
@synchronized (self) {
if (self.surplus > 0) { //当还有余票时,就执行卖票
[NSThread sleepForTimeInterval:1];
self.surplus = self.surplus - 1;
NSLog(@"%@ 卖出一张票,剩余:%d",[NSThread currentThread].name, self.surplus);
}
}
2:NSCondition
使用方法:创建了 NSCondition 对象,将保护起来的代码块使用 lock 和 unlock 进行前后包裹;
//线程锁的第二种方式:NSCondition
if (!self.condition) {
self.condition = [[NSCondition alloc] init];
}
[self.condition lock];
if (self.surplus > 0) { //当还有余票时,就执行卖票
[NSThread sleepForTimeInterval:1];
self.surplus = self.surplus - 1;
NSLog(@"%@ 卖出一张票,剩余:%d",[NSThread currentThread].name, self.surplus);
}
[self.condition unlock];
3:NSLock
使用方法:创建了 NSLock 对象,将保护起来的代码块使用 lock 和 unlock 进行前后包裹;
//线程锁的第三种方式:NSLock
if (!self.lock) {
self.lock = [[NSLock alloc] init];
}
[self.lock lock];
if (self.surplus > 0) { //当还有余票时,就执行卖票
[NSThread sleepForTimeInterval:1];
self.surplus = self.surplus - 1;
NSLog(@"%@ 卖出一张票,剩余:%d",[NSThread currentThread].name, self.surplus);
}
[self.lock unlock];
在我们使用了以上三种线程锁之后的打印结果是怎么样的呢?
可以看出打印的结果是健康和良性的;
至此,线程锁的使用先告一段落,我会再后期详细给出三种线程锁的详细特性和使用场景。敬请期待 ~~~~