多线程之线程安全

前提

面试的时候,多线程的问题经常被问到,刚开始会问你iOS中实现多线程有几种方式;
答曰:NSthread、GCD、NSOperation+NSoperationQueue
进而会引出更深一个层次的问题?如何保证线程安全呢?

现象

相信做过服务器开发的同学经常会突然想到一个词:锁;
但是在iOS开发过程中,也许不怎么会关注这一块,但是相信大家都是用过nonatomic(非原子性)、atomic(原子性),并且更多的情况下用的都是nonatomic。

首先理解下为什么在移动开发的过程中一般都是使用nonatomic?这里就不多解释了

为什么要考虑多线程安全呢?通过下面常见的两个现象来看下线程安全可能带来的问题

现象一、取钱和存钱

老妈:小明,还有钱吗?
小明:我的卡里还有10000呢
老妈:那么少,再给你转点吧
小明:老妈爱你,再给转20000吧
老妈:现在就给你转,你待会去查查收到了吗

小明正打算取8000和朋友一起出去嗨,于是去附近的取款机取钱

想想老妈给小明转钱的同时,小明从取款机里取了8000,会出现什么问题?


多线程之线程安全_第1张图片
1.pic.jpg

会出现两个结果:
1、老妈转账成功后,我却没有收到,余额:2000;
2、小明取了8000后,余额:30000;
先思考下什么情况下会出现这种现象;

现象二、卖火车票

多个售票员卖同一批火车票,简单的看下图吧(画的比较丑,凑合看吧)


多线程之线程安全_第2张图片
2.pic.jpg

代码模拟

一、

@interface YTViewController ()

@property (nonatomic, assign) int leftMoney;

@property (nonatomic, strong) NSThread *thread1;
@property (nonatomic, strong) NSThread *thread2;

@end

@implementation YTViewController

- (instancetype)init
{
    self = [super init];
    if (self) {
        self.leftMoney = 10000;
    }
    return self;
}

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    
    UIButton *but = [UIButton buttonWithType:UIButtonTypeCustom];
    [self.view addSubview:but];
    [but setFrame:self.view.bounds];
    [but setBackgroundColor:[UIColor blueColor]];
    [but setTitle:@"开始存取钱" forState:UIControlStateNormal];
    [but addTarget:self action:@selector(btnStart:) forControlEvents:UIControlEventTouchUpInside];
    
    //开启多个线程 模拟存取钱
    self.thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(sellthread) object:nil];
    self.thread1.name = @"老妈";
    
    self.thread2 = [[NSThread alloc] initWithTarget:self selector:@selector(sellthread) object:nil];
    self.thread2.name = @"小明";
}

- (void)btnStart:(UIButton *)btn
{
    [self.thread1 start];
    [self.thread2 start];
}

- (void)sellthread
{
    //1、先检查余额
    int money = self.leftMoney;
    
    if (money > 0) {
        //暂停一段时间 模拟业务处理耗时
        [NSThread sleepForTimeInterval:0.02];
        
        //获取线程信息
        NSThread *thread = [NSThread currentThread];
        if ([thread.name isEqualToString:@"小明"]) {
            self.leftMoney = money - 8000;
            
            NSLog(@"%@--取了8000,还剩余额%d", thread.name, self.leftMoney);
        } else {
            self.leftMoney = money + 20000;
            NSLog(@"%@--存了20000,还剩余额%d", thread.name, self.leftMoney);
        }
    } else {
        //退出线程
        [NSThread exit];
    }
}

打印结果:

3.pic.jpg

二、

/*!
 *  @author yangL, 16-07-28 11:07:21
 *
 *  @brief 线程安全
 */
#import "YTViewController.h"

@interface YTViewController ()

@property (nonatomic, assign) int leftTickets;

@property (nonatomic, strong) NSThread *thread1;
@property (nonatomic, strong) NSThread *thread2;
@property (nonatomic, strong) NSThread *thread3;

@end

@implementation YTViewController

- (instancetype)init
{
    self = [super init];
    if (self) {
        self.leftTickets = 10;
    }
    return self;
}

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    
    UIButton *but = [UIButton buttonWithType:UIButtonTypeCustom];
    [self.view addSubview:but];
    [but setFrame:self.view.bounds];
    [but setBackgroundColor:[UIColor blueColor]];
    [but setTitle:@"开始售票" forState:UIControlStateNormal];
    [but addTarget:self action:@selector(btnStart:) forControlEvents:UIControlEventTouchUpInside];
    
    //开启多个线程 模拟售票员售票
    self.thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(sellthread) object:nil];
    self.thread1.name = @"售票员A";
    
    self.thread2 = [[NSThread alloc] initWithTarget:self selector:@selector(sellthread) object:nil];
    self.thread2.name = @"售票员B";
    
    self.thread3 = [[NSThread alloc] initWithTarget:self selector:@selector(sellthread) object:nil];
    self.thread3.name = @"售票员C";
    
}

- (void)btnStart:(UIButton *)btn
{
    [self.thread1 start];
    [self.thread2 start];
    [self.thread3 start];
}

- (void)sellthread
{
    while (1) {
        //1、先检查票数
        int count = self.leftTickets;
        
        if (count > 0) {
            //暂停一段时间 模拟业务处理耗时
            [NSThread sleepForTimeInterval:0.02];
            
            //票数减1
            self.leftTickets = count - 1;
            
            //获取线程信息
            NSThread *thread = [NSThread currentThread];
            NSLog(@"%@--卖出一张票,还剩余%d张票", thread.name, self.leftTickets);
        } else {
            //退出线程
            [NSThread exit];
        }
    }
}

打印结果:


多线程之线程安全_第3张图片
4.pic_hd.jpg

如何解决

当然银行和铁道部不会让上面的情况发生;
我们法相上述两种情况都有一个共同的特点:多个对象共享一块公共的资源(现象一:余额 ;现象二:车票)
存钱和取钱相当于两个线程,如果存钱的时候把余额锁定,存钱的线程完成后才允许取钱的线程操作,就不会出现上述问题了;

可以使用互斥锁实现:@synchronized(锁对象) { // 需要锁定的代码}

关键代码和运行结果截图:


多线程之线程安全_第4张图片
6.pic.jpg
多线程之线程安全_第5张图片
5.pic_hd.jpg

不懂就药问

你可能感兴趣的:(多线程之线程安全)