【面試】如果你這樣回答“什麼是執行緒安全”,面試官都會對你刮目相看
不是執行緒的安全
面試官問:“什麼是執行緒安全”,如果你不能很好的回答,那就請往下看吧。
論語中有句話叫“學而優則仕”,相信很多人都覺得是“學習好了可以做官”。然而,這樣理解卻是錯的。切記望文生義。
同理,“執行緒安全”也不是指執行緒的安全,而是指記憶體的安全。為什麼如此說呢?這和作業系統有關。
目前主流作業系統都是多工的,即多個程序同時執行。為了保證安全,每個程序只能訪問分配給自己的記憶體空間,而不能訪問別的程序的,這是由作業系統保障的。
在每個程序的記憶體空間中都會有一塊特殊的公共區域,通常稱為堆(記憶體)。程序內的所有執行緒都可以訪問到該區域,這就是造成問題的潛在原因。
假設某個執行緒把資料處理到一半,覺得很累,就去休息了一會,回來準備接著處理,卻發現數據已經被修改了,不是自己離開時的樣子了。可能被其它執行緒修改了。
比如把你住的小區看作一個程序,小區裡的道路/綠化等就屬於公共區域。你拿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。
沒有規則,那就先入為主
前面給出的三種方案,有點“理想化”了。現實中的情況其實是非常混亂嘈雜的,沒有規則的。
比如在中午高峰期你去飯店吃飯,進門後發現只剩一個空桌子了,你心想先去點餐吧,回來就坐這裡吧。當你點完餐回來後,發現已經被別人捷足先登了。
因為桌子是屬於公共區域的物品,任何人都可以坐,那就只能誰先搶到誰坐。雖然你在人群中曾多看了它一眼,但它並不會記住你容顏。
解決方法就不用我說了吧,讓一個人在那兒看著座位,其它人去點餐。這樣當別人再來的時候,你就可以理直氣壯的說,“不好意思,這個座位,我,已經佔了”。
我再次相信聰明的你已經猜到了我要說的東西了,沒錯,就是(互斥)鎖。
回到程式裡,如果公共區域(堆記憶體)的資料,要被多個執行緒操作時,為了確保資料的安全(或一致)性,需要在資料旁邊放一把鎖,要想操作資料,先獲取鎖再說吧。
假設一個執行緒來到資料跟前一看,發現鎖是空閒的,沒有人持有。於是它就拿到了這把鎖,然後開始操作資料,幹了一會活,累了,就去休息了。
這時,又來了一個執行緒,發現鎖被別人持有著,按照規定,它不能操作資料,因為它無法得到這把鎖。當然,它可以選擇等待,或放棄,轉而去幹別的。
第一個執行緒之所以敢大膽的去睡覺,就是因為它手裡拿著鎖呢,其它執行緒是不可能操作資料的。當它回來後繼續把資料操作完,就可以把鎖給釋放了。鎖再次回到空閒狀態,其它執行緒就可以來搶這把鎖了。還是誰先搶到鎖誰操作資料。
-
相關推薦
【面試】如果你這樣回答“什麼是執行緒安全”,面試官都會對你刮目相看
有讀者跟我說,喜歡看我的文章,說很容易讀,我確實在易讀性上花費的心思不亞於在內容上。因為我不喜歡一上來就堆很多東西,而且把簡單的東西搞得複雜人人都會,但是把複雜的東西講的簡單,確實需要非常多的思考。不是執行緒的安全面試官問:“什麼是執行緒安全”,如果你不能很好的回答,那就請往下看吧。論
【資料庫】使用悲觀鎖實現執行緒同步,實現秒殺效果
一、前言 小編在最近的專案中遇到了要對資料庫中同一個欄位進行操作的一個功能,少數人操作的話,還體現不出來執行緒的問題,當很多人同時使用,資料量變大,就會出現執行緒的問題。如何保持執行緒同步
網路程式設計基礎【day10】:我是一個執行緒(四)
本節內容 1、第一回 初生牛犢 2、第二回 漸入佳境 3、第三回 虎口脫險 4、第四回 江湖再見 第一回 初生牛犢 我是一個執行緒,我一出生就被編了個號:0x3704,然後被領到一個昏暗的屋子裡,在這裡我發現了很多和我一模一樣的同伴。 我身邊的同伴0x6900 待的時間比較長,他帶著滄桑的口氣對
【Linux】多程序與多執行緒之間的區別
http://blog.csdn.net/byrsongqq/article/details/6339240 網路程式設計中設計併發伺服器,使用多程序與多執行緒 ,請問有什麼區別? 答案一: 1,程序:子程序是父程序的複製品。子程序獲得父程序資料空間、堆和棧的複製品。 2,執行緒:相
【iOS】第02講 多執行緒NSThread/GCD/RunLoop/NSTimer/Socket資料傳輸
一、NSThread 1.1 基本使用 -(void) createThread{ //NSThread &nb
【07】單例VS多執行緒
還是套路問題,一種思想而已,兩種方式 1 dubble check instance 2 static inner class 兩次檢測加類鎖 靜態內部類,其實就是餓漢模式,直接給你就好了 package Concurre
【JMeter】04 詳解jmeter執行緒組
文章目錄 一、執行緒組 二、執行緒組的三種類型 1、 setup thread group 應用場景舉例: 2、teardown thread group 應用場景舉例:
Java併發程式設計隨筆【九】中被丟棄的執行緒組ThreadGroup
執行緒組的初衷是作為一種隔離的機制,當然是出於安全的考慮。但是它們從來沒有真正的履行這個承諾,它們的安全價值已經差到根本不在Java安全模型的標準工作中被提及的地步。 既然執行緒組並沒有提供所提及的任何安全功能,那麼它們到底提供了什麼功能呢?不多,它們允許你同
【Java】基於TCP協議多執行緒伺服器-客戶端互動控制檯聊天室簡例
前兩天想到一個手機APP專案,使用到藍芽,發現BluetoothSocket和J2EE網路變成的Socket差不多,使用之餘順手寫一個多執行緒伺服器與客戶端互動實現聊天室的一個小例子,方便新人學習網路程式設計模組,期間使用到多執行緒和IO輸入輸出流的
【轉載】在C#中主執行緒和子執行緒如何實現互相傳遞資料
using System; using System.Collections.Generic; using System.Text; using System.Threading; namespace ATest { class A { public static
【收藏】C#中的多執行緒——執行緒同步基礎
第二部分:執行緒同步基礎 同步要領 下面的表格列展了.NET對協調或同步執行緒動作的可用的工具: 簡易阻止方法 構成 目的 Sleep 阻止給定的時間週期 Join 等待另一個執行緒完成 鎖系統 構成 目的 跨程序?
【python】Threading快速使用和執行緒鎖的理解及.join()用法詳說
0X0:在開始之前先理解一下threading的意義,我們知道寫一個程式,之後程式執行完畢,得到想要的結果。這就是我對一個軟體的理解。 那麼有時候就出現了一個問題,如果這個程式在執行過程中要實現多個功能,即先執行第一個功能,同時執行第二個功能,最後執行第三個功
【多執行緒】初步瞭解java多執行緒安全的容器類CopyOnWriteArrayList
通常我們理解上,執行緒安全的容器類一般指Vector、HashTable等,但在進一步瞭解後,其實真正意義上的執行緒安全沒有那麼簡單。 執行緒安全實際上分為多個級別: (1)不可變 不可變類,典型例子是常用的String、Integer、Long等,作為不可變類,任何一
【Spring】Spring高階話題-多執行緒-TaskExecutor
分析 在Spring中,通過任務執行器,也就是TaskExecutor來實現多執行緒和併發程式設計。 使用ThreadPoolTaskExecutor可實現一個基於執行緒池的TaskExecutor。 而實際開發中任務一般是非阻礙的,也就是非非同步
【Linux】生產者消費者程式設計實現-執行緒池+訊號量
生產者消費者程式設計實現,採用了執行緒池以及訊號量技術。 執行緒的概念就不多說,首先說一下多執行緒的好處:多執行緒技術主要解決處理器單元內多個執行緒執行的問題,它可以顯著減少處理器單元的閒置時間,增加處理器單元的吞吐能力。 那麼為什麼又需要執行緒池呢? 我們知道應
【多執行緒】例項變數(synchronized)與執行緒安全
一、例項變數與執行緒安全: package cn.hncu.lang.thread_; /** * 專案名:例項變數和執行緒安全 * 時間 :2017-9-17 下午2:14:02 */
面試必問題目“程序、執行緒對比”,包你會
簡要說明 在C語言、C++等方向面試時,經常會被問道 程序、執行緒等問題,當然了10年前我剛開始找工作那會,也是各種煎熬“我又不寫作業系統,為什麼還要學這麼底層的知識”,真想不通面試官是不是sha。。。 轉眼間,我現在成了面試官,你說可笑不。。。。世事變化無常啊。。。。 為了讓各位小夥伴把這塊
【萬字圖文-原創】 | 學會Java中的執行緒池,這一篇也許就夠了!
![](https://img2020.cnblogs.com/blog/799093/202005/799093-20200524082508581-233911575.png) ### 碎碎念 關於JDK原始碼相關的文章這已經是第四篇了,原創不易,粉絲從幾十人到昨天的`666`人,真的很感謝之前幫我
【JAVA8新的時間與日期 API】- 傳統時間格式化的執行緒安全問題
Java8之前的日期和時間API,存在一些問題,最重要的就是執行緒安全的問題。這些問題都在Java8中的日期和時間API中得到了解決,而且Java8中的日期和時間API更加強大。 傳統時間格式化的執行緒安全問題 示例: import java.text.SimpleDateFormat; import
讓你秒懂執行緒和執行緒安全,只需5步!
在探討執行緒安全之前,我們先來聊聊什麼是程序。 什麼是程序? 電腦中時會有很多單獨執行的程式,每個程式有一個獨立的程序,而程序之間是相互獨立存在的。比如下圖中的QQ、酷狗播放器、電腦管家等等。 什麼是執行緒? 程序想要執行任務就需要依賴執行緒。換句話說,就是程