非线程安全
1 //初始化火车票数量、卖票窗口(非线程安全)、并开始卖票 2 - (void)initTicketStatusNotSave { 3 // 1. 设置剩余火车票为 50 4 self.ticketSurplusCount = 10; 5 6 // 2. 设置北京火车票售卖窗口的线程 7 self.ticketSaleWindow1 = [[NSThread alloc]initWithTarget:self selector:@selector(saleTicketNotSafe) object:nil]; 8 self.ticketSaleWindow1.name = @"北京火车票售票窗口"; 9 10 // 3. 设置上海火车票售卖窗口的线程 11 self.ticketSaleWindow2 = [[NSThread alloc]initWithTarget:self selector:@selector(saleTicketNotSafe) object:nil]; 12 self.ticketSaleWindow2.name = @"上海火车票售票窗口"; 13 14 // 4. 开始售卖火车票 15 [self.ticketSaleWindow1 start]; 16 [self.ticketSaleWindow2 start]; 17 18 } 19 20 /** 21 * 售卖火车票(非线程安全) 22 */ 23 - (void)saleTicketNotSafe { 24 while (1) { 25 //如果还有票,继续售卖 26 if (self.ticketSurplusCount > 0) { 27 self.ticketSurplusCount --; 28 NSLog(@"%@", [NSString stringWithFormat:@"剩余票数:%ld 窗口:%@", self.ticketSurplusCount, [NSThread currentThread].name]); 29 [NSThread sleepForTimeInterval:0.2]; 30 } 31 //如果已卖完,关闭售票窗口 32 else { 33 NSLog(@"所有火车票均已售完"); 34 break; 35 } 36 } 37 }
打印结果:
1 <-------------------------NSThread线程结束------------------------2018-04-04 13:44:34.640134+0800 StruggleSwift[3660:189882] 剩余票数:9 窗口:北京火车票售票窗口 2 --> 3 2018-04-04 13:44:37.602762+0800 StruggleSwift[3660:189887] 剩余票数:8 窗口:上海火车票售票窗口 4 2018-04-04 13:44:39.144970+0800 StruggleSwift[3660:189882] 剩余票数:6 窗口:北京火车票售票窗口 5 2018-04-04 13:44:39.144970+0800 StruggleSwift[3660:189887] 剩余票数:6 窗口:上海火车票售票窗口 6 2018-04-04 13:44:40.939316+0800 StruggleSwift[3660:189882] 剩余票数:5 窗口:北京火车票售票窗口 7 2018-04-04 13:44:40.939316+0800 StruggleSwift[3660:189887] 剩余票数:5 窗口:上海火车票售票窗口 8 2018-04-04 13:44:42.343211+0800 StruggleSwift[3660:189887] 剩余票数:4 窗口:上海火车票售票窗口 9 2018-04-04 13:44:42.343219+0800 StruggleSwift[3660:189882] 剩余票数:4 窗口:北京火车票售票窗口 10 2018-04-04 13:44:43.646726+0800 StruggleSwift[3660:189887] 剩余票数:3 窗口:上海火车票售票窗口 11 2018-04-04 13:44:43.646728+0800 StruggleSwift[3660:189882] 剩余票数:3 窗口:北京火车票售票窗口 12 2018-04-04 13:44:45.168354+0800 StruggleSwift[3660:189882] 剩余票数:2 窗口:北京火车票售票窗口 13 2018-04-04 13:44:45.168361+0800 StruggleSwift[3660:189887] 剩余票数:2 窗口:上海火车票售票窗口 14 2018-04-04 13:44:47.232723+0800 StruggleSwift[3660:189882] 剩余票数:0 窗口:北京火车票售票窗口 15 2018-04-04 13:44:47.232735+0800 StruggleSwift[3660:189887] 剩余票数:0 窗口:上海火车票售票窗口 16 2018-04-04 13:44:49.086100+0800 StruggleSwift[3660:189882] 所有火车票均已售完 17 2018-04-04 13:44:49.086100+0800 StruggleSwift[3660:189887] 所有火车票均已售完
总结:可以看到在线程不安全的情况下,得到票数是错乱的,这样显然不符合我们的需求,所以我们需要考虑线程安全问题。
NSThread 线程安全
线程安全解决方案:可以给线程加锁,在一个线程执行该操作的时候,不允许其他线程进行操作。iOS 实现线程加锁有很多种方式。@synchronized、 NSLock、NSRecursiveLock、NSCondition、NSConditionLock、pthread_mutex、dispatch_semaphore、OSSpinLock、atomic(property) set/ge等等各种方式。为了简单起见,这里不对各种锁的解决方案和性能做分析,只用最简单的@synchronized
来保证线程安全,从而解决线程同步问题。
1 //初始化火车票数量、卖票窗口(线程安全)、并开始卖票 2 - (void)initTicketStatusSave { 3 // 1. 设置剩余火车票为 50 4 self.ticketSurplusCount = 10; 5 6 // 2. 设置北京火车票售卖窗口的线程 7 self.ticketSaleWindow1 = [[NSThread alloc]initWithTarget:self selector:@selector(saleTicketSafe) object:nil]; 8 self.ticketSaleWindow1.name = @"北京火车票售票窗口"; 9 10 // 3. 设置上海火车票售卖窗口的线程 11 self.ticketSaleWindow2 = [[NSThread alloc]initWithTarget:self selector:@selector(saleTicketSafe) object:nil]; 12 self.ticketSaleWindow2.name = @"上海火车票售票窗口"; 13 14 // 4. 开始售卖火车票 15 [self.ticketSaleWindow1 start]; 16 [self.ticketSaleWindow2 start]; 17 18 } 19 20 /** 21 * 售卖火车票(线程安全) 22 */ 23 - (void)saleTicketSafe { 24 while (1) { 25 // 互斥锁 26 @synchronized (self) { 27 //如果还有票,继续售卖 28 if (self.ticketSurplusCount > 0) { 29 self.ticketSurplusCount --; 30 NSLog(@"%@", [NSString stringWithFormat:@"剩余票数:%ld 窗口:%@", self.ticketSurplusCount, [NSThread currentThread].name]); 31 [NSThread sleepForTimeInterval:0.2]; 32 } 33 //如果已卖完,关闭售票窗口 34 else { 35 NSLog(@"所有火车票均已售完"); 36 break; 37 } 38 } 39 } 40 }
打印结果:
1 2018-04-09 10:38:20.960196+0800 StruggleSwift[1190:23146] 剩余票数:9 窗口:北京火车票售票窗口 2 2018-04-09 10:38:22.523697+0800 StruggleSwift[1190:23147] 剩余票数:8 窗口:上海火车票售票窗口 3 <-------------------------NSThread线程结束--------------------------> 4 2018-04-09 10:38:27.273431+0800 StruggleSwift[1190:23146] 剩余票数:7 窗口:北京火车票售票窗口 5 2018-04-09 10:38:27.273459+0800 StruggleSwift[1190:23147] 剩余票数:6 窗口:上海火车票售票窗口 6 2018-04-09 10:38:34.442708+0800 StruggleSwift[1190:23683] 剩余票数:5 窗口:北京火车票售票窗口 7 2018-04-09 10:38:34.442731+0800 StruggleSwift[1190:23146] 剩余票数:4 窗口:北京火车票售票窗口 8 2018-04-09 10:38:34.442731+0800 StruggleSwift[1190:23147] 剩余票数:3 窗口:上海火车票售票窗口 9 2018-04-09 10:38:34.647692+0800 StruggleSwift[1190:23147] 剩余票数:2 窗口:上海火车票售票窗口 10 2018-04-09 10:38:34.647657+0800 StruggleSwift[1190:23146] 剩余票数:1 窗口:北京火车票售票窗口 11 2018-04-09 10:38:36.308703+0800 StruggleSwift[1190:23694] 剩余票数:0 窗口:上海火车票售票窗口 12 2018-04-09 10:38:36.512623+0800 StruggleSwift[1190:23683] 所有火车票均已售完 13 2018-04-09 10:38:36.512589+0800 StruggleSwift[1190:23146] 所有火车票均已售完 14 2018-04-09 10:38:36.512572+0800 StruggleSwift[1190:23147] 所有火车票均已售完 15 2018-04-09 10:38:37.557290+0800 StruggleSwift[1190:23694] 所有火车票均已售完
结论:在考虑了线程安全的情况下,加锁之后,得到的票数是正确的,没有出现混乱的情况。
线程的状态转换
SThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];
,在内存中的表现为:
当调用[thread start];
后,系统把线程对象放入可调度线程池中,线程对象进入就绪状态,如下图所示。
当然,可调度线程池中,会有其他的线程对象,如下图所示。在这里我们只关心左边的线程对象。
当前线程的状态转换
a. 如果CPU现在调度当前线程对象,则当前线程对象进入运行状态,如果CPU调度其他线程对象,则当前线程对象回到就绪状态。
b. 如果CPU在运行当前线程对象的时候调用了sleep方法等待同步锁,则当前线程对象就进入了阻塞状态,等到sleep到时得到同步锁,则回到就绪状态。
c. 如果CPU在运行当前线程对象的时候线程任务执行完毕异常强制退出,则当前线程对象进入死亡状态。
只看文字可能不太好理解,具体当前线程对象的状态变化如下图所示。