NSThread
每個iOS應用程序都有個專門用來更新顯示UI界面腥放、處理用戶觸摸事件的主線程孙咪,因此不能將其他太耗時的操作放在主線程中執(zhí)行欠拾,不然會造成主線程堵塞
創(chuàng)建一條線程
方式一 動態(tài)方法
// 1.創(chuàng)建子線程
/*
Target: 子線程需要調(diào)用誰的方法
selector: 被子線程調(diào)用的方法
object: 調(diào)用方法時, 給方法傳遞的參數(shù)
*/
// 注意: 如果線程正在執(zhí)行, 那么系統(tǒng)會自動強引用NSThread 當線程中的任務執(zhí)行完畢, 系統(tǒng)會自動釋放線程, 對線程進行一次release操作
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(demo:) object:@"lnj"];
// 給線程取名字
thread.name = @"子線程1";
// 設置線程的優(yōu)先級, 取值范圍0.0~1.0, 1.0是最高, 默認就是0.5
// 注意點: 線程的優(yōu)先級越高, 那么被CPU調(diào)度的可能性就越大 但是并不代表著, 優(yōu)先級高的一定會先執(zhí)行
thread.threadPriority = 0.0;
// 2.啟動線程 如果通過alloc/init創(chuàng)建NSThread, 那么需要手動啟動線程
[thread start];
方式二 靜態(tài)方法
/*
優(yōu)點:
- 使用簡便
- 如果通過detach方法創(chuàng)建線程, 不需要手動調(diào)用start啟動線程 \ 系統(tǒng)會自動啟動
缺點:
- 不可以進行其它設置
應用場景:
- 如果僅僅需要簡單的開啟一個子線程執(zhí)行一些操作, 不需要對子線程進行其它設置, 那么推薦使用detach方法創(chuàng)建線程
*/
[NSThread detachNewThreadSelector:@selector(demo:) toTarget:self withObject:@"canshu"];
方式三 隱式創(chuàng)建線程
// 系統(tǒng)會自動在后臺給我們開啟一個子線程, 并且會自動啟動該線程執(zhí)行任務
[self performSelectorInBackground:@selector(demo:) withObject:@"oooo"];
線程通信
場景:從網(wǎng)絡下載一張圖片酥夭,然后返回主線程更新UI
開啟一個子線程下載圖片
[self performSelectorInBackground:@selector(downlod) withObject:nil];
實現(xiàn)下載圖片方法
- (void)downlod
{
NSLog(@"%@", [NSThread currentThread]); // 打印當前線程
// 1.下載圖片
NSURL *url = [NSURL URLWithString:@"http://pic.4j4j.cn/upload/pic/20130531/07ed5ea485.jpg"];
NSData *data = [NSData dataWithContentsOfURL:url];
// 2.將二進制轉(zhuǎn)換為圖片
UIImage *image = [UIImage imageWithData:data];
}
拿到圖片后 返回主線程更新UI 方式一
/*
waitUntilDone:
YES: 如果傳入YES, 那么會等待updateImage方法執(zhí)行完畢, 才會繼續(xù)執(zhí)行后面的代碼
NO: 如果傳入NO, 那么不會等待updateImage方法執(zhí)行完畢, 就可以繼續(xù)之后后面的代碼
*/
// 可以在指定的線程中, 調(diào)用指定對象的指定方法
[self performSelector:@selector(updateImage:) onThread:[NSThread mainThread] withObject:image waitUntilDone:YES];
拿到圖片后 返回主線程更新UI 方式二 工作中常用
// 這個方法也就不需要再重新實現(xiàn)更新UI的方法了
[self.imageView performSelectorOnMainThread:@selector(setImage:) withObject:image waitUntilDone:YES];
實現(xiàn)更新UI的方法
- (void)updateImage:(UIImage *)image
{
NSLog(@"%@", [NSThread currentThread]);
self.imageView.image = image;
}
在當前線程執(zhí)行操作
[self performSelector:@selector(run) withObject:nil];
多條線程在同一時間訪問同一塊資源容易出現(xiàn)安全隱患
模擬場景:12306服務器設置了20張從北京到上呵岷冢火車票檐嚣,有三個售票員用他們的電腦進行售票助泽,這里要需要用到互斥鎖 它的作用就是鎖住這塊資源讓它在同一時間只能讓一條線程訪問 接下來先測試加鎖與不加鎖的區(qū)別
創(chuàng)建3條線程代表3個售貨員
- (void)viewDidLoad {
[super viewDidLoad];
// 0 初始化票數(shù)
self.totalCount = 100;
// 1 創(chuàng)建3個售票員
NSThread *thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(saleTicket) object:nil];
thread1.name = @"售票員1";
self.thread1 = thread1;
NSThread *thread2 = [[NSThread alloc] initWithTarget:self selector:@selector(saleTicket) object:nil];
thread2.name = @"售票員2";
self.thread2 = thread2;
NSThread *thread3 = [[NSThread alloc] initWithTarget:self selector:@selector(saleTicket) object:nil];
thread3.name = @"售票員3";
self.thread3 = thread3;
}
開始售票
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
// 2.讓3個售票員同事售票
[self.thread1 start];
[self.thread2 start];
[self.thread3 start];
}
售票進行中 不加鎖
- (void)saleTicket
{
while (1) {
NSLog(@"歡迎光臨");
// 1.查詢剩余的票數(shù)
NSUInteger count = self.totalCount;
// 2.判斷是否還有余票
if (count > 0) {
// 線程1 讓線程處于休眠狀態(tài) 模擬售票時間為0.1秒
[NSThread sleepForTimeInterval:0.1];
// 2.1賣票
self.totalCount = count - 1; // 99
NSLog(@"%@賣了一張票, 還剩%zd票", [NSThread currentThread].name, self.totalCount);
}else
{
// 3.提示客戶, 沒有票了
NSLog(@"對不起, 沒有票了");
break;
}
}
}
看打印結(jié)果
明顯線程沒有獲取到最新的值 造成數(shù)據(jù)訪問錯誤
Paste_Image.png
售票進行中 加鎖
// 售票方法
- (void)saleTicket
{
while (1) {
NSLog(@"歡迎光臨");
@synchronized (self) { //@synchronized 為互斥鎖 self可以為任何對象 但必須是同一對象 保證單例
// 1.查詢剩余的票數(shù)
NSUInteger count = self.totalCount;
// 2.判斷是否還有余票
if (count > 0) {
// 線程1 讓線程處于休眠狀態(tài) 模擬售票時間為0.1秒
[NSThread sleepForTimeInterval:0.1];
// 2.1賣票
self.totalCount = count - 1; // 99
NSLog(@"%@賣了一張票, 還剩%zd票", [NSThread currentThread].name, self.totalCount);
}else
{
// 3.提示客戶, 沒有票了
NSLog(@"對不起, 沒有票了");
break;
}
}
}
}
這下就沒有問題了
Paste_Image.png