一、多线程的安全隐患
资源共享1块资源可能会被多个线程共享,也就是多个线程可能会访问同一块资源
比如多个线程访问同一个对象、同一个变量、同一个文件
当多个线程访问同一块资源时,很容易引发数据错乱和数据安全问题
示例一:
示例二:
问题代码:
#import "ViewController.h"
@interface ViewController ()
/** 剩余票数 */
@property (nonatomic, assign) NSInteger leftTicketsCount;
@property (nonatomic, strong) NSThread *thread1;
@property (nonatomic, strong) NSThread *thread2;
@property (nonatomic, strong) NSThread *thread3;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// 默认有20张票
self.leftTicketsCount = 20;
// 开启多个线程,模拟售票员售票
self.thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(sellTickets) object:nil];
// 设置线程名称
self.thread1.name = @"售票员A";
self.thread2 = [[NSThread alloc] initWithTarget:self selector:@selector(sellTickets) object:nil];
// 设置线程名称
self.thread2.name = @"售票员B";
self.thread3 = [[NSThread alloc] initWithTarget:self selector:@selector(sellTickets) object:nil];
// 设置线程名称
self.thread3.name = @"售票员C";
}
// 售票
- (void)sellTickets
{
while (true) {
// 先检查票数
NSInteger count = self.leftTicketsCount;
if (count > 0) {
// 暂停一会(模拟卖票时间)
[NSThread sleepForTimeInterval:0.002];
// 票数减一
self.leftTicketsCount = count - 1;
//获取当前线程
NSThread *current = [NSThread currentThread];
NSLog(@"%@---卖了一张票,还剩余%zd张票", current, self.leftTicketsCount);
}else{
// 退出线程
[NSThread exit];
}
}
}
// 当手指触摸屏幕时,开启线程
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
// 开始售票(开启线程)
[self.thread1 start];
[self.thread2 start];
[self.thread3 start];
}
@end
打印结果:
二、安全隐患分析
三、如何解决
互斥锁使用格式@synchronized(锁对象) { // 需要锁定的代码 }
注意:锁定1份代码只用1把锁,用多把锁是无效的
代码示例:
#import "ViewController.h"
@interface ViewController ()
/** 剩余票数 */
@property (nonatomic, assign) NSInteger leftTicketsCount;
@property (nonatomic, strong) NSThread *thread1;
@property (nonatomic, strong) NSThread *thread2;
@property (nonatomic, strong) NSThread *thread3;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// 默认有20张票
self.leftTicketsCount = 20;
// 开启多个线程,模拟售票员售票
self.thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(sellTickets) object:nil];
// 设置线程名称
self.thread1.name = @"售票员A";
self.thread2 = [[NSThread alloc] initWithTarget:self selector:@selector(sellTickets) object:nil];
// 设置线程名称
self.thread2.name = @"售票员B";
self.thread3 = [[NSThread alloc] initWithTarget:self selector:@selector(sellTickets) object:nil];
// 设置线程名称
self.thread3.name = @"售票员C";
}
// 售票
- (void)sellTickets
{
while (true) {
// 锁对象:全局唯一的,而且只能有一把锁
@synchronized(self) {
// 先检查票数
NSInteger count = self.leftTicketsCount;
if (count > 0) {
// 暂停一会(模拟卖票时间)
[NSThread sleepForTimeInterval:0.002];
// 票数减一
self.leftTicketsCount = count - 1;
//获取当前线程
NSThread *current = [NSThread currentThread];
NSLog(@"%@---卖了一张票,还剩余%zd张票", current, self.leftTicketsCount);
}else{
// 退出线程
[NSThread exit];
}
}
}
}
// 当手指触摸屏幕时,开启线程
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
// 开始售票(开启线程)
[self.thread1 start];
[self.thread2 start];
[self.thread3 start];
}
@end
执行效果图
互斥锁的优缺点
优点:能有效防止因多线程抢夺资源造成的数据安全问题
缺点:需要消耗大量的CPU资源
互斥锁的使用前提:多条线程抢夺同一块资源
相关专业术语: 线程同步,多条线程按顺序地执行任务
互斥锁,就是使用了线程同步技术
四:原子和非原子属性
OC在定义属性时有nonatomic和atomic两种选择atomic:原子属性,为setter方法加锁(默认就是atomic)
nonatomic:非原子属性,不会为setter方法加锁
atomic加锁原理
@property (atomic, assign) NSString *name;
- (void)setName:(NSString *)name
{
@synchronized(self){
_name = name;
}
}
原子和非原子属性的选择
nonatomic和 atomic对比
atomic:线程安全,需要消耗大量的资源
nonatomic:非线程安全,适合内存小的移动设备
iOS开发的建议
所有属性都声明为 nonatomic
尽量避免多线程抢夺同一块资源
尽量将加锁、资源抢夺的业务逻辑交给服务器端处理,减小移动客户端的压力