多线程的安全隐患
- 当多个线程访问同一块资源时,很容易引发数据错乱和数据安全问题
安全隐患解决
方案一:使用“同步块(synchronization block)”加一把互斥锁
@synchronized(锁对象) {
//需要锁定的代码
}
注意:锁定1份代码之用一把锁,用多把锁是无效的
-
同步块(synchronization block)的优缺点
- 优点:能有效防止因多线程抢夺资源造成的数据安全问题
- 缺点:需要消耗大量的CPU资源(滥用@synchronized(锁对象)会降低代码效率,因为共用同一个锁的同步块,都必须按顺序执行,程序可能要等待另一段与此无关的代码执行完毕,才能继续执行当前代码,这样没必要)
- 同步块(synchronization block)的使用前提:多条线程抢夺同一块资源
-
同步块(synchronization block)使用了线程同步技术:多条线程在同一条线上执行(多条线程本来是并发执行的,线程同步之后,按顺序的执行任务,为了保证不抢东西,一个线程做完另外一个线程做)
示例代码
//
// ViewController.m
//
//
// Created by AYuan on 16/1/31.
// Copyright © 2016年 AYuan. All rights reserved.
//
#import "ViewController.h"
@interface ViewController ()
//为了在一开始创建线程不调用start也不死,这里搞一个强引用,因为创建一个进程如果没有启动它的话很有可能被销毁
/**
售票员0
*/
@property (nonatomic, strong) NSThread *thread0;
/**
售票员1
*/
@property (nonatomic, strong) NSThread *thread1;
/**
售票员2
*/
@property (nonatomic, strong) NSThread *thread2;
/**
票总数
*/
@property (nonatomic, assign) NSInteger ticktCount;
/**
锁对象
*/
@property (nonatomic, strong) NSObject *lockObj;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.ticktCount = 100;
self.lockObj = [[NSObject alloc] init];
self.thread0 = [[NSThread alloc] initWithTarget:self selector:@selector(saleTickts) object:nil];
self.thread0.name = @"售票员01";
self.thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(saleTickts) object:nil];
self.thread1.name = @"售票员02";
self.thread2 = [[NSThread alloc] initWithTarget:self selector:@selector(saleTickts) object:nil];
self.thread2.name = @"售票员3";
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
[self.thread0 start];
[self.thread1 start];
[self.thread2 start];
}
- (void)saleTickts
{
while (1) {
@synchronized(self.lockObj) {
//先取出总数
NSInteger count = self.ticktCount;
if (count > 0) {
self.ticktCount = count - 1;
NSLog(@"%@卖出一张票,剩余%zd",[NSThread currentThread].name,self.ticktCount);
} else {
NSLog(@"票卖完了");
break;
}
}
}
}
@end
方案二:使用NSLock对象
_lock = [[NSLock alloc] init];
- (void)synchronizedMethod {
[_lock lock];
//safe
[_lock unlock];
}
方案三(笔者推荐用法):使用GCD
-
使用GCD的"串行同步队列(serial synchronization queue)"将读取操作及写入操作都安排在同一个队列里,即可保证数据同步
- 如果队列中的块执行的任务简单的话则用法如下:
_syncQueue = dispatch_queue_create("com.ayuan.syncQueue", NULL); - (NSString *)someString { __block NSString *localSomeString; dispatch_sync(_syncQueue, ^{ localSomeString = _someString; }); return localSomeString; } - (void)setSomeString:(NSString *)someString { dispatch_sync(_syncQueue, ^{ _someString = someString; });
}
- 如果队列中的块执行的任务复杂则可以把同步派发改为异步派发
- (void)setSomeString:(NSString *)someString {
dispatch_async(_syncQueue, ^{
_someString = someString;
});
- 更高效的做法,使用同步队列及栅栏块:
- 在这个并发队列中,读取操作是用普通的块来实现的,而写入操作则是由栅栏块来实现的,读取操作可以并行,但写入操作必须单独执行,因为它是栅栏块
_syncQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
-
(NSString *)someString {
__block NSString *localSomeString;
dispatch_sync(_syncQueue, ^{
localSomeString = _someString;
});
return localSomeString;
} -
(void)setSomeString:(NSString *)someString {
dispatch_brrier_async(_syncQueue, ^{
_someString = someString;
});
}
##总结要点
- 使用GCD的同步语义比使用@synchronized块或者NSLock对象更简单
- 将同步与异步结合起来,可以实现同步加锁,还不会阻塞异步线程
- 使用同步队列及栅栏块使同步行为更加高效
网友评论