多线程编程是有趣的事情,它很容易突然出现“错误情况”,这是由于系统的线程调度具有一定的随机性造成的。不过,即使程序偶然出现“错误情况”,这是由于系统的线程调度具有一定的随机性造成的。不过,即使程序偶然出现问题,那也是由于编程不当所引起的,当使用多个线程来访问同一个数据时,很容易“偶然”出现线程安全问题。
线程安全问题
关于线程安全问题,有一个景点的问题:银行取钱的问题。银行取钱基本可以分为如下几个步骤。
1,用户输入账号,密码,系统判断用户的 账号,密码是否匹配。
2,用户输入取款金额。
3,判断用户账户余额是否大于取款金额。
4,如果余额大于取款金额,则取款成功;如果余额小于取款金额,则取款失败。

演示代码:
账户类接口代码:

#import <Foundation/Foundation.h>@interface FKAccount : NSObject
// 封装账户编号、账户余额两个属性
@property (nonatomic, copy) NSString* accountNo;
@property (nonatomic, readonly) CGFloat balance;
- (id)initWithAccountNo:(NSString*)accountNobalance:(CGFloat)balance;
- (void) draw:(CGFloat)drawAmount;
@end
#import "FKAccount.h"@implementation FKAccount
- (id)initWithAccountNo:(NSString*)aAccountbalance:(CGFloat)aBalance
{self = [super init];if (self) {_accountNo = aAccount;_balance = aBalance;}return self;
}
// 提供一个draw方法来完成取钱操作
- (void) draw:(CGFloat)drawAmount
{// 账户余额大于取钱数目if (self.balance >= drawAmount){// 吐出钞票NSLog(@"%@取钱成功!吐出钞票:%g", [NSThread currentThread].name, drawAmount);
//      [NSThread sleepForTimeInterval:0.001];// 修改余额_balance = _balance - drawAmount;NSLog(@"\t余额为: %g" , self.balance);}else{NSLog(@"%@取钱失败!余额不足!", [NSThread currentThread].name);}
}
- (NSUInteger) hash
{return [self.accountNo hash];
}
- (BOOL)isEqual:(id)anObject
{if(self == anObject)return YES;if (anObject != nil&& [anObject class] == [FKAccount class]){FKAccount* target = (FKAccount*)anObject;return [target.accountNo isEqualToString:self.accountNo];}return NO;
}
@end
#import "FKViewController.h"
#import "FKAccount.h"@interface FKViewController ()@end@implementation FKViewController
FKAccount* account;
- (void)viewDidLoad
{[super viewDidLoad];// 创建一个账号account = [[FKAccount alloc] initWithAccountNo:@"321231" balance: 1000.0];
}
- (IBAction)draw:(id)sender
{// 创建第1个线程对象 NSThread* thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(drawMethod:) object:[NSNumber numberWithInt:800]]; // 创建第2个线程对象 NSThread* thread2 = [[NSThread alloc] initWithTarget:self selector:@selector(drawMethod:) object:[NSNumber numberWithInt:800]]; // 启动2条线程 [thread1 start]; [thread2 start];}
- (void) drawMethod:(NSNumber*) drawAmount
{// 直接调用account对象的draw方法来执行取钱[account draw:drawAmount.doubleValue];
}

运行结果1如下:

2015-12-18 10:49:00.942 DrawTest[21771:4480081] 取钱成功!吐出钞票:800
2015-12-18 10:49:00.942 DrawTest[21771:4480080] 取钱成功!吐出钞票:800
2015-12-18 10:49:00.943 DrawTest[21771:4480081]     余额为: 200
2015-12-18 10:49:00.943 DrawTest[21771:4480080]     余额为: -600

按照正常的执行逻辑,应该是第1个线程可以取到钱,第2个线程显示”余额不足”。但运行结果并不是期望的结果(不过也有可能看到运行正确的结果),这正是多线程编程突然出现的“偶然”错误——因为线程调度的不确定性。我们来分析运行结果,账户余额只有1000时取出了1600,而且账户余额出现了负值,这不是银行希望的结果。虽然上面程序是人为地使用Thread.sleep(1)来强制线程调度的切换,但这种切换也是完全有可能发生的——100000次操作只要有1次出现了错误,那就是编程错误引起的。

使用@synchronized实现同步
之所以会出现运行结果1所示的结果,是因为线程执行体的方法不具备同步安全性——程序中有两个并发线程在修改FKAccount对象。而且系统恰好在红色字体代码处执行线程切换,切换给另一个修改FKAccount对象的编程,所以就出现了问题。
为了解决这个问题,Object-C的多线程支持引入了同步,使用同步的通用方法就是@synchronized修饰代码块,被@synchronsized修饰的代码块可以建成为 同步代码块。同步代码块的语法格式如下:

@synchronsized(obj){
...
//此处的代码就是同步代码块
}

修改后的代码如下:


// 提供一个线程安全的draw方法来完成取钱操作
- (void) draw:(CGFloat)drawAmount
{// 使用self作为同步监视器,任何线程进入下面同步代码块之前,// 必须先获得对self账户的锁定——其他线程无法获得锁,也就无法修改它// 这种做法符合:“加锁 → 修改 → 释放锁”的逻辑    @synchronized(self){// 账户余额大于取钱数目if (self.balance >= drawAmount){// 吐出钞票NSLog(@"%@取钱成功!吐出钞票:%g", [NSThread currentThread].name, drawAmount);[NSThread sleepForTimeInterval:0.001];// 修改余额_balance = _balance - drawAmount;NSLog(@"\t余额为: %g" , self.balance);}else{NSLog(@"%@取钱失败!余额不足!", [NSThread currentThread].name);}}//同步代码块结束,该线程释放同步锁
}

运行结果2如下:


大家可以看到结果正常了,达到了我们想要的目的。

通过这种方式可以非常方便地实现线程安全的类,线程安全的类具有如下特征:
1,该类的对象可以被多个线程安全地访问。
2,每个线程调用该对象的任意方法之后都将得到正确结果。
3,每个线程调用该对象的任意方法之后,该对象依然保持合理状态。
Foundation框架中很多类都有可变和不可变两种版本,比如NSString,NSArray就是不可变类,而NSMutableString,NSMutableArray就是可变类。其中不可变类总是线程安全的,因为它的对象不可改变。但可变类的对象需要额外的方法来保证其线程安全。例如上面的FKAccount类就是一个可变类,它的accountNo和balance两个属性都可变,当两个线程同时修改FKAccount对象的balance时,程序就可能出现异常。
可变类的线程安全是以降低程序的运行效率作为代价的。为了减少线程安全所带来的负面影响,程序可以采用如下策略。
1,不要对线程安全类的所有方法都进行同步,只对那些会改变竞争资源(竞争资源也就是共享资源)的方法进行同步。例如上面的FKAccount类中的accountNo属性就无须同步,所以程序只对draw方法进行同步控制。
2,如果可变类有两种运行环境:但线程环境和多线程环境,则应该为该可变类提供两种版本——线程不安全版本和线程安全版本。在单线程环境中使用线程不安全版本以保证性能,在多线程环境中使用线程安全版本。

释放对同步监视器的锁定
任何线程在进入同步代码之前,必须先获得对同步代码监视器的锁定,那么何时会释放对同步监视器的锁定呢?程序无法显式释放对同步监视器的锁定,线程会在如下几种情况下释放对同步监视器的锁定。
1,当前线程的同步代码块之行结束,当前线程即释放同步监视器。
2,当线程在同步代码块中遇到goto,return终止了该代码块,该方法的继续执行时,当前线程将会释放同步监视器。
3,当线程在同步代码块中出现了错误,导致该代码异常结束时,将会释放同步监视器。典型地,当程序调用 NSThread的sleepXxx方法暂停线程时,线程不会释放同步监视器。

同步锁
Foundation 还提供了 NSLock,它通过显式定义同步锁来实现同步,在这种机制下,同步锁使用NSLock对象充当。
NSLock是控制多个线程对共享资源进行访问的工具。通常提供了对共享资源的独占访问,每次只能有一个线程对NSLock对象加锁,线程开始访问共享资源前,应先获得NSLock对象。
演示代码如下:

// 提供一个线程安全的draw方法来完成取钱操作
- (void) draw:(CGFloat)drawAmount
{// 显式锁定lock对象[lock lock];// 账户余额大于取钱数目if (self.balance >= drawAmount){// 吐出钞票NSLog(@"%@取钱成功!吐出钞票:%g", [NSThread currentThread].name, drawAmount);[NSThread sleepForTimeInterval:0.001];// 修改余额_balance = _balance - drawAmount;NSLog(@"\t余额为: %g" , self.balance);}else{NSLog(@"%@取钱失败!余额不足!", [NSThread currentThread].name);}// 释放lock的锁定[lock unlock];
}

运行结果3如下:


使用NSCondition控制线程通信
当线程在系统内运行时,线程的调度具有一定的透明性,程序通常无法准确控制线程的轮换执行,但我们可以通过一些机制来保证线程协调运行,也就是处理线程之间的通信。
Foundation提供了一个NSCondition类来处理线程通信,NSCondition实现了NSLocking协议, 因此也可以调用lock,unlock来实现线程同步。除此之外,NSCondition可以让那些已经锁定NSCondition对象却无法继续执行的线程释放NSCondition对象,NSCondition对象也可以唤醒其他处于等待状态的线程。
NSCondition类提供了如下3个方法。
1,- wait :该方法导致当前线程一直等待,直到其他线程调用该NSCondition的signal方法或broadcast方法来唤醒该线程。wait 方法有一个变体: -(BOOL)waitUntilDate:(NSDate *)limiteout, 用于控制等待到指定时间点,如果到了该时间点,该线程将会被自动唤醒。
2, - signal : 唤醒在此NSCondition对象上等待的单个线程。如果所有线程都在该NSCondition对象上等待,则会选择唤醒其中一个线程。选择是任意性的。只有当前线程放弃对该NScondition对象的锁定后(使用wait 方法),才可以执行被唤醒的线程。
3,-broadcast : 唤醒在此NSCondition对象上等待的所有线程。只有当前线程放弃对该NSCondition对象的锁定后,才可以执行被唤醒的线程。

IOS多线程系统学习之线程同步与线程通信相关推荐

  1. iOS开发——高级篇——线程同步、线程依赖、线程组

    前言 对于iOS开发中的网络请求模块,AFNet的使用应该是最熟悉不过了,但你是否把握了网络请求正确的完成时机?本篇文章涉及线程同步.线程依赖.线程组等专用名词的含义,若对上述名词认识模糊,可先进行查 ...

  2. 多线程——线程实现、线程状态、线程同步、线程通信、线程池

    多线程 一.线程 1.普通方法调用和多线程 2.程序.进行.线程 二.线程创建 1.继承Thread类 2.实现Runable接口 3.实现Callable接口 4.静态代理模式 5.Lamda表达式 ...

  3. python 测试 多线程 _thread和threading模块 线程同步,线程优先级队列

    文章目录 python 多线程简介 Python中使用线程的两种方式 1.函数式 示例 2.线程模块 示例 线程同步 示例 线程优先级队列( Queue)[暂时没用到,没仔细看] 示例 其他 thre ...

  4. 什么是线程同步和线程异步?

    1.什么是线程同步和线程异步 线程同步:是多个线程同时访问同一资源,等待资源访问结束,浪费时间,效率不高 线程异步:访问资源时,如果有空闲时间,则可在空闲等待同时访问其他资源,实现多线程机制 异步处理 ...

  5. 线程(线程基本概念、java实现多线程、使用多线程、线程的生命周期、线程同步、线程死锁)

    (一)线程基本概念 一. 程序, 进程, 线程的概念 程序: 使用某种语言编写一组指令(代码)的集合,静态的 进程: 运行的程序,表示程序一次完整的执行, 当程序运行完成, 进程也就结束了 个人电脑: ...

  6. java 多线程编程(包括创建线程的三种方式、线程的生命周期、线程的调度策略、线程同步、线程通信、线程池、死锁等)

    1 多线程的基础知识 1.1 单核CPU和多核CPU 单核CPU,其实是一种假的多线程,因为在一个时间单元内,也只能执行一个线程的任务.微观上这些程序是分时的交替运行,只不过是给人的感觉是同时运行,那 ...

  7. Java多线程编程——线程同步与线程安全问题及synchronized关键字

    在多线程环境下,我们常常需要让多个线程同时去操作同一资源.在某些情况下,这种情形会导致程序的运行结果出现差错.专业上的,当多个线程在执行同一段代码的时候,每次的执行结果和单线程执行的结果都是一样的,不 ...

  8. 【多线程编程学习笔记6】终止线程执行,千万别踩这个坑!

    申明:本学习笔记是在该教程的基础上结合自己的学习情况进行的总结,不是原创,想要看原版的请看C语言中文网的多线程编程(C语言+Linux),该网站有很多好的编程学习教程,尤其是关于C语言的. 在< ...

  9. java多线程、线程同步与线程池

    1. 线程的基本概念 1.1 进程 任何的软件存储在磁盘中,运行软件的时候,OS使用IO技术,将磁盘中的软件的文件加载到内存,程序在能运行. 进程的概念 : 应用程序(typora,word,IDEA ...

最新文章

  1. 专家点评Nat Micro | 朱永群组首次发现特异地切割线性泛素链的全新去泛素化酶...
  2. 文档标题:WinNTWin2K下实现进程的完全隐藏
  3. zabbix 进阶(二)
  4. 等价类划分应用的扩展
  5. 推荐系统resys小组线下活动见闻2009-08-22
  6. Python 类函数迭代器
  7. 【MM配置】Inventory Management 库存管理
  8. 数据结构与算法 / 排序算法(2)
  9. 20.IDA-修改二进制文件、显示修改点
  10. 【离散数学中的数据结构与算法】五 排列与组合一
  11. Python之%s%d%f使用实例
  12. python提取高频词_seo与python大数据结合给文本分词并提取高频词
  13. java 6和_java都到6了 有什么不同 哦????
  14. 2018贝壳找房研发校招笔试题
  15. SyntaxError: Missing parentheses in call to 'print' 这个错误原因是Python版本问题
  16. 记一次easywechat企业付款问题
  17. wordpress付费阅读_2020年27个最佳WordPress杂志主题[免费+付费]
  18. 在无任何报错的情况下 pagehelper.startpage分页无效问题
  19. win7电脑麦克风有电流声怎么办
  20. 8K播放网络全终端播放器H5播放器网页直播/点播播放器EasyPlayer和vlc播放RTSP流地址不兼容问题排查解决

热门文章

  1. 机器学习评价指标PRF的计算(转载)
  2. 渗透测试之信息收集思维导图
  3. 2021ICPC昆明区域赛
  4. 工业相机的帧率和曝光(快门)之间的关系
  5. MapXtreme 2005 鹰眼源代码
  6. 报刊英语单词精华-1
  7. 短暂的人生,不管你选择什么,都要对得起时间
  8. 树莓派启用看门狗watchdog
  9. linux下进程的模式和类型如何,Linux——基础概念和命令
  10. 什么是Autosar?