1 多线程的资源抢夺问题
案例:买票 两个端口同时进行买票,第一个端口买票是总票数是1000张,没等第一个端口买票完成,第二个端口也从总票数为1000时进行买票,返回时均为1000-1,造成了数据错误
2 解决资源抢夺问题:加锁实现多读单写
当多个线程资源抢夺和数据安全问题的两种解决方案:
- 互斥锁(即同步锁):
@synchronized
- 自旋锁
2.1 互斥锁
- 保护临界区,确保
同一时间,只有一条线程能够执行
只有一个地方需要加锁,大多都使用 self
,这样可以避免单独再创建一个锁对象 @synchronized(self)- 加了互斥锁的代码,当新线程访问时,如果发现其他线程正在执行锁定的代码,新线程就会进入
休眠
互斥锁特点
- 互斥锁的
锁定范围,应该尽量小
,锁定范围越大,效率越差 - 能够
加锁的任意 NSObject 对象
- 锁对象一定要保证所有的线程都能够访问
2.2 自旋锁
自旋锁
与互斥锁类似,但它不是通过休眠使线程阻塞,而是忙等(即原地打转,称为自旋)阻塞状态
- 使用场景:锁持有的时间短,且线程不希望在重新调度上花太多成本时,就需要使用自旋锁,属性修饰符
atomic
,本身就有一把自旋锁
- 加入了自旋锁,当新线程访问代码时,如果发现有其他线程正在锁定代码,新线程会用
死循环
的方法,一直等待锁定的代码执行完成,即不停的尝试执行代码,比较消耗性能
2.3 自旋锁与互斥锁的比较
-
相同点:在
同一时间
,保证了只有一条线程执行任务
,即同步
-
不同点:
互斥锁
:发现其他线程执行,当前线程休眠
(即就绪状态
),进入等待执行,即挂起。一直等其他线程打开之后,然后唤醒执行自旋锁
:发现其他线程执行,当前线程一直询问
,处于忙等状态
,耗费的性能
比较高
-
场景
:根据任务复杂度区分,使用不同的锁,但判断不全时,更多是使用互斥锁去处理- 当前的任务状态比较
短小精悍
时,用自旋锁
- 反之的,用互斥锁
- 当前的任务状态比较
2.4 atomic 原子锁 & nonatomic 非原子锁
atomic 和 nonatomic主要用于属性的修饰,以下是相关的一些说明:
-
atomic是
原子属性
,是为多线程开发准备
的,是默认属性!- 在属性的
setter
方法中,增加了锁(自旋锁)
,保证同一时间,只有一条线程
对属性进行写
操作 同一时间 单(线程)写多(线程)读
的线程处理技术
- Mac开发中常用
- 在属性的
-
nonatomic 是
非原子属性
没有锁
,性能高
- 移动端开发常用
atomic与nonatomic 的区别
-
nonatomic
非原子
属性非线程安全
,适合内存小的移动设备
-
atomic
原子
属性(线程安全),针对多线程设计
的,默认值- 保证
同一时间只有一个线程能够写入
(但是同一个时间多个线程都可以读值) - atomic 本身就有一把锁(
自旋锁
)单写多读
线程安全
,需要消耗大量的资源
iOS 开发的建议
- 所有
属性
都声明为nonatomic
- 尽量避免多线程抢夺同一块资源 尽量
将加锁、资源抢夺的业务逻辑交给服务器端处理,减小移动客户端的压力