iOS基础 - 模拟两窗口售票:NSLock | @synchronized

▶ 线程安全

多线程同时访问一块公共资源是一件很危险的事!下面我们来模拟多窗口售票的情景

A. 使用 NSLock 保证线程安全!NSLock 是 OC 层封装底层线程操作来实现的一种锁,继承 NSLocking 协议,在此我们不讨论各种锁的实现细节,因为基本用不到。NSLock 的使用非常简单

 1 #import "ViewController.h"
 2 @interface ViewController (){
 3 
 4     NSInteger _TCNumber;     // 总票数
 5     NSInteger _ticketsCount; // 剩余票数
 6     NSLock   *_threadLock;   // 线程锁
 7 }
 8 
 9 @end
10 
11 @implementation ViewController
12 
13 - (void)viewDidLoad {
14     [super viewDidLoad];
15 
16     // 初始化
17     _TCNumber = 500;
18     _ticketsCount = _TCNumber;
19     _threadLock = [[NSLock alloc] init];
20 }
21 
22 // 开启售票:两个售票窗口
23 - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
24     [self saleTicketsWindow];
25 }
26 
27 - (void)saleTicketsWindow{
28 
29     // 线程A
30     NSThread *thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(makeSaled) object:nil];
31     thread1.name = @"窗口A";
32     // 线程B
33     NSThread *thread2 = [[NSThread alloc] initWithTarget:self selector:@selector(makeSaled) object:nil];
34     thread2.name = @"窗口B";
35     // 开启线程
36     [thread1 start];
37     [thread2 start];
38 }
39 
40 - (void)makeSaled{
41 
42     while (1) {
43 
44         // 上锁
45         [_threadLock lock];
46         
47         if (_ticketsCount > 0) {
48             _ticketsCount --;    // 出票
49             NSInteger saleCount = _TCNumber - _ticketsCount;// 卖出张数
50             NSLog(@"售出:%ld 剩余:%ld %@卖出的",(long)saleCount,(long)_ticketsCount,[NSThread currentThread].name);
51         }else{
52             NSLog(@"票已售完!");
53             [_threadLock unlock];// 解锁
54             break;
55         }
56         [_threadLock unlock]; // 解锁
57     }
58 }
59 
60 @end

日志信息:局部截图

B. 使用 @synchronized:它所做的事情跟锁类似,是用来防止不同的线程同时执行同一段代码。相比于使用 NSLock 创建锁对象、加锁和解锁来说,它用着更为方便、可读性更高,但并不是在任意场景下都能使用 @synchronized,它的性能较低

注:互斥锁其实就是使用了线程同步技术。它的优点就是能有效防止因多线程抢夺资源造成的数据安全问题,缺点就是需要消耗大量的 CPU 资源

 1 #import "ViewController.h"
 2 @interface ViewController ()
 3 
 4 @property (nonatomic,assign) NSInteger ticketsCount;// 剩余票数
 5 @property (nonatomic,strong) NSThread *thread1; // 线程1
 6 @property (nonatomic,strong) NSThread *thread2; // 线程2
 7 
 8 @end
 9 
10 @implementation ViewController
11 
12 - (void)viewDidLoad{
13     [super viewDidLoad];
14     
15     self.ticketsCount = 5;
16     // 开启两个线程,模拟售票员
17     self.thread1=[[NSThread alloc]initWithTarget:self selector:@selector(sellTickets) object:nil];
18     self.thread1.name=@"售票员A";
19     self.thread2=[[NSThread alloc]initWithTarget:self selector:@selector(sellTickets) object:nil];
20     self.thread2.name=@"售票员B";
21 }
22 
23 // 开启线程
24 -(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
25     [self.thread1 start];
26     [self.thread2 start];
27 }
28 
29 -(void)sellTickets{
30     
31     while (1) {
32         
33         @synchronized(self){
34             
35             long count = self.ticketsCount;
36             if (count > 0) {
37                 
38                 [NSThread sleepForTimeInterval:1.0];// 间隔 1 秒出票
39                 self.ticketsCount = count - 1;
40                 NSThread *currentTH = [NSThread currentThread];
41                 NSLog(@"%@--卖了一张票,还剩余%ld张票",currentTH,self.ticketsCount);
42             }else{
43                 NSLog(@"票已售完!");
44                 break;
45             }
46         }
47     }
48 }
49 
50 @end

使用 @synchronized 多次对同一个对象进行锁定不会导致任何后果或问题。因为每次执行 @synchronized 块时,就会检查对象是否已被锁定;如果是,则当前线程会等待直到锁被释放,一旦锁被释放,线程可以再次获得该锁并继续执行!

多次锁定同一个对象可能会导致线程等待的时问增加,因为每次获取锁时都需要等待前一个锁释放。这可能会影响程序的性能。建议在确保必要性的情況下,尽量避免多次对同一个对象使用 @synchronized 锁定!如果可能,可以使用其他同步机制来替代,例如使用 GCD 的串行队列或使用 NSLock

注:@synchronized 加锁对象不能为 nil,否则加锁无效,并不能保证线程安全

 

 

posted on 2021-11-15 01:53  低头捡石頭  阅读(110)  评论(0编辑  收藏  举报

导航