• 欢迎访问web前端中文站,JavaScript,CSS3,HTML5,web前端demo
  • 如果您觉得本站非常有看点,那么赶紧使用Ctrl+D 收藏web前端中文站吧

【面试】如果你这样回答“什么是线程安全”,面试官都会对你刮目相看

JavaScript web前端中文站 1年前 (2019-05-09) 4320次浏览 已收录 26个评论
今天给大家介绍下和面试相关的内容,例如:什么是线程安全,在面试中应该如何回答才能让面试官对你刮目相看。

更多精彩内容请看 web 前端中文站
http://www.lisa33xiaoq.net 可按 Ctrl + D 进行收藏

不是线程的安全

面试官问:“什么是线程安全”,如果你不能很好的回答,那就请往下看吧。

论语中有句话叫“学而优则仕”,相信很多人都觉得是“学习好了可以做官”。然而,这样理解却是错的。切记望文生义。

同理,“线程安全”也不是指线程的安全,而是指内存的安全。为什么如此说呢?这和操作系统有关。

目前主流操作系统都是多任务的,即多个进程同时运行。为了保证安全,每个进程只能访问分配给自己的内存空间,而不能访问别的进程的,这是由操作系统保障的。

在每个进程的内存空间中都会有一块特殊的公共区域,通常称为堆(内存)。进程内的所有线程都可以访问到该区域,这就是造成问题的潜在原因。

假设某个线程把数据处理到一半,觉得很累,就去休息了一会,回来准备接着处理,却发现数据已经被修改了,不是自己离开时的样子了。可能被其它线程修改了。

比如把你住的小区看作一个进程,小区里的道路/绿化等就属于公共区域。你拿 1 万块钱往地上一扔,就回家睡觉去了。睡醒后你打算去把它捡回来,发现钱已经不见了。可能被别人拿走了。

因为公共区域人来人往,你放的东西在没有看管措施时,一定是不安全的。内存中的情况亦然如此。

所以线程安全指的是,在堆内存中的数据由于可以被任何线程访问到,在没有限制的情况下存在被意外修改的风险。

即堆内存空间在没有保护机制的情况下,对多线程来说是不安全的地方,因为你放进去的数据,可能被别的线程“破坏”。

那我们该怎么办呢?解决问题的过程其实就是一个取舍的过程,不同的解决方案有不同的侧重点。


私有的东西就不该让别人知道


现实中很多人都会把 1 万块钱藏着掖着,不让无关的人知道,所以根本不可能扔到大马路上。因为这钱是你的私有物品。

在程序中也是这样的,所以操作系统会为每个线程分配属于它自己的内存空间,通常称为栈内存,其它线程无权访问。这也是由操作系统保障的。

如果一些数据只有某个线程会使用,其它线程不能操作也不需要操作,这些数据就可以放入线程的栈内存中。较为常见的就是局部变量。

double avgScore(double[] scores) {    double sum = 0;    for (double score : scores) {        sum += score;    }    int count = scores.length;    double avg = sum / count;    return avg;}


这里的变量 sum,count,avg 都是局部变量,它们都会被分配在线程栈内存中。

假如现在 A 线程来执行这个方法,这些变量会在 A 的栈内存分配。与此同时,B 线程也来执行这个方法,这些变量也会在 B 的栈内存中分配。

也就是说这些局部变量会在每个线程的栈内存中都分配一份。由于线程的栈内存只能自己访问,所以栈内存中的变量只属于自己,其它线程根本就不知道。

就像每个人的家只属于自己,其他人不能进来。所以你把 1 万块钱放到家里,其他人是不会知道的。且一般还会放到某个房间里,而不是仍在客厅的桌子上。

所以把自己的东西放到自己的私人地盘,是安全的,因为其他人无法知道。而且越隐私的地方越好。


大家不要抢,人人有份


相信聪明的你已经发现,上面的解决方案是基于“位置”的。因为你放东西的“位置”只有你自己知道(或能到达),所以东西是安全的,因此这份安全是由“位置”来保障的。

在程序里就对应于方法的局部变量。局部变量之所以是安全的,就是因为定义它的“位置”是在方法里。这样一来安全是达到了,但是它的使用范围也就被限制在这个方法里了,其它方法想用也不用了啦。

