您好,登錄后才能下訂單哦!
這篇文章主要介紹了JAVA虛擬機高效并發的案例分析,具有一定借鑒價值,需要的朋友可以參考下。希望大家閱讀完這篇文章后大有收獲。下面讓小編帶著大家一起了解一下。
內存模型
內存模型是在特定的操作協議下,對特定的內存或高速緩存進行讀寫訪問的過程抽象。其主要目標是定義程序中各個變量的訪問規則。
主內存和工作內存
所有的變量都存儲在主內存中,每條線程還有自己的工作內存,其工作內存中是被線程使用到的變量的主內存副本拷貝,線程對變量的讀取、賦值等操作都必須在工作內存中進行,而不能直接讀取主內存中的變量。
內存間交互操作
從主內存拷貝到工作內存:順序地執行read和load操作。
工作內存同步到主內存:store和write操作。
volatile的特性
Volatile的作用和synchronized相同,但是和synchronized相比,更輕量。其特性主要有如下兩點:
保證此變量對所有線程的可見性
啥意思呢?指當一個線程修改了這個變量的值,新值對于其他線程來說是立即可知的。而普通變量做不到這一點,普通變量的值在線程間傳遞均需要通過主內存來完成,比如線程A修改了一個普通變量的值,然后向主內存進行回寫,另外一條線程B在線程A回寫完成了之后再從主內存進行讀取操作,新變量值才會對線程B可見。
禁止指令重排序優化
因為指令重排序會干擾程序的并發執行。
多線程
為什么需要多線程?
計算機的運算速度與它的存儲和通信子系統速度的差距太大,大量的時間都花費在磁盤I/O、網絡通信、數據庫訪問上了。使用多線程能更好地利用cpu。
有哪些并發應用場景?
充分利用計算機處理器
一個服務端同時對多個客戶端提供服務
如何使處理器內部的運算單元被充分利用?
加入一層高速緩存
將運算需要使用到的數據復制到緩存中,讓運算能快速進行。當運算結束后再從緩存同步回內存中,這樣處理器就無須等待緩慢的內存讀寫了。不過這個要考慮一個問題:怎么保證緩存的一致性。
對輸入代碼進行亂序執行優化
線程的實現方式
使用內核線程實現
內核線程就是直接由操作系統內核支持的線程。
使用用戶線程實現
用戶線程的建立、同步、銷毀和調度完全在用戶態中完成,不需要內核的幫助,內核也感知不到線程存在的實現。這種實現方式使用較少。
使用用戶線程加輕量級進行混合實現
合并到一起
線程調度
線程調度是指系統為線程分配處理器使用權的過程。主要分為兩種:協同式和搶占式。
協同式
線程的執行時間由線程本身來控制,線程把自己的工作執行完了,會主動通知系統切換到另外一個線程上。
其優點是實現簡單,而且沒有線程同步的問題。缺點是如果一個線程編寫有問題,一直不告訴系統進行線程切換,那程序就會一直阻塞在那里,容易導致系統崩潰。
搶占式
線程將由系統來分配執行時間,線程切換不由本身來決定。java使用的線程調度方式就是這種。
線程安全
當多個線程訪問一個對象時,如果不考慮這個線程在運行時環境下的調度和交替執行,也不需要進行額外的同步,或者在調用方進行任何其它的協調操作,調用這個對象的行為都可以獲得正確的結果,那這個對象就是安全的。
共享數據的分類
不可變
不可變的共享數據是用final修飾的數據,其一定是線程安全的。如果共享數據是一個基本類型變量,那么只要在定義的時候使用final關鍵字即可。
如果共享數據是一個對象,那就需要對象的行為不會對其狀態產生影響,可以將對象中帶有狀態的變量都聲明為final。比如String類就是一個不可變類
絕對線程安全
在Java API中標注自己是線程安全的類,大多數都不是絕對的線程安全。比如Vector是一個線程安全的集合,它的所有的方法都被修飾成同步,但是在多線程的環境中,它依舊不是同步的。
相對線程安全
相對線程安全就是我們通常意義上所說的線程安全,它只能保證對這個對象單獨操作是線程安全的。但是對于一些特定順序的連續調用,就可能需要在調用端使用額外的同步手段來保證調用的正確性。
大部分的線程安全類都屬于這種類型。
線程兼容
對象本身不是線性安全的,但可以通過在調用端正確地使用同步手段來保證對象在并發環境中可以安全的使用。大部分的不是線程安全的類,都屬于這種類型。
線程對立
無論怎樣,都不能在多線程環境中并發使用,如System.setIn()、System.SetOut()。一個對輸入進行修改,一個對輸出進行修改,兩者是不能“交替”進行的。
實現方法
方式一:互斥同步——悲觀并發策略
(1)synchronized
其原理是:這個關鍵字在經過編譯后,會在同步塊的前后分別形成monitorenter和monitorexit這兩個字節碼指令。當執行monitorenter指令時,程序會嘗試獲取對象的鎖,如果能獲取到,則把鎖的計數器+1,相應的,在執行monitorexit時,會將鎖計數器-1。當計數器為0時,鎖就被釋放。
其特點是:對同一條線程來說是可重入的;同步塊在已進入的線程執行完之前,會阻塞后面的其他線程進入。
其選用場景是:在確實必要的情況下才使用此,因為其是重量級的。
(2)ReentrantLock
此重入鎖是java.util.concurrent(JUC)包下的類。其高級特性有:等待可中斷、可實現公平鎖、鎖可以綁定多個條件。
方式二:非阻塞同步——樂觀并發策略
先進行操作,如果沒有其它線程爭用共享數據,那操作就是成功了;如果共享數據有爭用,產生了沖突,那就再采取其它的補償措施。
方式三:無同步方案
如果一個方法本來就不涉及共享數據,那就沒有必要進行同步措施。比如可重復代碼和線程本地存儲。
(1)可重入代碼
如果一個方法,它的返回結果是可預測的,只要輸入了相同的數據,就都能返回相同的結果,那它就滿足可重入的要求。
(2)線程本地存儲
如果一段代碼中所需要的數據必須與其它代碼共享,而且這些共享數據的代碼在同一個線程中執行,如此,我們可以把共享數據的可見范圍限制在一個線程中,這樣,就不用同步也能保證線程之間不出現數據爭用問題。
鎖優化
適應性自旋
因為阻塞或者喚醒一個JAVA的線程需要操作系統切換CPU狀態來完成,這種狀態的轉換需要耗費處理器時間。如果同步代碼塊中的內容過于簡單,很可能導致狀態轉換消耗的時間比用戶代碼執行的時間還要長。
為了解決這個問題,我們可以讓后面請求鎖的線程“稍等一下”,執行一個忙循環,進行自旋。此時沒有放棄處理器的執行時間。如果自旋超過了限定的次數,仍然沒有成功獲得鎖,那就會使用傳統的方式去掛起線程了。
那什么叫做適應性自旋呢?
就是在同一個鎖對象上,如果自旋等待剛剛成功獲得過鎖,那虛擬機就會認為這次自旋獲得鎖的概率挺大,就會允許其自旋等待持續相對更長的時間。相反,如果自旋很少成功獲得過鎖,則可能省略掉自旋過程。
鎖消除
指虛擬機即時編譯器在運行時,對一些代碼上要求同步,但是被檢測到不可能存在共享數據競爭的鎖進行消除。
鎖粗化
如果一系列的連續操作都對同一個對象反復加鎖和解鎖,甚至加鎖操作是出現在循環體中的,那即使沒有線程競爭,頻繁地進行互斥同步操作也會導致不必要的性能損耗。
如果虛擬機探測到一串零碎的操作都對同一個對象加鎖,將會把加鎖同步的范圍粗化到整個操作序列的外部,這樣只需要加鎖一次就夠了。
輕量級鎖
在沒有多線程競爭的前提下,減少傳統的重量級鎖使用操作系統互斥量產生的性能損耗。
適用場景:無實際競爭,多個線程交替使用鎖;允許短時間的鎖競爭。
偏向鎖
偏向鎖用于減少無競爭且只有一個線程使用鎖的情況下,使用輕量級鎖產生的性能消耗。輕量級鎖每次申請、釋放鎖都至少需要一次CAS,但偏向鎖只有初始化時需要一次CAS。
適用場景:無實際競爭,且將來只有第一個申請鎖的線程會使用鎖。
感謝你能夠認真閱讀完這篇文章,希望小編分享JAVA虛擬機高效并發的案例分析內容對大家有幫助,同時也希望大家多多支持億速云,關注億速云行業資訊頻道,遇到問題就找億速云,詳細的解決方法等著你來學習!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。