NSLock 是 Objective-C 提供的一种 轻量级互斥锁,用于保证多线程访问共享资源的安全性。相比 @synchronized,它的性能更好,并且提供了更灵活的锁管理方法。
1. NSLock 的基本使用
1)lock和unlock
@interface SafeCounter : NSObject
@property (nonatomic, strong) NSLock *lock;
@property (nonatomic, assign) NSInteger count;
@end@implementation SafeCounter- (instancetype)init {if (self = [super init]) {_lock = [[NSLock alloc] init];}return self;
}- (void)increment {[self.lock lock]; // 加锁self.count += 1;NSLog(@"当前 count: %ld", (long)self.count);[self.lock unlock]; // 解锁
}@end
lock 确保 count 在多个线程访问时不会竞争,避免数据错乱。lock 后必须 unlock,否则会导致死锁。
2)我们还可以使用tryLock(尝试加锁,不阻塞),如果 tryLock 获取到锁,返回 YES,否则返回 NO,不会阻塞线程:
if ([self.lock tryLock]) {// 线程安全代码[self.lock unlock];
} else {NSLog(@"锁已被占用,执行其他逻辑");
}
适用于低优先级任务,避免等待锁导致线程卡住。
3)lockBeforeDate:(限时加锁)
设置超时时间,超过这个时间还拿不到锁,就放弃:
if ([self.lock lockBeforeDate:[NSDate dateWithTimeIntervalSinceNow:2]]) {// 线程安全代码[self.lock unlock];
} else {NSLog(@"超时未获取到锁");
}
适用于有时间敏感性的任务,防止线程长时间等待锁。
2. NSLock 能解决哪些问题?
-
防止数据竞争
- 例如多个线程同时读写
NSMutableArray、NSMutableDictionary时可能崩溃,NSLock可以确保一个线程完成操作后,另一个线程才能访问。
- 例如多个线程同时读写
-
避免死锁
NSLock提供tryLock和lockBeforeDate:方法,可以防止线程长时间等待锁,降低死锁的风险。
-
提高
@synchronized的性能-
@synchronized内部也是基于NSLock实现的,但多了一些管理开销,因此NSLock更轻量,性能更高。
-
3、注意事项
避免多个线程交叉锁(死锁)
如果一个线程持有 lock1,等待 lock2,而另一个线程持有 lock2,等待 lock1,就会产生死锁:
[self.lock1 lock];
[self.lock2 lock]; // 这里可能导致死锁
[self.lock2 unlock];
[self.lock1 unlock];
正确做法:
- 尽量保持加锁顺序一致,避免交叉等待。
- 考虑用
tryLock失败时执行其他逻辑。
总结
NSLock是@synchronized的更轻量级版本,适合需要手动管理锁的场景。- 支持
tryLock和lockBeforeDate:,可以避免死锁,提高程序的健壮性。 - 必须保证
lock之后一定unlock,避免死锁或线程卡死。 - 适用于高频加锁解锁场景,但不支持递归调用(可用
NSRecursiveLock代替)。