现实中往往会有一个变量需要多个方法都能够使用的情况,此时定义这个变量的“位置”就不能在方法里面了,而应该在方法外面。即从(方法的)局部变量变为(类的)成员变量,其实就是“位置”发生了变化。

那么按照主流编程语言的规定,类的成员变量不能再分配在线程的栈内存中,而应该分配在公共的堆内存中。其实也就是变量在内存中的“位置”发生了变化,由一个私有区域来到了公共区域。因此潜在的安全风险也随之而来。

那怎么保证在公共区域的东西安全呢?答案就是,大家不要抢,人人有份。设想你在街头免费发放矿泉水,来了 1 万人,你却只有 1 千瓶水,结果可想而知,一拥而上,场面失守。但如果你有 10 万瓶水,大家一看,水多着呢,不用着急,一个个排着队来,因为肯定会领到。

东西多了,自然就不值钱了,从另一个角度来说,也就安全了。大街上的共享单车,现在都很安全,因为太多了,到处都是,都长得一样,所以连搞破坏的人都放弃了。因此要让一个东西安全,就疯狂的 copy 它吧。

回到程序里,要让公共区域堆内存中的数据对于每个线程都是安全的,那就每个线程都拷贝它一份,每个线程只处理自己的这一份拷贝而不去影响别的线程的,这不就安全了嘛。相信你已经猜到了,我要表达的就是 ThreadLocal 类了。

class StudentAssistant {
ThreadLocal<String> realName = new ThreadLocal<>(); ThreadLocal<Double> totalScore = new ThreadLocal<>();
String determineDegree() { double score = totalScore.get(); if (score >= 90) { return "A"; } if (score >= 80) { return "B"; } if (score >= 70) { return "C"; } if (score >= 60) { return "D"; } return "E"; }
double determineOptionalcourseScore() { double score = totalScore.get(); if (score >= 90) { return 10; } if (score >= 80) { return 20; } if (score >= 70) { return 30; } if (score >= 60) { return 40; } return 60; }}


这个学生助手类有两个成员变量,realName 和 totalScore,都是 ThreadLocal 类型的。每个线程在运行时都会拷贝一份存储到自己的本地。

A 线程运行的是“张三”和“90”,那么这两个数据“张三”和“90”是存储到 A 线程对象(Thread 类的实例对象)的成员变量里去了。假设此时 B 线程也在运行,是“李四”和“85”,那么“李四”和“85”这两个数据是存储到了 B 线程对象(Thread 类的实例对象)的成员变量里去了。

线程类(Thread)有一个成员变量,类似于 Map 类型的,专门用于存储 ThreadLocal 类型的数据。从逻辑从属关系来讲,这些 ThreadLocal 数据是属于 Thread 类的成员变量级别的。从所在“位置”的角度来讲,这些 ThreadLocal 数据是分配在公共区域的堆内存中的。

说的直白一些,就是把堆内存中的一个数据复制 N 份,每个线程认领 1 份,同时规定好,每个线程只能玩自己的那份,不准影响别人的。

需要说明的是这 N 份数据都还是存储在公共区域堆内存里的,经常听到的“线程本地”,是从逻辑从属关系上来讲的,这些数据和线程一一对应,仿佛成了线程自己“领地”的东西了。其实从数据所在“位置”的角度来讲,它们都位于公共的堆内存中,只不过被线程认领了而已。这一点我要特地强调一下。

其实就像大街上的共享单车。原来只有 1 辆,大家抢着骑,老出问题。现在从这 1 辆复制出 N 辆,每人 1 辆,各骑各的,问题得解。共享单车就是数据,你就是线程。骑行期间,这辆单车从逻辑上来讲是属于你的,从所在位置上来讲还是在大街上这个公共区域的,因为你发现每个小区大门口都贴着“共享单车,禁止入门”。哈哈哈哈。

共享单车是不是和 ThreadLocal 很像呀。再重申一遍,ThreadLocal 就是,把一个数据复制 N 份,每个线程认领一份,各玩各的,互不影响。


只能看,不能摸


放在公共区域的东西,只是存在潜在的安全风险,并不是说一定就不安全。有些东西虽然也在公共区域放着,但也是十分安全的。比如你在大街上放一个上百吨的石头雕像,就非常安全,因为大家都弄不动它。

