NSThread线程对象
NSThread
创建线程的方式
- 准备在后台线程调用的方法
longOperation:
- (void)longOperation:(id)obj {
NSLog(@"%@ - %@", [NSThread currentThread], obj);
}
方式1:alloc / init - start
- (void)threadDemo1 {
NSLog(@"before %@", [NSThread currentThread]);
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(longOperation:) object:@"THREAD"];
[thread start];
NSLog(@"after %@", [NSThread currentThread]);
}
代码小结
[thread start];
执行后,会在另外一个线程执行longOperation:
方法- 在 OC 中,任何一个方法的代码都是从上向下顺序执行的
- 同一个方法内的代码,都是在相同线程执行的(
block
除外)
方式2:detachNewThreadSelector
- (void)threadDemo2 {
NSLog(@"before %@", [NSThread currentThread]);
[NSThread detachNewThreadSelector:@selector(longOperation:) toTarget:self withObject:@"DETACH"];
NSLog(@"after %@", [NSThread currentThread]);
}
代码小结
detachNewThreadSelector
类方法不需要启动,会自动创建线程并执行@selector
方法
方式3:分类方法
- (void)threadDemo3 {
NSLog(@"before %@", [NSThread currentThread]);
[self performSelectorInBackground:@selector(longOperation:) withObject:@"PERFORM"];
NSLog(@"after %@", [NSThread currentThread]);
}
代码小结
performSelectorInBackground
是NSObject
的分类方法- 会自动在后台线程执行
@selector
方法 - 没有
thread
字眼,隐式
创建并启动线程 - 所有
NSObject
都可以使用此方法,在其他线程执行方法
NSThread 的 Target
NSThread
的实例化方法中的target
指的是开启线程后,在线程中执行哪一个对象
的@selector
方法
代码演练
- 准备对象
@interface Person : NSObject
@property (nonatomic, copy) NSString *name;
@end
@implementation Person
+ (instancetype)personWithDict:(NSDictionary *)dict {
id obj = [[self alloc] init];
[obj setValuesForKeysWithDictionary:dict];
return obj;
}
- (void)longOperation:(id)obj {
NSLog(@"%@ - %@ - %@", [NSThread currentThread], self.name, obj);
}
@end
- 定义属性
@property (nonatomic, strong) Person *person;
- 懒加载
- (Person *)person {
if (_person == nil) {
_person = [Person personWithDict:@{@"name": @"zhangsan"}];
}
return _person;
}
三种线程调度方法
- alloc / init
NSThread *thread = [[NSThread alloc] initWithTarget:self.person selector:@selector(longOperation:) object:@"THREAD"];
[thread start];
- detach
[NSThread detachNewThreadSelector:@selector(longOperation:) toTarget:self.person withObject:@"DETACH"];
- 分类方法
[self.person performSelectorInBackground:@selector(longOperation:) withObject:@"PERFORM"];
代码小结
- 通过指定不同的
target
会在后台线程执行该对象的@selector
方法 - 提示:不要看见
target
就写self
performSelectorInBackground
可以让方便地在后台线程执行任意NSObject
对象的方法
线程状态
线程状态
新建
- 实例化线程对象
就绪
- 向线程对象发送
start
消息,线程对象被加入可调度线程池
等待 CPU 调度 detach
方法和performSelectorInBackground
方法会直接实例化一个线程对象并加入可调度线程池
- 向线程对象发送
运行
- CPU 负责调度
可调度线程池
中线程的执行 - 线程执行完成之前,状态可能会在
就绪
和运行
之间来回切换 就绪
和运行
之间的状态变化由 CPU 负责,程序员不能干预
- CPU 负责调度
阻塞
- 当满足某个预定条件时,可以使用休眠或锁阻塞线程执行
sleepForTimeInterval
:休眠指定时长sleepUntilDate
:休眠到指定日期@synchronized(self)
:乎斥锁
- 当满足某个预定条件时,可以使用休眠或锁阻塞线程执行
死亡
- 正常死亡
- 线程执行完毕
- 非正常死亡
- 当满足某个条件后,在线程内部中止执行
- 当满足某个条件后,在主线程中止线程对象
- 正常死亡
代码演练
- (void)statusDemo {
NSLog(@"先睡会");
[NSThread sleepForTimeInterval:1.0];
for (int i = 0; i < 20; i++) {
if (i == 9) {
NSLog(@"再睡会");
[NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:1.0]];
}
NSLog(@"%d %@", i, [NSThread currentThread]);
if (i == 16) {
NSLog(@"88");
// 终止线程之前,需要记住释放资源
[NSThread exit];
}
}
NSLog(@"over");
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
// 注意不要在主线程上调用 exit 方法
// [NSThread exit];
// 实例化线程对象(新建)
NSThread *t = [[NSThread alloc] initWithTarget:self selector:@selector(statusDemo) object:nil];
// 线程就绪(被添加到可调度线程池中)
[t start];
}
代码小结
阻塞
方法执行过程,符合某一条件时,可以利用
sleep
方法让线程进入阻塞
状态sleepForTimeInterval
从现在起睡多少秒
sleepUntilDate
从现在起睡到指定的日期
死亡
[NSThread exit];
- 一旦强行终止线程,后续的所有代码都不会被执行
- 注意:在终止线程之前,应该注意释放之前分配的对象!
注意:线程从
就绪
和运行
状态之间的切换是由CPU
负责的,程序员无法干预
线程属性
代码演练
// MARK: - 线程属性
- (void)threadProperty {
NSThread *t1 = [[NSThread alloc] initWithTarget:self selector:@selector(demo) object:nil];
// 1. 线程名称
t1.name = @"Thread AAA";
// 2. 优先级
t1.threadPriority = 0;
[t1 start];
NSThread *t2 = [[NSThread alloc] initWithTarget:self selector:@selector(demo) object:nil];
// 1. 线程名称
t2.name = @"Thread BBB";
// 2. 优先级
t2.threadPriority = 1;
[t2 start];
}
- (void)demo {
for (int i = 0; i < 10; ++i) {
// 堆栈大小
NSLog(@"%@ 堆栈大小:%tuK", [NSThread currentThread], [NSThread currentThread].stackSize / 1024);
}
// 模拟崩溃
// 判断是否是主线程
// if (![NSThread currentThread].isMainThread) {
// NSMutableArray *a = [NSMutableArray array];
//
// [a addObject:nil];
// }
}
属性
1. name
- 线程名称
- 在大的商业项目中,通常需要在程序崩溃时,获取程序准确执行所在的线程
2. threadPriority
- 线程优先级
- 优先级,是一个浮点数,取值范围从
0~1.0
1.0
表示优先级最高0.0
表示优先级最低- 默认优先级是
0.5
- 优先级高只是保证 CPU 调度的可能性会高
- 刀哥个人建议,在开发的时候,不要修改优先级
- 多线程的目的:是将耗时的操作放在后台,不阻塞主线程和用户的交互!
- 多线程开发的原则:简单
3. stackSize
- 栈区大小
- 默认情况下,无论是主线程还是子线程,栈区大小都是
512K
- 栈区大小可以设置
[NSThread currentThread].stackSize = 1024 * 1024;
4. isMainThread
- 是否主线程
资源共享
资源共享-卖票
多线程开发的复杂度相对较高,在开发时可以按照以下套路编写代码:
- 首先确保单个线程执行正确
- 添加线程
卖票逻辑
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
self.tickets = 20;
[self saleTickets];
}
/// 卖票逻辑 - 每一个售票逻辑(窗口)应该把所有的票卖完
- (void)saleTickets {
while (YES) {
if (self.tickets > 0) {
self.tickets--;
NSLog(@"剩余票数 %d %@", self.tickets, [NSThread currentThread]);
} else {
NSLog(@"没票了 %@", [NSThread currentThread]);
break;
}
}
}
添加线程
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
self.tickets = 20;
NSThread *t1 = [[NSThread alloc] initWithTarget:self selector:@selector(saleTickets) object:nil];
t1.name = @"售票员 A";
[t1 start];
NSThread *t2 = [[NSThread alloc] initWithTarget:self selector:@selector(saleTickets) object:nil];
t2.name = @"售票员 B";
[t2 start];
}
添加休眠
- (void)saleTickets {
while (YES) {
// 模拟休眠
[NSThread sleepForTimeInterval:1.0];
if (self.tickets > 0) {
self.tickets--;
NSLog(@"剩余票数 %d %@", self.tickets, [NSThread currentThread]);
} else {
NSLog(@"没票了 %@", [NSThread currentThread]);
break;
}
}
}
运行测试结果
互斥锁
添加互斥锁
- (void)saleTickets {
while (YES) {
[NSThread sleepForTimeInterval:1.0];
@synchronized(self) {
if (self.tickets > 0) {
self.tickets--;
NSLog(@"剩余票数 %d %@", self.tickets, [NSThread currentThread]);
continue;
}
}
NSLog(@"没票了 %@", [NSThread currentThread]);
break;
}
}
互斥锁小结
- 保证锁内的代码,同一时间,只有一条线程能够执行!
- 互斥锁的锁定范围,应该尽量小,锁定范围越大,效率越差!
- 速记技巧
[[NSUserDefaults standardUserDefaults] synchronize];
互斥锁参数
- 能够加锁的任意
NSObject
对象 - 注意:锁对象一定要保证所有的线程都能够访问
- 如果代码中只有一个地方需要加锁,大多都使用
self
,这样可以避免单独再创建一个锁对象
原子属性
- 原子属性(线程安全),是针对多线程设计的,是默认属性
- 多个线程在写入原子属性时(调用
setter
方法),能够保证同一时间只有一个线程执行写入操作 - 原子属性是一种
单(线程)写多(线程)读
的多线程技术 原子属性的效率比互斥锁高
,不过可能会出现脏数据
- 在定义属性时,必须显示地指定
nonatomic
代码演练
- 定义属性
@property (nonatomic, strong) NSObject *obj1;
@property (atomic, strong) NSObject *obj2;
@property (nonatomic, strong) NSObject *obj3;
- 模拟原子属性
@synthesize obj3 = _obj3;
- (void)setObj3:(NSObject *)obj3 {
@synchronized(self) {
_obj3 = obj3;
}
}
- (NSObject *)obj3 {
return _obj3;
}
* 性能测试
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
int largeNumber = 1000 * 10000;
NSLog(@"非原子属性");
CFAbsoluteTime start = CFAbsoluteTimeGetCurrent();
for (int i = 0; i < largeNumber; i++) {
self.obj1 = [[NSObject alloc] init];
}
NSLog(@"%f", CFAbsoluteTimeGetCurrent() - start);
NSLog(@"原子属性");
start = CFAbsoluteTimeGetCurrent();
for (int i = 0; i < largeNumber; i++) {
self.obj2 = [[NSObject alloc] init];
}
NSLog(@"%f", CFAbsoluteTimeGetCurrent() - start);
NSLog(@"模拟原子属性");
start = CFAbsoluteTimeGetCurrent();
for (int i = 0; i < largeNumber; i++) {
self.obj3 = [[NSObject alloc] init];
}
NSLog(@"%f", CFAbsoluteTimeGetCurrent() - start);
}
原子属性内部的锁是
自旋锁
,自旋锁的执行效率比互斥锁高
自旋锁 & 互斥锁
共同点
- 都能够保证同一时间,只有一条线程执行锁定范围的代码
不同点
互斥锁
:如果发现有其他线程正在执行锁定的代码,线程会进入休眠状态
,等待其他线程执行完毕,打开锁之后,线程会被唤醒
自旋锁
:如果发现有其他线程正在执行锁定的代码,线程会以死循环
的方式,一直等待锁定代码执行完成
结论
- 自旋锁更适合执行非常短的代码
- 无论什么锁,都是要付出代价
线程安全
- 多个线程进行读写操作时,仍然能够得到正确结果,被称为线程安全
- 要实现线程安全,必须要用到
锁
- 为了得到更佳的用户体验,
UIKit 不是线程安全的
约定:所有更新 UI 的操作都必须主线程上执行!
- 因此,
主线程
又被称为UI 线程
iOS 开发建议
- 所有属性都声明为
nonatomic
- 尽量避免多线程抢夺同一块资源
- 尽量将加锁、资源抢夺的业务逻辑交给服务器端处理,减小移动客户端的压力
线程间通讯
主线程实现
定义属性
/// 根视图是滚动视图
@property (nonatomic, strong) UIScrollView *scrollView;
/// 图像视图
@property (nonatomic, weak) UIImageView *imageView;
/// 网络下载的图像
@property (nonatomic, weak) UIImage *image;
loadView
- 加载视图层次结构
- 用纯代码开发应用程序时使用
- 功能和
Storyboard
&XIB
是等价的
如果重写了
loadView
,Storyboard
&XIB
都无效
- (void)loadView {
_scrollView = [[UIScrollView alloc] init];
_scrollView.backgroundColor = [UIColor orangeColor];
self.view = _scrollView;
UIImageView *iv = [[UIImageView alloc] init];
[self.view addSubview:iv];
_imageView = iv;
}
viewDidLoad
- 视图加载完成后执行
- 可以做一些数据初始化的工作
- 如果用纯代码开发,不要在此方法中设置界面 UI
- (void)viewDidLoad {
[super viewDidLoad];
// 下载图像
[self downloadImage];
}
下载网络图片
- (void)downloadImage {
// 1. 网络图片资源路径
NSURL *url = [NSURL URLWithString:@"http://c.hiphotos.baidu.com/image/pic/item/4afbfbedab64034f42b14da1aec379310a551d1c.jpg"];
// 2. 从网络资源路径实例化二进制数据(网络访问)
NSData *data = [NSData dataWithContentsOfURL:url];
// 3. 将二进制数据转换成图像
UIImage *image = [UIImage imageWithData:data];
// 4. 设置图像
self.image = image;
}
设置图片
- (void)setImage:(UIImage *)image {
// 1. 设置图像视图的图像
self.imageView.image = image;
// 2. 按照图像大小设置图像视图的大小
[self.imageView sizeToFit];
// 3. 设置滚动视图的 contentSize
self.scrollView.contentSize = image.size;
}
设置滚动视图的缩放
1> 设置滚动视图缩放属性
// 1> 最小缩放比例
self.scrollView.minimumZoomScale = 0.5;
// 2> 最大缩放比例
self.scrollView.maximumZoomScale = 2.0;
// 3> 设置代理
self.scrollView.delegate = self;
2> 实现代理方法 - 告诉滚动视图缩放哪一个视图
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView {
return self.imageView;
}
3> 跟踪 scrollView
缩放效果
- (void)scrollViewDidZoom:(UIScrollView *)scrollView {
NSLog(@"%@", NSStringFromCGAffineTransform(self.imageView.transform));
}
线程间通讯
- 在后台线程下载图像
[self performSelectorInBackground:@selector(downloadImage) withObject:nil];
- 在主线程设置图像
[self performSelectorOnMainThread:@selector(setImage:) withObject:image waitUntilDone:NO];
NSThread线程对象的更多相关文章
- iOS开发Swift篇(02) NSThread线程相关简单说明
iOS开发Swift篇(02) NSThread线程相关简单说明 一 说明 1)关于多线程部分的理论知识和OC实现,在之前的博文中已经写明,所以这里不再说明. 2)该文仅仅简单讲解NSThread在s ...
- swift开发多线程篇 - NSThread 线程相关简单说明(一些使用和注意点)
一 说明 本文涉及代码可以从https://github.com/HanGangAndHanMeimei/Code地址获得. 二 NSThread的基本使用和创建 1)基本用法(主线程|当前线程) 1 ...
- java-并发-线程对象
浏览以下内容前,请点击并阅读 声明 每个线程都和类Thread的实例相关,有两种基本的使用Thread对象来创建并发应用的方法: 直接控制线程的创建和管理,每次需要开始一个异步任务使简单地实例化Thr ...
- java 22 - 5 多线程之获取和设置线程对象的名称
如何获取线程对象的名称呢? public final String getName():获取线程的名称.如何设置线程对象的名称呢? public final void setName(String n ...
- 【翻译三】java-并发之线程对象和实现
Thread Objects Each thread is associated with an instance of the class Thread. There are two basic s ...
- java多线程之:线程对象一些api
一:wait()方法,wait(long timeout)--->锁对象调用wait()方法,让当前线程小a进入等待状态,阻塞住,并让出当先线程拥有的锁.--->直到其他线程用锁对象调用n ...
- JAVA之旅(十二)——Thread,run和start的特点,线程运行状态,获取线程对象和名称,多线程实例演示,使用Runnable接口
JAVA之旅(十二)--Thread,run和start的特点,线程运行状态,获取线程对象和名称,多线程实例演示,使用Runnable接口 开始挑战一些难度了,线程和I/O方面的操作了,继续坚持 一. ...
- lua 源码分析之线程对象lua_State
lua_State 中放的是 lua 虚拟机中的环境表.注册表.运行堆栈.虚拟机的上下文等数据. 从一个主线程(特指 lua 虚拟机中的线程,即 coroutine)中创建出来的新的 lua_Stat ...
- [Xcode 实际操作]八、网络与多线程-(21)延时启动画面:使用Thread线程对象的延时方法
目录:[Swift]Xcode实际操作 本文将演示如何使用线程对象的延时方法,让线程休眠一段时间,暂停动作的执行. 在项目导航区,打开启动画面的故事板[LaunchScreen.storyboard] ...
随机推荐
- 「MoreThanJava」Day 7:接口详解
「MoreThanJava」 宣扬的是 「学习,不止 CODE」,本系列 Java 基础教程是自己在结合各方面的知识之后,对 Java 基础的一个总回顾,旨在 「帮助新朋友快速高质量的学习」. 当然 ...
- 41.4 Method Security方法安全性
41.4.1 <global-method-security> 这个元素是为Spring Security beans上的安全方法添加支持的主要手段.可以通过使用注释(在接口或类级别定义) ...
- 非确定性有穷状态决策自动机练习题Vol.3 D. Dp搬运工3
非确定性有穷状态决策自动机练习题Vol.3 D. Dp搬运工3 题目描述 给定两个长度为 \(n\) 的排列,定义 \(magic(A,B)=∑_{i=1}^nmax(Ai,Bi)\) . 现在给定 ...
- Linux C++实现一服务器与多客户端之间的通信
通过网络查找资料得到的都是一些零碎不成体系的知识点,无法融会贯通.而且需要筛选有用的信息,这需要花费大量的时间.所以把写代码过程中用到的相关知识的博客链接附在用到的位置,方便回顾. 1.程序流程 服务 ...
- 软工团队项目之团队展示&选题(OnTime——S.L.N)
软工团队项目之团队展示&选题(OnTime——S.L.N) 一.团队展示 队名:『S.L.N』即Seigelion——乃“攻城狮”之意. 队员学号: 团队项目描述:(项目名称:OnTime) ...
- 结对项目:四则运算题目生成器(C)
一.Github项目地址:https://github.com/Spartaright/myapp(合作人:梁天龙.赖佑铭) 二.PSP表格(如下图) 1.项目地址 PSP表格 PSP2.1 Pers ...
- NumPy笔记-ndarray
ndarray,N维数组对象(矩阵) 所有元素必须是相同类型 ndim属性,维度个数 shape属性,各维度大小 dtype属性,数据类型 创建ndarray np.array(collection) ...
- HTTP基本原理-Network各列标签的含义
第一列 Name:请求的名称,一般会将URL的最后一部分内容当作名称 第二列 Status:响应的状态码,显示为 200,则代表相应是正常的.通过状态码,我们可以判断发送了请求之后是否得到了正常的相应 ...
- 吊打 Tomcat ,Undertow 性能很炸!!
在 Java Web 容器的世界里,Tomcat 和 Jetty 是大名鼎鼎的.用的最多的开源项目,也是大众熟知的. 今天再介绍另外一款能和 Tomcat 媲美的神器:Undertow,据说性能方面还 ...
- java爬取图片示例
爬虫是什么 这里引用一下 wiki 中关于 网络爬虫的定义,相信大家看过后会有一个清晰的认识 网络爬虫(英语:web crawler),也叫网络蜘蛛(spider),是一种用来自动浏览万维网的网络机器 ...