日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

【建议珍藏系列】如果你这样回答「什么是线程安全」,面试官都会对你刮目相看!...

發布時間:2024/8/23 编程问答 38 豆豆
生活随笔 收集整理的這篇文章主要介紹了 【建议珍藏系列】如果你这样回答「什么是线程安全」,面试官都会对你刮目相看!... 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

戳藍字“CSDN云計算”關注我們哦!

作者 |?陳樹義

責編 | 阿禿

不是線程的安全

面試官問:“什么是線程安全”,如果你不能很好的回答,那就請往下看吧。

論語中有句話叫“學而優則仕”,相信很多人都覺得是“學習好了可以做官”。然而,這樣理解卻是錯的。切記望文生義。

同理,“線程安全”也不是指線程的安全,而是指內存的安全。為什么如此說呢?這和操作系統有關。

目前主流操作系統都是多任務的,即多個進程同時運行。為了保證安全,每個進程只能訪問分配給自己的內存空間,而不能訪問別的進程的,這是由操作系統保障的。

在每個進程的內存空間中都會有一塊特殊的公共區域,通常稱為堆(內存)。進程內的所有線程都可以訪問到該區域,這就是造成問題的潛在原因。

假設某個線程把數據處理到一半,覺得很累,就去休息了一會,回來準備接著處理,卻發現數據已經被修改了,不是自己離開時的樣子了。可能被其它線程修改了。

比如把你住的小區看作一個進程,小區里的道路/綠化等就屬于公共區域。你拿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其實就是樂觀鎖,上一種方案里的獲取鎖和釋放鎖其實就是悲觀鎖。樂觀鎖持樂觀態度,就是假設我的數據不會被意外修改,如果修改了,就放棄,從頭再來。悲觀鎖持悲觀態度,就是假設我的數據一定會被意外修改,那干脆直接加鎖得了。

作者觀點


前兩種屬于隔離法,一個是位置隔離,一個是數據隔離。

然后兩種是標記法,一個是只讀標記,一個是加鎖標記。

最后一種是大膽法,先來懟一把試試,若不行從頭再來。

對于大膽法,還是有必要嘗試的。有人曾說過,“夢想還是要有的,萬一實現了呢”。

福利

掃描添加小編微信,備注“姓名+公司職位”,入駐【CSDN博客】,加入【云計算學習交流群】,和志同道合的朋友們共同打卡學習!

推薦閱讀:

  • 互聯網公司的裁員,能玩出多少種花樣?

  • 我怎么就被一張照片出賣了?可怕!

  • 賈揚清:為什么說數據智能和云原生之間是“天作之合”?

  • 自學編程、玩 vlog,90 后程序員們的冠軍之路

  • 警惕!程序員萬字揭露被空姐騙到香港做傳銷的來龍去脈!

  • 支持OpenStack,紅帽將開源進行到底

真香,朕在看了!

總結

以上是生活随笔為你收集整理的【建议珍藏系列】如果你这样回答「什么是线程安全」,面试官都会对你刮目相看!...的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

主站蜘蛛池模板: 在线免费观看福利 | 午夜a视频 | 伊人视频 | 欧美三级黄色大片 | 免费天堂av | 91搞搞| www.超碰97| 国产精品久久久亚洲 | 成人片在线免费看 | 99插插插 | 亚洲一区二区三区在线 | 91中文在线观看 | 日韩av在线免费 | 男生操女生免费网站 | 日韩av高清无码 | 欧美日韩激情在线 | 国产成人在线电影 | 一卡二卡国产 | 91黄色大片 | 麻豆视频传媒入口 | 9191国产精品 | 国产片淫乱18一级毛片动态图 | 欧美另类在线播放 | 91久久久久国产一区二区 | 国产免费网 | 女人又爽又黄免费女仆 | 国产盗摄精品 | 欧美日韩二三区 | 今天高清视频在线观看视频 | 这里只有精品视频在线 | 狼人香蕉 | 五月在线视频 | 免费观看的黄色网址 | 北条麻妃一区二区三区免费 | 国产嫩草影院久久久 | 国产一区激情 | 狠狠操狠狠摸 | 一区二区黄色片 | 欧美一级一区二区 | 欧美精品第1页 | 伊人网伊人网 | 欧美日韩国产成人在线 | 久久久久久久久国产精品 | 久久日精品| 欧美国产乱视频 | 调教奶奴| 久久久男女 | 天堂资源站 | 台湾chinesehdxxxx少妇 | 成人无码www在线看免费 | 岛国精品资源网站 | 国产让女高潮的av毛片 | 风韵少妇性饥渴推油按摩视频 | 朝桐光一区二区三区 | 动漫av网| 人人干人人干人人干 | 日韩欧美国产精品 | 亚洲黄色在线观看视频 | 免费看欧美一级片 | 欧美性视屏 | 国产网址在线观看 | 久热草 | 亚洲成色在线 | 欧美一区二区免费电影 | 特黄aaaaaaaaa毛片免费视频 | av一起看香蕉 | 美女久久久久久久久久 | 少妇xxxx69| 奇米四色777| 国产成人av一区二区三区不卡 | 日本在线中文字幕专区 | 疯狂撞击丝袜人妻 | 亚洲色图欧美视频 | 91播放在线 | 911成人网 | www.99在线| 国产情侣自拍av | 成人理论视频 | 福利在线看 | 91免费播放 | 岳奶大又白下面又肥又黑水多 | 韩国一区二区在线观看 | 国产精品一卡二卡三卡 | 欧美熟妇交换久久久久久分类 | 欧美专区视频 | 中国特级黄色大片 | av免费在线观看不卡 | 高清无打码 | 欧美熟妇乱码在线一区 | 日韩精品小视频 | 老司机免费精品视频 | 天堂久久久久久 | 久夜精品| 日韩精品中文字 | 手机看片一区二区三区 | 青青草华人在线视频 | 国产91视频在线 | 波多野结衣av中文字幕 | 全黄一级播放 |