摘要:多線程環境下的一些問題安全性問題在沒有正確同步的情況下,多線程環境下程序可能得出錯誤的結果。一些相關概念競爭條件多線程的環境下,程序執行的結果取決于線程交替執行的方式。而線程的交替操作順序是不可預測的,如此程序執行的結果也是不可預測的。
入口
Java多線程的應用復雜性之如jvm有限的幾個內存方面的操作和規范,就像無數紛繁復雜的應用邏輯建立在有限的指令集上。
如何寫出線程安全的程序,有各種各樣需要遵循的規則,如果硬是去記憶這些寫法或者規則,就事倍功半了,最好是先學習原理,抓住問題的主干,再拓展細節,這也是大家公認的學習某種技術的方式。對于多線程的問題,java使用java內存模型 JMM來保證多個線程可以有效地,正確地工作。
學習的步驟可以分為:關注大師的言行,跟隨大師的舉動——JUC包已經足夠豐富,按照API規范正確使用。
和大師一起修行——理解多線程問題的由來,以及jvm給出的解決方案,需要理解java的內存模型JMM,以及JMM給出的線程工作內存與主內存交互的規則如何形成JMM的happens-before原則等。參考書有知名的《深入理解Java虛擬機》第12章,最重要的《JSR-133 Java內存模型與線程規范》以及《并發編程實戰》第16章。
領悟大師的意境——JUC包的實現原理,volatile和CAS構筑了JUC包的基礎類,AQS,非阻塞數據結構,原子變量,這些基礎類又構建了JUC包的高層類,Lock,同步器,阻塞隊列,并發容器,Executor等。理解了高層類的原理,能夠心里有底地使用這些類,構建健壯的應用。
成為真正的大師——學習JUC包的實現,說不定哪天也能寫出一樣優秀的類。
一些背景知識:現代操作系統的線程主要有三種實現:內核線程實現,用戶線程實現,混合實現
內核線程(KLT):線程表由內核維護,由內核完成線程的切換,內核通過調度器對線程進行調度,并將線程的任務映射到處理器上,每個內核線程可以視為內核的一個分身。程序一般不會直接使用內核線程,而是使用內核線程的一種高級接口——輕量級進程(LWP)(廣義上來說,輕量級進程也是在用戶空間的進程中的,所以也是一種用戶線程)。LWP和KLT是一一對應的,是1:1的關系,因此也叫作一對一線程模型(1:1)。內核線程最大的特點就是,如果有輕量級進程發生了阻塞,不會影響整個進程的工作,內核會運行其他可運行的線程。缺點也是明顯的:各種線程操作都需要系統調用,需要在用戶態和內核態進行來回切換,代價高昂,而且因為占用內核空間,所以內核能支持的數量是有限的。
用戶級線程(UT):狹義上的用戶線程是指,完全建立在用戶空間的線程庫上,由所在進程實現管理的線程。最大的亮點在于可以在不支持多線程的操作系統之上實現多線程,如DOS,同時因為不需要切換到內核態,所以快速且低消耗,也能支持更大規模的線程數量。這種模型中,一個輕量級進程對應多個線程,因此叫做一對多線程模型(1:N),用戶進程的優勢在于不需要內核的支援,而因為沒有內核的支援,所有的線程操作都需要用戶自己處理,導致復雜性是其劣勢。線程的創建,切換,調度都是需要考慮的問題,現在使用用戶線程的程序已經越來越少了。java線程在JDK1.2之前,使用用戶線程。
用戶線程+輕量級進程的混合實現:使用輕量級進程作為用戶線程和內核線程的橋梁的一種實現,用戶線程和輕量級進程的比例不定,因此也叫多對多線程模型(N:M),UNIX家族中的Solaris提供了N:M的線程模型實現。
更詳細的說明可以查看介紹操作系統的書籍。
jdk1.2 之前,java使用的是稱為“綠色線程”的用戶線程,而在1.2中,線程模型替換為基于操作系統原生線程模型來實現
操作系統支持什么樣的線程模型,很大程度上影響java的線程模型,windows和linux系統提供的線程模型是1:1的,所有這兩個平臺上的JDK使用的是1:1的線程模型,一個java線程映射到一個輕量級進程中,Solaris系統同時支持1:1和N:M,該平臺中的JDK可以指定參數選擇線程模型。
操作系統的線程特性會對線程的并發規模和操作成本產生影響,但是對java程序的編寫和運行來說是透明的。
安全性問題:在沒有正確同步的情況下,多線程環境下程序可能得出錯誤的結果。
活躍性問題:在多線程環境下,當某個操作應該繼續執行卻無法繼續執行下去,就造成了活躍性問題,如:死鎖,饑餓,活鎖。
死鎖:
活鎖:
饑餓:
性能問題:線程的頻繁切換將帶來極大的開銷,如:
保存和恢復執行上下文,丟失局部性
使用同步機制的時候,這些機制會抑制某些編譯器優化以保證執行順序,
如使用volatile保證可見性的情況下,使內存緩沖區中的數據無效,其他線程需要重新從主內存中加載
所有這些因素會帶來額外的性能開銷。
競爭條件:多線程的環境下,程序執行的結果取決于線程交替執行的方式。而線程的交替操作順序是不可預測的,如此程序執行的結果也是不可預測的。
狀態:狀態在多線程編程中是一個很核心的概念,因為線程安全性的核心就在于:對可變的共享狀態的訪問操作進行正確的管理。
非正式的定義:狀態可以簡單理解為存儲在對象的域中的數據,下面的Counter類中的count就是一個Counter對象的狀態。如果不能正確地訪問和修改count,那么count的值就不具備正確性。狀態也包括一個對象依賴的對象的域。
多線程的環境下,主要是可變的,共享的狀態會導致安全性問題,可變意味著狀態可以被修改,共享意味著可以被多個線程改變,自然而然的,有三種方法來解決這個問題:1.讓對象不可改變 2. 讓狀態不可共享 3. 必須共享和改變的,使用某種機制來保證順序——同步。
或者更加直接可靠的,不要給對象狀態,一個沒有狀態的對象一定是線程安全的。
線程安全性:當多個線程訪問某個類的時候,這個類始終能表現正確的行為,那么這個類是線程安全的。
public class Counter { private long count = 0; public long getCount(){ return count; } }
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/69353.html
摘要:學習完多線程之后可以通過下面這些問題檢測自己是否掌握,下面這些問題的答案以及常見多線程知識點的總結在這里??蛇x數據結構與算法如果你想進入大廠的話,我推薦你在學習完基礎或者多線程之后,就開始每天抽出一點時間來學習算法和數據結構。 我自己總結的Java學習的系統知識點以及面試問題,已經開源,目前已經 35k+ Star。會一直完善下去,歡迎建議和指導,同時也歡迎Star: https://...
摘要:超詳細的面試題總結一之基本知識多線程和虛擬機創建線程有幾種不同的方式你喜歡哪一種為什么繼承類實現接口應用程序可以使用框架來創建線程池實現接口。死亡線程方法執行結束,或者因異常退出了方法,則該線程結束生命周期。死亡的線程不可再次復生。 超詳細的Java面試題總結(一)之Java基本知識 多線程和Java虛擬機 創建線程有幾種不同的方式?你喜歡哪一種?為什么? 繼承Thread類 實現R...
閱讀 3503·2021-11-24 09:39
閱讀 781·2019-08-30 14:22
閱讀 3031·2019-08-30 13:13
閱讀 2310·2019-08-29 17:06
閱讀 2918·2019-08-29 16:22
閱讀 1255·2019-08-29 10:58
閱讀 2427·2019-08-26 13:47
閱讀 1628·2019-08-26 11:39