再比如你去旅游时,经常发现一些珍贵的东西,会被用铁栅栏围起来,上面挂一个牌子,写着“只能看,不能摸”。当然可以国际化一点,“only look,don’t touch”。这也是很安全的,因为光看几眼是不可能看坏的。

回到程序里,这种情况就属于,只能读取,不能修改。其实就是常量或只读变量,它们对于多线程是安全的,想改也改不了。

class StudentAssistant {
final double passScore = 60;}


比如把及格分数设定为 60 分,在前面加上一个 final,这样所有线程都动不了它了。这就很安全了。


小节一下以上三种解决方案,其实都是在“耍花招”。

第一种,找个只有自己知道的地方藏起来,当然安全了。

第二种,每人复制 1 份,各玩各的,互不影响,当然也安全了。

第三种,更狠了,直接规定,只能读取,禁止修改,当然也安全了。

是不是都在“避重就轻”呀。如果这三种方法都解决不了,该怎么办呢?Don’t worry,just continue reading。


没有规则,那就先入为主


前面给出的三种方案,有点“理想化”了。现实中的情况其实是非常混乱嘈杂的,没有规则的。

比如在中午高峰期你去饭店吃饭,进门后发现只剩一个空桌子了,你心想先去点餐吧,回来就坐这里吧。当你点完餐回来后,发现已经被别人捷足先登了。

因为桌子是属于公共区域的物品,任何人都可以坐,那就只能谁先抢到谁坐。虽然你在人群中曾多看了它一眼,但它并不会记住你容颜。

解决方法就不用我说了吧,让一个人在那儿看着座位,其它人去点餐。这样当别人再来的时候,你就可以理直气壮的说,“不好意思,这个座位,我,已经占了”。

我再次相信聪明的你已经猜到了我要说的东西了,没错,就是(互斥)锁。

回到程序里,如果公共区域(堆内存)的数据,要被多个线程操作时,为了确保数据的安全(或一致)性,需要在数据旁边放一把锁,要想操作数据,先获取锁再说吧。

假设一个线程来到数据跟前一看,发现锁是空闲的,没有人持有。于是它就拿到了这把锁,然后开始操作数据,干了一会活,累了,就去休息了。

这时,又来了一个线程,发现锁被别人持有着,按照规定,它不能操作数据,因为它无法得到这把锁。当然,它可以选择等待,或放弃,转而去干别的。

第一个线程之所以敢大胆的去睡觉,就是因为它手里拿着锁呢,其它线程是不可能操作数据的。当它回来后继续把数据操作完,就可以把锁给释放了。锁再次回到空闲状态,其它线程就可以来抢这把锁了。还是谁先抢到锁谁操作数据。

class ClassAssistant {
double totalScore = 60; final Lock lock = new Lock();
void addScore(double score) { lock.obtain(); totalScore += score; lock.release(); }
void subScore(double score) { lock.obtain(); totalScore -= score; lock.release(); }}


假定一个班级的初始分数是 60 分,这个班级抽出 10 名学生来同时参加 10 个不同的答题节目,每个学生答对一次为班级加上 5 分,答错一次减去 5 分。因为 10 个学生一起进行,所以这一定是一个并发情形。

因此加分和减分这两个方法被并发的调用,它们共同操作总分数。为了保证数据的一致性,需要在每次操作前先获取锁,操作完成后再释放锁。


相信世界充满爱,即使被伤害


再回到一开始的例子,假如你往地上仍 1 万块钱,是不是一定会丢呢?这要看情况了,如果是在人来人往的都市,可以说肯定会丢的。如果你跑到无人区扔地上,可以说肯定不会丢。

可以看到,都是把东西无保护的放到公共区域里,结果却相差很大。这说明安全问题还和公共区域的环境状况有关系。

比如我把数据放到公共区域的堆内存中,但是始终都只会有 1 个线程,也就是单线程模型,那这数据肯定是安全的。

再者说,2 个线程操作同一个数据和 200 个线程操作同一个数据,这个数据的安全概率是完全不一样的。肯定线程越多数据不安全的概率越大,线程越少数据不安全的概率越小。取个极限情况,那就是只有 1 个线程,那不安全概率就是 0,也就是安全的。

