多线程之线程安全

作者: kevinLY | 来源:发表于2016-07-28 15:22 被阅读169次

    前提

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

    现象

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

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

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

    现象一、取钱和存钱

    老妈:小明,还有钱吗?
    小明:我的卡里还有10000呢
    老妈:那么少,再给你转点吧
    小明:老妈爱你,再给转20000吧
    老妈:现在就给你转,你待会去查查收到了吗
    
    小明正打算取8000和朋友一起出去嗨,于是去附近的取款机取钱
    

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


    1.pic.jpg

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

    现象二、卖火车票

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


    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];
            }
        }
    }
    

    打印结果:


    4.pic_hd.jpg

    如何解决

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

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

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


    6.pic.jpg 5.pic_hd.jpg

    不懂就药问

    相关文章

      网友评论

      • 小怡情ifelse:楼主可以贴一个demo的地址哈
        小怡情ifelse:@sunwindyLy只是如果自己想运行 还要复制黏贴麻烦一点而已 有github的话直接下载运行
        kevinLY:@厦大 贴出来的是所有的代码

      本文标题:多线程之线程安全

      本文链接:https://www.haomeiwen.com/subject/ueeijttx.html