多線程編程中經常會碰到多個線程訪問一個變量的問題,那麼我們先來熟悉下我們跟線程相關的修飾符nonatomic和atomic一搜索,會有很多文章;但是這些文章有一個共同的特點那就是nonatomic多線程不安全和atomic多線程安全如何來判斷線程安全或不安全?對於小公司在大多數項目說的簡單點安全就是不報錯,不安全就是報錯我寫了個demo驗證了下
@property (strong, nonatomic) NSMutableArray *arrList1;
@property (strong, atomic) NSMutableArray *arrList2;
定義了兩個變量最大的不同是一個用nonatomic,一個使用atomic然後,我開啓了多個線程
- (void)viewDidLoad {
[super viewDidLoad];
self.arrList1 = [[NSMutableArray alloc] init];
self.arrList2 = [[NSMutableArray alloc] init];
thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(addObject1) object:NULL];
thread2 = [[NSThread alloc] initWithTarget:self selector:@selector(addObject2) object:NULL];
thread3 = [[NSThread alloc] initWithTarget:self selector:@selector(addObject3) object:NULL];
thread4 = [[NSThread alloc] initWithTarget:self selector:@selector(addObject3) object:NULL];
}
-(void)addObject1{
for (int i = 0; i < 50; ++i) {
[self.arrList1 addObject:@"1111"];
NSLog(@"%@",self.arrList1);
}
}
-(void)addObject2{
for (int i = 0; i < 50; ++i) {
[self.arrList1 addObject:@"aaaa"];
NSLog(@"%@",self.arrList1);
}
}
-(void)addObject3{
for (int i = 0; i < 50; ++i) {
[self.arrList1 addObject:@"AAAA"];
NSLog(@"%@",self.arrList1);
}
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event {
[thread1 start];
[thread2 start];
[thread3 start];
}
三個線程都很給面子的訪問兩個不同的NSMutableArray確沒有報錯說好了線程不安全爲啥支持多線程訪問了?不知道是OC做了修改還是其他原因,怎麼跟Java中StringBuffer和StringBuilder一樣???說好了不安全,怎麼可以多線程訪問 ???不解
上面扯到的東西貌似跟今天我們講到的鎖沒什麼聯繫,但是如果你繼續搜索下去的話不少人介紹說跟鎖有關係,但是我看了很多篇日誌或文章沒找到一條論證的代碼,全都是文字介紹—這就扯犢子了,沒有代碼說明所有的結論都是廢的
鎖:我們可以理解爲 變量在某一段時間只能被一個線程訪問 貌似這個概念抽象的一塌糊塗;來段代碼加日誌截圖,說說情況和原因
在上面的基礎上,方便看到日誌;我們把代碼改爲循環10次
-(void)addObject1{
for (int i = 0; i < 10; ++i) {
[arrList addObject:@"1111"];
NSLog(@"%@" ,arrList);
}
}
-(void)addObject2{
for (int i = 0; i < 10; ++i) {
[arrList addObject:@"AAAA"];
NSLog(@"%@" ,arrList);
}
}
-(void)addObject3{
for (int i = 0; i < 10; ++i) {
[arrList addObject:@"aaaa"];
NSLog(@"%@" ,arrList);
}
}
注意這裏是把循環50次變爲循環10次,爲了看到日誌;三個方法分別對應三個線程;我們的理解是線程1,2,3誰先執行是不確定的但是按照我們的感覺是輸出的結果:
1111111111AAAAAAAAAAaaaaaaaaaa
或者
AAAAAAAAAA1111111111aaaaaaaaaa
或者類似這種排列,也就是輸出連貫的,再執行下一個連貫的循環輸出 ,但是我們看下我們的日誌
看到這樣的日誌輸出,我們彷彿對多線程有了那麼一點點的瞭解,for循環體還沒有結束而循環體裏面的可變數組卻被兩個的線程操作,這個就尷尬了那麼如何得到上面順序排列的值了,比如:AAAAAAAAAA1111111111aaaaaaaaaa;那麼我們需要做的就是加鎖防止循環沒有結束而被另外的線程訪問,我們來介紹下ios中的所謂的鎖
1. 關鍵字synchronized
如果從Java轉過來,那麼很容易理解了 ,Java中這個關鍵字使用的比較頻繁;這個關鍵字是修飾一個對象的,這個很關鍵!!!
-(void)addObject1{
@synchronized (arrList) {
for (int i = 0; i < 10; ++i) {
[arrList addObject:@"1111"];
NSLog(@"%@" ,arrList);
}
}
}
同樣在addObject2和addObject3方法中也要這樣寫,這樣寫神馬意思了 ? synchronized 是同步的意思,或者這樣理解通過synchronized 把 arrList關起來等這段代碼(被synchronized大括號括起來的代碼 )執行完之後再放出來,很顯然關起來的時候只允許一個線程對它操作,來張截圖
這結果簡直就是強迫症的福音
2. NSLock 鎖
這是OC中的一把鎖,那麼跟剛剛提到的synchronized有什麼區別了 ?剛剛上文提到synchronized只能給某一個對象加鎖,而NSLock可以個一段代碼加鎖;首先簡單的看下OC中有幾把鎖
一共四把鎖:NSLock、 NSConditionLock、 NSRecursiveLock 、NSCondition;但是他們不是繼承某一個類,而是都實現了NSLocking這個代理,那麼必須看看這個代碼有神馬東西
@protocol NSLocking
- (void)lock;
- (void)unlock;
@end
這個代理還是比較老實的,就提供兩個方法:加鎖(lock)和解鎖(unlock)
2.1 NSLock普通鎖
剛剛上面說過NSLock是鎖一段代碼的,那麼這個就比較簡單了,首先在ViewDidLoad中初始化NSLock ;然後使用這個鎖,上代碼
- (void)viewDidLoad {
[super viewDidLoad];
/*其他的代碼省略三千個字...*/
nslock = [[NSLock alloc] init];
}
-(void)addObject1{
[nslock lock];
for (int i = 0; i < 10; ++i) {
[arrList addObject:@"1111"];
NSLog(@"%@", arrList);
}
[nslock unlock];
}
-(void)addObject2{
[nslock lock];
for (int i = 0; i < 10; ++i) {
[arrList addObject:@"AAAA"];
NSLog(@"%@", arrList);
}
[nslock unlock];
}
-(void)addObject3{
[nslock lock];
for (int i = 0; i < 10; ++i) {
[arrList addObject:@"aaaa"];
NSLog(@"%@", arrList);
}
[nslock unlock];
}
也就是 從 [nslock lock] —> [nslock unlock] 這中間的代碼塊被鎖定只允許一個線程操作,等到循環完成之後自動執行解鎖操作;這裏我們可以看出synchronized 和 NSLock的區別
名稱 | 特點 |
---|---|
synchronized | 對一個對象加鎖 |
NSLock | 對一段代碼加鎖 |
2.2 NSConditionLock 條件鎖
條件鎖看到了源碼,我們可以這麼理解:在指定條件下加鎖(lock)和解鎖(unlock)
@interface NSConditionLock : NSObject <NSLocking> {
@private
void *_priv;
}
- (instancetype)initWithCondition:(NSInteger)condition NS_DESIGNATED_INITIALIZER;
@property (readonly) NSInteger condition;
- (void)lockWhenCondition:(NSInteger)condition;
- (BOOL)tryLock;
- (BOOL)tryLockWhenCondition:(NSInteger)condition;
- (void)unlockWithCondition:(NSInteger)condition;
- (BOOL)lockBeforeDate:(NSDate *)limit;
- (BOOL)lockWhenCondition:(NSInteger)condition beforeDate:(NSDate *)limit;
@property (nullable, copy) NSString *name NS_AVAILABLE(10_5, 2_0);
@end
不難看出它的初始化方法包含有一個條件,這個條件是個自定義的值;到XXX時候加鎖(lock)或解鎖(unlock)這個鎖到底怎麼用了 ?我發現網上都他媽沒有講清楚,而且是純扯犢子 。。。 這個鎖比較難理解,可以說是最難理解的一個鎖了,先上代碼和日誌
- (void)viewDidLoad {
[super viewDidLoad];
thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(addObject1) object:NULL];
thread2 = [[NSThread alloc] initWithTarget:self selector:@selector(addObject2) object:NULL];
nslock = [[NSConditionLock alloc] init];
}
-(void)addObject1{
NSLog(@"1111111111111111");
for (int i = 0; i < 10; ++i) {
[nslock lock];
[nslock unlockWithCondition:i];
NSLog(@"?????????");
[NSThread sleepForTimeInterval:1];
}
}
-(void)addObject2{
NSLog(@"22222222222222222");
[nslock lockWhenCondition:2];
NSLog(@"##############");
[nslock unlock];
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event {
[thread1 start];
[thread2 start];
}
我們看下日誌輸出神馬東西。。。
我們分析下代碼日誌輸出和代碼:
當兩個線程啓動的時候顯然兩個方法addObject1和addObject2都執行了 ,因爲我們可以看到1111111111111111 和 22222222222222222但是,並沒有執行addObject2下面的 NSLog(@”##############”);這個方法,而是直接執行addObject1的方法,說明:
lockWhenCondition沒有向下執行而是也就是上鎖失敗,當條件變成2的時候上鎖成功
NSConditionLock(條件鎖)總結
- NSConditionLock 是鎖,一旦一個線程獲得鎖,其他線程一定等待
- [xxxx lock]; 表示 xxx 期待獲得鎖,如果沒有其他線程獲得鎖(不需要判斷內部的condition)那它能執行此行以下代碼,如果已經有其他線程獲得鎖(可能是條件鎖,或者無條件鎖), 則等待,直至其他線程解鎖
- [xxx lockWhenCondition:A條件]; 表示如果沒有其他線程獲得該鎖,但是該鎖內部的condition不等於A條件,它依然不能獲得鎖,仍然等待。如果內部的condition等於A條件,並且沒有其他線程獲得該鎖,則進入代碼區,同時設置它獲得該鎖,其他任何線程都將等待它代碼的完成,直至它解鎖。
- [xxx unlockWithCondition:A條件]; 表示釋放鎖,同時把內部的condition設置爲A條件
- return = [xxx lockWhenCondition:A條件 beforeDate:A時間]; 表示如果被鎖定(沒獲得鎖),並超過該時間則不再阻塞線程。但是注意:返回的值是NO,它沒有改變鎖的狀態, 這個函數的目的在於可以實現兩種狀態下的處理
- 所謂的condition就是整數,內部通過整數比較條件
2.3 NSRecursiveLock 遞歸鎖
遞歸鎖是一種特殊的NSLock主要用在遞歸裏面,上代碼
- (void)viewDidLoad {
[super viewDidLoad];
thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(addObject1) object:NULL];
nslock = [[NSRecursiveLock alloc] init];
}
-(void)addObject1{
[self sumTotal:20];
}
-(void)sumTotal:(long)value{
[nslock lock];
if(value >0 ){
value -- ;
NSLog(@"%d",value);
[self sumTotal:value];
}
[nslock unlock];
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event {
[thread1 start];
}
如果不用NSRecursiveLock會怎麼樣了 ?也就是把上面的NSRecursiveLock換成NSLock,來張日誌截圖吧。。。
2.4 NSCondition 斷言
這應該是跟條件鎖一樣很操蛋的鎖,因爲這個鎖比較特殊,看看它的方法就覺得它奇葩了
@interface NSCondition : NSObject <NSLocking> {
@private
void *_priv;
}
- (void)wait;
- (BOOL)waitUntilDate:(NSDate *)limit;
- (void)signal;
- (void)broadcast;
@property (nullable, copy) NSString *name NS_AVAILABLE(10_5, 2_0);
@end
wait:等待線程
signal:喚醒一個指定線程
broadcast:喚醒所有線程
我們可以通過這種比較簡單的描述來理解下 ,老套路,上代碼;比較老掉牙的消費者和生產者
- (void)viewDidLoad {
[super viewDidLoad];
arrList = [[NSMutableArray alloc] init];
thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(addObject1) object:NULL];
thread2 = [[NSThread alloc] initWithTarget:self selector:@selector(addObject2) object:NULL];
nslock = [[NSCondition alloc] init];
UIButton *button = [[UIButton alloc] initWithFrame:CGRectMake(10, 10, 100, 100)];
button.backgroundColor = [UIColor redColor];
[button addTarget:self action:@selector(button1) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:button];
button = [[UIButton alloc] initWithFrame:CGRectMake(10, 210, 100, 100)];
button.backgroundColor = [UIColor greenColor];
[button addTarget:self action:@selector(button2) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:button];
}
-(void)button1{
[thread1 start];
}
-(void)button2{
[thread2 start];
}
-(void)addObject1{
[nslock lock];
while ([arrList count] == 0) {
NSLog(@"等待生產者 生產 !!!!");
[nslock wait];
}
[arrList removeObjectAtIndex:0];
NSLog(@"消費者消費了一個 產品");
[nslock unlock];
}
-(void)addObject2{
[nslock lock];
[arrList addObject:@"我是一個產品"];
NSLog(@"產生了一個產品");
[nslock signal];
[nslock unlock];
}
先啓動消費者,消費者會等待生產者生產,來個日誌
OC中的鎖就介紹到這裏,當然還去其他的C寫的鎖;這裏就不討論了 。。。