可能你又猜到了我想表达的内容了,没错,就是 CAS。可能大家觉得既然锁可以解决问题,那就用锁得了,为啥又冒出了个 CAS 呢?

那是因为锁的获取和释放是要花费一定代价的,如果在线程数目特别少的时候,可能根本就不会有别的线程来操作数据,此时你还要获取锁和释放锁,可以说是一种浪费。

针对这种“地广人稀”的情况,专门提出了一种方法,叫 CAS(Compare And Swap)。就是在并发很小的情况下,数据被意外修改的概率很低,但是又存在这种可能性,此时就用 CAS。

假如一个线程操作数据,干了一半活,累了,想要去休息。(貌似今天的线程体质都不太好)。于是它记录下当前数据的状态(就是数据的值),回家睡觉了。

醒来后打算继续接着干活,但是又担心数据可能被修改了,于是就把睡觉前保存的数据状态拿出来和现在的数据状态比较一下,如果一样,说明自己在睡觉期间,数据没有被人动过(当然也有可能是先被改成了其它,然后又改回来了,这就是 ABA 问题了),那就接着继续干。如果不一样,说明数据已经被修改了,那之前做的那些操作其实都白瞎了,就干脆放弃,从头再重新开始处理一遍。

所以 CAS 这种方式适用于并发量不高的情况,也就是数据被意外修改的可能性较小的情况。如果并发量很高的话,你的数据一定会被修改,每次都要放弃,然后从头再来,这样反而花费的代价更大了,还不如直接加锁呢。

这里再解释下 ABA 问题,假如你睡觉前数据是 5,醒来后数据还是 5,并不能肯定数据没有被修改过。可能数据先被修改成 8 然后又改回到 5,只是你不知道罢了。对于这个问题,其实也很好解决,再加一个版本号字段就行了,并规定只要修改数据,必须使版本号加 1。

这样你睡觉前数据是 5 版本号是 0,醒来后数据是 5 版本号是 0,表明数据没有被修改。如果数据是 5 版本号是 2,表明数据被改动了 2 次,先改为其它,然后又改回到 5。

我再次相信聪明的你已经发现了,这里的 CAS 其实就是乐观锁,上一种方案里的获取锁和释放锁其实就是悲观锁。乐观锁持乐观态度,就是假设我的数据不会被意外修改,如果修改了,就放弃,从头再来。悲观锁持悲观态度,就是假设我的数据一定会被意外修改,那干脆直接加锁得了。

作者观点


前两种属于隔离法,一个是位置隔离,一个是数据隔离。

然后两种是标记法,一个是只读标记,一个是加锁标记。

最后一种是大胆法,先来怼一把试试,若不行从头再来。

对于大胆法,还是有必要尝试的。有人曾说过,“梦想还是要有的,万一实现了呢”。

【注:本文源自网络文章资源,由站长整理发布】


