<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
面試題:
什麼是執行緒安全呢?當多個執行緒並行存取某個Java物件時,無論系統如何排程這些執行緒,也無論這些執行緒將如何交替操作,這個物件都能表現出一致的、正確的行為,那麼對這個物件的操作是執行緒安全的。
如果這個物件表現出不一致的、錯誤的行為,那麼對這個物件的操作不是執行緒安全的,發生了執行緒的安全問題。
執行緒安全實驗:兩個執行緒對初始值為 0 的靜態變數一個做自增,一個做自減,各做 5000 次,結果是 0 嗎?具體的程式碼如下
public class ThreadDemo { private static int i = 0; public static void main(String[] args) throws InterruptedException { // 執行緒1對變數i做5000次自增運算 Thread t1 = new Thread(()->{ for(int j=0;j<5000;j++){ i++; } }); Thread t2 = new Thread(()->{ for(int j=0;j<5000;j++){ i--; } }); t1.start(); t2.start(); // 主執行緒等待t1執行緒和t2執行緒執行結束再繼續執行 t1.join(); t2.join(); System.out.println(i);// 581 / -1830 / 0 } }
以上的結果可能是正數、負數、零。為什麼呢?因為 Java 中對靜態變數的自增,自減並不是原子操作,要徹底理解,必須從位元組碼來進行分析。
例如對於 i++ 而言,實際會產生如下的 JVM 位元組碼指令:
getstatic i // 獲取靜態變數i的值 iconst_1 // 準備常數1 iadd // 自增 putstatic i // 將修改後的值存入靜態變數i
而對應 i-- 也是類似:
getstatic i // 獲取靜態變數i的值 iconst_1 // 準備常數1 isub // 自減 putstatic i // 將修改後的值存入靜態變數
而 Java 的記憶體模型如下,完成靜態變數的自增,自減需要在主記憶體和工作記憶體中進行資料交換:
如果是單執行緒以上 8 行程式碼是順序執行(不會交錯)沒有問題:
但多執行緒下這 8 行程式碼可能交錯執行:
出現負數的情況:
出現正數的情況:
因此,一個自增運運算元是一個複合操作,至少包括三個JVM指令:“記憶體取值”“暫存器增加1”和“存值到記憶體”。這三個指令在JVM內部是獨立進行的,中間完全可能會出現多個執行緒並行進行。“記憶體取值”“暫存器增加1”和“存值到記憶體”這三個JVM指令本身是不可再分的,它們都具備原子性,是執行緒安全的,也叫原子操作。但是,兩個或者兩個以上的原子操作合在一起進行操作就不再具備原子性了。比如先讀後寫,就有可能在讀之後,其實這個變數被修改了,出現讀和寫資料不一致的情況。
在多個執行緒操作相同資源(如變數、陣列或者物件)時就可能出現執行緒安全問題。一般來說,只在多個執行緒對這個資源進行寫操作的時候才會出現問題,如果是簡單的讀操作,不改變資源的話,顯然是不會出現問題的。
臨界區資源表示一種可以被多個執行緒使用的公共資源或共用資料,但是每一次只能有一個執行緒使用它。一旦臨界區資源被佔用,想使用該資源的其他執行緒則必須等待。在並行情況下,臨界區資源是受保護的物件。
臨界區程式碼段是每個執行緒中存取臨界資源的那段程式碼,多個執行緒必須互斥地對臨界區資源進行存取。執行緒進入臨界區程式碼段之前,必須在進入區申請資源,申請成功之後執行臨界區程式碼段,執行完成之後釋放資源。臨界區程式碼段的進入和退出如圖所示:
競態條件可能是由於在存取臨界區程式碼段時沒有互斥地存取而導致的特殊情況。如果多個執行緒在臨界區程式碼段的並行執行結果可能因為程式碼的執行順序不同而不同,我們就說這時在臨界區出現了競態條件問題。
比如下面程式碼中的臨界區資源和臨界區程式碼段:
public class SafeDemo { // 臨界區資源 private static int i = 0; // 臨界區程式碼段 public void selfIncrement(){ for(int j=0;j<5000;j++){ i++; } } // 臨界區程式碼段 public void selfDecrement(){ for(int j=0;j<5000;j++){ i--; } } // 這個不是臨界區程式碼,因為雖然使用了共用資源,但是這個方法並沒有被多個執行緒同時存取 public int getI(){ return i; } }
public class ThreadDemo { public static void main(String[] args) throws InterruptedException { SafeDemo safeDemo = new SafeDemo(); Thread t1 = new Thread(()->{ safeDemo.selfIncrement(); }); Thread t2 = new Thread(()->{ safeDemo.selfDecrement(); }); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println(safeDemo.getI()); } }
當多個執行緒存取臨界區的selfIncrement()方法時,就會出現競態條件的問題。更標準地說,當兩個或多個執行緒競爭同一個資源時,對資源的存取順序就變得非常關鍵。為了避免競態條件的問題,我們必須保證臨界區程式碼段操作具備排他性。這就意味著當一個執行緒進入臨界區程式碼段執行時,其他執行緒不能進入臨界區程式碼段執行。
(1) 一個程式執行多個執行緒本身是沒有問題的,問題出在多個執行緒存取共用資源,多個執行緒讀共用資源其實也沒有問題,而在多個執行緒對共用資源讀寫操作時發生指令交錯,就會出現問題 ;
(2) 一段程式碼塊內如果存在對共用資源的多執行緒讀寫操作,稱這段程式碼塊為臨界區程式碼塊;
(3) 多個執行緒在臨界區內執行,由於程式碼的執行序列不同而導致結果無法預測,稱之為發生了競態條件;
在Java中,可以使用synchronized關鍵字,使用Lock顯式鎖範例,或者使用原子變數(AtomicVariables)對臨界區程式碼段進行排他性保護。
本篇文章就到這裡了,希望能夠給你帶來幫助,也希望您能夠多多關注it145.com的更多內容!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45