web 前端中文站 , 版权所有丨如未注明 , 均为原创丨本网站采用BY-NC-SA协议进行授权
转载请注明原文链接:【面试】如果你这样回答“什么是线程安全”,面试官都会对你刮目相看
喜欢 (71)
发表我的评论
取消评论
表情 贴图 加粗 删除线 居中 斜体 签到

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
(26)个小伙伴在吐槽
  1. WillowUTNew Orleans
    WillowUTNew Orleans2020-10-06 01:16 回复
  2. EverettNCSan Antonio
  3. I precisely desired to thank you so much once more. I am not sure what I would have accomplished in the absence of those concepts discussed by you over such a situation. Previously it was a very daunting crisis for me, nevertheless seeing the specialized avenue you managed it made me to jump for happiness. I am just happy for the work and even expect you recognize what an amazing job that you're accomplishing educating people through the use of your web site. Most probably you have never got to know any of us.
    moncler outlet2020-10-05 02:34 回复
  4. I would like to express my gratitude for your kind-heartedness giving support to those people who should have help with this concern. Your real dedication to getting the solution across appeared to be quite effective and has consistently helped regular people just like me to get to their targets. Your own invaluable advice signifies so much to me and a whole lot more to my fellow workers. Many thanks; from each one of us.
    supreme t shirt2020-09-30 23:50 回复
  5. MilesALCollege Station
  6. BrielleIDNewark
    BrielleIDNewark2020-09-22 17:31 回复
  7. MateoVANorfolk
    MateoVANorfolk2020-09-22 17:30 回复
  8. LilySDCarmel
    LilySDCarmel2020-09-02 19:47 回复
  9. Hiya very cool site!! Guy .. Excellent .. Wonderful .. I will bookmark your web site and take the feeds additionally? I'm happy to seek out a lot of useful info here in the submit, we want work out more techniques in this regard, thank you for sharing. . . . . .
    Arthur2020-08-10 01:05 回复
  10. Your place is valueble for me. Thanks!?
    nike air max 2702020-07-23 05:23 回复
  11. There are actually plenty of details like that to take into consideration. That may be a nice point to deliver up. I provide the ideas above as normal inspiration however clearly there are questions just like the one you deliver up the place the most important thing shall be working in trustworthy good faith. I don?t know if best practices have emerged around things like that, but I am positive that your job is clearly recognized as a fair game. Both boys and girls really feel the impact of only a second抯 pleasure, for the rest of their lives.
    birkin bag2020-07-21 03:49 回复
  12. Your place is valueble for me. Thanks!?
    yeezy shoes2020-07-18 18:10 回复
  13. Would you be concerned about exchanging links?
    longchamp handbags2020-07-16 11:32 回复
  14. Youre so cool! I dont suppose Ive learn anything like this before. So nice to find someone with some original thoughts on this subject. realy thank you for starting this up. this web site is something that's wanted on the net, somebody with slightly originality. useful job for bringing something new to the web!
    birkin bag2020-07-14 11:18 回复
  15. I was very pleased to search out this web-site.I needed to thanks on your time for this excellent learn!! I positively enjoying each little bit of it and I have you bookmarked to take a look at new stuff you weblog post.
    jordan shoes2020-07-12 08:45 回复
  16. This is a topic that is close to my heart... Thank you! Exactly where are your contact details though? P.S. If you have a minute, would love your feedback on my new website re-design. You can find it by searching for ?royal cbd? — no sweat if you can’t. Keep up the good work!
    Royal CBD2020-05-19 14:40 回复
  17. This is a topic that is close to my heart... Thank you! Exactly where are your contact details though? P.S. If you have a minute, would love your feedback on my new website re-design. You can find it by searching for ?royal cbd? — no sweat if you can’t. Keep up the good work!
    Royal CBD2020-05-19 14:40 回复
  18. Every weekend i used to go to see this website, as i want enjoyment, for the reason that this this web site conations actually pleasant funny data too.
    Royal CBD2020-05-15 00:12 回复
  19. Its like you learn my thoughts! You appear to know so much about this, like you wrote the ebook in it or something. I feel that you can do with a few % to power the message house a bit, however instead of that, that is wonderful blog. An excellent read. I'll definitely be back.
    Royal CBD2020-05-07 13:40 回复
  20. Thank you for the good writeup. It in fact was a amusement account it. Look advanced to far added agreeable from you! However, how could we communicate?
    best cbd oil2020-05-04 12:05 回复
  21. I really like what you guys are usually up too. Such clever work and reporting! Keep up the good works guys I've added you guys to blogroll.
    best cbd oil2020-04-24 13:18 回复
  22. Hi it's me, I am also visiting this site on a regular basis, this web page is in fact fastidious and the viewers are in fact sharing nice thoughts.
    CBD cream2020-04-11 22:49 回复
  23. Simply desire to say your article is as surprising. The clearness in your post is simply great and i can assume you are an expert on this subject. Fine with your permission let me to grab your feed to keep up to date with forthcoming post. Thanks a million and please keep up the enjoyable work.
    best cbd2020-03-15 11:11 回复
  24. This design is steller! You most certainly know how to keep a reader amused. Between your wit and your videos, I was almost moved to start my own blog (well, almost...HaHa!) Great job. I really enjoyed what you had to say, and more than that, how you presented it. Too cool!
    best cbd2020-03-15 06:10 回复
  25. 哈哈,面试造火箭,进公司搬砖 :mrgreen:
    VPS234主机测评2019-09-20 20:52 回复
  26. 一笑而过。
    repostone2019-05-12 15:49 回复