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

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 人文社科 > 生活经验 >内容正文

生活经验

java虚拟机线程调优与底层原理分析_Java并发编程——多线程的底层原理

發布時間:2023/11/27 生活经验 36 豆豆
生活随笔 收集整理的這篇文章主要介紹了 java虚拟机线程调优与底层原理分析_Java并发编程——多线程的底层原理 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

Java代碼在編譯后會變成Java字節碼,字節碼被類加載器加載到JVM里,JVM執行字節碼,最終需要轉化為匯編指令在CPU上執行,Java中所使用的并發機制依賴于JVM的實現和 CPU的指令。本章我們將深入底層一起探索下Java并發機制的底層實現原理

——java并發編程的藝術

volatile關鍵字

并發編程中synchronized和volatile都扮演著重要的角色,volatile是輕量級的synchronized,他在多線程的開發中保證了共享變量的內存可見性。可見性是指,一個線程在修改一個共享變量時,另外一個線程能讀到這個修改的值,而不是內存或者CPU緩存的值。volatile不會引起線程上下文的切換和調度。

volatile的定義與實現原理

定義

java語言規范第三版對volatile的定義如下:Java編程語言允許線程訪問共享變量,為了確保共享變量能被準確和一致地更新,線程應該確保通過排他鎖單獨獲得這個變量。Java語言 提供了volatile,在某些情況下比鎖要更加方便。如果一個字段被聲明成volatile,Java線程內存模型確保所有線程看到這個變量的值是一致的。

volatile有關的CPU術語

在了解volatile的原理之前,我們需要看下與其實現原理相關的CPU術語與說明

術語英文單詞術語描述
內存屏障memory barriers是一組處理器指令,用于實現對內存操作的順序限制
緩沖行cache line緩存中最小的存儲單位,處理器填寫緩存線時會加載整個緩存線,需要使用多個主內存讀周期(偽共享的關鍵原因)
原子操作atomic operations不可中斷的一個或一系列操作
緩存行填充cache line fill當處理器識別到從內存中讀取操作數是可緩存的,處理器讀取整個緩存行到適當的緩存(L1,L2,L3或所有)
緩存命中cache hit如果進行高速緩存行填充操作的內存位置仍然是下次處理器訪問的地址時,處理器從緩存中讀取操作數,而不是從內存中讀取(讀操作)
寫命中write hit當處理器將操作數寫回到一個內存緩存的區域時,他首先會檢查這個緩存的內存地址是否在緩存行中,如果存在一個有效的緩存行,則處理器將這個操作數寫回緩存,而不是寫回到內存,這個操作被稱為寫命中(寫操作)
寫缺失write misses the cache一個有效的緩存行被寫入到不存在的內存區域(寫之前緩沖區沒有操作數的緩存)

那么volatile是如何保證內存可見性的呢?我們在X86處理器下通過工具獲取到JIT編譯器生成的匯編指令來查看對volatile進行寫操作,CPU會做什么事情

Java代碼如下:

instance?=?new?Singleton()??????????????????//instance是volatile變量

轉變成匯編代碼,如下:

0x01a3de1d: movb $0×0,0×1104800(%esi);0x01a3de24: lock addl $0×0,(%esp);

有volatile關鍵字修飾的共享變量進行寫操作的時候會多出第二行匯編代碼,Lock前綴的指令在多核處理器會引發兩件事情:

  • 將當前處理器緩存行的數據寫回到系統內存
  • 這個寫回內存的操作會使在其他CPU里緩存了該內存地址的數據無效

這就是Lock指令保證了CPU緩存的一致性。當處理器對被volatile關鍵字修飾的變量修改時,會重新從系統內存中把數據讀到處理器緩存里。

volatile的實現原則
  • Lock前綴指令會引起處理器緩存寫回到內存
  • 一個處理器的緩存寫到內存會導致其他處理器的緩存無效

volatile的使用優化

追加字節優化性能

因為對于Intel core i7、酷睿、Atom和NetBurst,以及Core Solo和Pentium M處理器的L1、L2、L3的高速緩存行都是64字節寬,不支持部分填充緩存行,這意味著,如果隊列的頭節點和尾節點都不足64字節的話、處理器會將他們讀到同一個高速緩存行中,在多處理器下每個處理器都會緩存同樣的頭、尾節點,當一個處理器試圖修改頭節點的時候,會將整個緩存行鎖定,導致其他處理器無法訪問自己高速緩存中的尾節點,造成了偽共享問題。這嚴重影響了效率。

但是如果我們將頭、尾節點填充到64字節,處理器就不會把他們放在同一個緩存行中,這就解決了偽共享問題。

但是,也不是在使用volatile變量時都應該追加到64字節,下面兩種情景就不需要

  • 緩存行非64字節寬的處理器
  • 共享變量不會被頻繁的讀寫

不過在Java7中,追加字節的方式可能不生效,因為Java7更加智能,會淘汰或者重新排列無用字段,除了volatile,Java并發編程中應用比較多的是synchronized

synchronized的實現原理與應用

synchronized被稱為重量級鎖。實際上隨著Java SE對synchronized進行各種優化之后,有些情況他就沒那么重了。

synchronized實現同步的基礎

Java中每一個對象都可以作為鎖。具體表現為以下三種形式:

  • 對于普通同步方法,鎖是當前實例對象
  • 對于靜態同步方法,鎖是當前類的Class對象
  • 對于同步方法塊,鎖是synchronized括號里配置的對象

當一個線程試圖訪問同步代碼塊時,他首先必須得到鎖,退出或拋出異常時必須釋放鎖。那么,鎖究竟是什么?

JVM基于進入和退出Monitor對象來實現方法同步和代碼塊同步,但兩者細節不一樣,代碼塊同步是使用monitorentermonitorexit指令實現的。而方法同步是使用另外一種方式實現的,這里不詳細說明。

monitorenter指令是在編譯后插入到同步代碼塊的開始位置,而monitorexit是插入到方法結束處和異常處,JVM需要保證每個monitorenter必須有對應的monitorexit與之配對。任何對象都有一個monitor與之關聯,當且一個monitor被持有后,他將處于鎖定狀態。線程執行到monitorenter指令時,將會嘗試獲取對象所對應的monitor的所有權,即嘗試獲取對象的鎖。

Java對象頭

synchronized用的鎖是存在Java對象頭里的。如果對象是數組類型,則虛擬機用3個字寬存儲對象頭,如果是非數組類型,則用2字寬存儲對象頭。

在32位虛擬機中,1字寬等于4字節,即32bit。

長度內容說明
32/64bitMark Word存儲對象的hashCode或鎖信息等
32/64bitClass Metadata Address存儲對象類型數據的指針
32/64bitArray length數組的長度(如果當前對象是數組)

Java對象頭里的Mark Word里默認存儲對象的HashCode、分代年齡和所標記位。32位JVM的MarkWord的默認存儲結構如下表

鎖狀態25bit4bit1bit是否是偏向鎖2bit鎖標志位
無鎖狀態對象的hashCode對象分代年齡001

在運行期間,Mark Word存儲的數據會隨著鎖標志位的變化而變化,其有可能變化為存儲以下4種數據。

MarkWord的狀態變化

在64位虛擬機下,Mark Word是64bit大小的,其存儲結構如下表:

image-20200710120927650

鎖的升級

為了減少獲得鎖和釋放鎖帶來的性能消耗,引入了“偏向鎖”和“輕量級鎖”,在JavaSE 1.6中,鎖一共有4種狀態,從低到高依次是無鎖狀態,偏向鎖狀態、輕量級鎖狀態和重量級鎖狀態。鎖可以升級但不能降級,這是為了提高獲得鎖和釋放鎖的效率。

偏向鎖

在大多數情況下,鎖不僅不存在多線程競爭,而且總是由同一線程多次獲得,為了讓線程獲得鎖的代價更低而引入了偏向鎖。當一個線程訪問同步塊并獲取鎖時,會在對象頭和棧幀的鎖記錄里存儲鎖偏向的線程ID,以后該線程在進入和退出同步塊時不需要進行CAS操作來加鎖和解鎖,只需要測試一下對象頭的Mark Word里是否儲存著指向當前線程的偏向鎖。如果測試失敗,會檢測Mark Word里偏向鎖的標識是否設置為1(表示當前是偏向鎖):如果沒有設置,則使用CAS競爭鎖;如果設置了,則嘗試使用CAS將對象頭的偏向鎖指向當前線程。

只有遇到其他線程嘗試競爭偏向鎖時,持有偏向鎖的線程才會主動釋放偏向鎖。線程不會主動釋放偏向鎖。

當有線程競爭的時候,偏向鎖會升級成輕量級鎖

偏向鎖的撤銷

偏向鎖使用了一種等到競爭出現才釋放鎖的機制,所以當其他線程嘗試競爭偏向鎖時,持有偏向鎖的線程才會釋放鎖。偏向鎖的撤銷需要等待全局安全點(在這個時間點上沒有正在執行的字節碼)。他會暫停并檢測擁有偏向鎖的線程,如果線程不處于活動狀態,就將對象頭設置為無鎖狀態。如果線程仍然活著,擁有偏向鎖的棧就會被執行,遍歷偏向對象的鎖記錄。對象頭的Mark Word要么重新偏向其他線程,要么恢復到無鎖或者標記對象不適合作為偏向鎖,最后喚醒暫停的線程。

image-20200712092001574
關閉偏向鎖

偏向鎖在Java6和Java7里是默認啟用的,但是它在應用程序啟動幾秒之后才激活,如有必要可以使用JVM參數來關閉延遲:-XX:BiasedLockingStartupDelay=0。如果確定應用程序中所有鎖通常情況處于競爭狀態,可以通過JVM參數關閉偏向鎖:-XX:-UseBiasedLocking=false,那么程序默認會進入輕量級鎖狀態。

輕量級鎖

輕量級鎖加鎖

線程在執行同步代碼塊之前,JVM會先在當前線程中創建用于存儲所記錄的空間并將對象頭中的Mark Word復制到鎖記錄中,官方稱為Displace Mark Word。線程嘗試用CAS將對象頭中的Mark Word替換成指向鎖記錄的指針。如果成功 ,當前線程獲得鎖,如果失敗,則表示其他線程競爭鎖,當前線程便嘗試自旋來獲取鎖。

輕量級鎖解鎖

輕量級解鎖時,會使用原子的CAS操作將Displaced Mark Word替換成對象頭,如果成功,則表示沒有競爭發生。當自選超過一定次數,表示當前鎖存在競爭,鎖就會膨脹成重量級鎖。下圖描述了這一過程

image-20200712145208862

因為自旋會消耗CPU,為了避免無用的自旋(比如鎖的線程被阻塞住了),一旦鎖升級成重量鎖,就不會再恢復到輕量級鎖了。

鎖的優缺點對比

優點缺點使用場景
偏向鎖加鎖和解鎖不需要額外的消耗,和執行非同步方法相比,僅存在納秒級的差距如果線程間存在鎖競爭,會帶來額外的鎖撤銷的消耗適用于只有一個線程訪問同步塊情景
輕量級鎖競爭的線程不會阻塞,提高了程序的響應速度如果始終得不到鎖競爭的線程,使用自旋會消耗CPU追求響應時間。同步塊執行速度非常快
重量級鎖線程競爭不用自旋,不會消耗CPU線程阻塞,相應時間緩慢追求吞吐量。同步塊執行速度較長

原子操作的實現原理

原子(atomic)意為不能進行分割的最小粒子,Java中的原子操作也是“不可被中斷的一個或一系列操作”。接下來簡單的說一說Intel處理器和Java里是如何實現原子操作的。

1、術語定義

術語名稱英文解釋
緩存行Cache Line緩存的最小操作單位
比較并交換Compare and Swap即CAS操作,通過比較操作前的值與期望的值是否一樣,如果一樣,就將舊值換成新值
CPU流水線CPU pipelineCPU流水線的工作方式類似于工業生產的裝配流水線,在CPU中由5~6個不同功能的電路單元組成一條指令處理流水線,提高CPU的運算速度
內存順序沖突Memory order violation內存順序沖突一般是由假共享引起的,假共享是指多個CPU同時修改同一個緩存行的不同部分而引起其中一個CPU的操作無效,當出現內存順序沖突時,CPU必須清空流水線

原子操作的實現

32位IA-32處理器使用基于對緩存加鎖或總線加鎖的方式來實現多處理器之間的原子操作。首先處理器會自動保證基本的內存操作的原子性(從內存讀取或者寫入一個字節是原子的)。復雜的操作,處理器無法保證原子性,因此,處理器提供**總線鎖定緩存鎖定**兩個機制來保證復雜內存操作的原子性。

總線鎖定保證原子性

如果多個處理器同時對共享變量進行讀改寫操作,那么共享變量就會被多個處理器同時進行操作,這樣讀改寫操作就不是原子的,操作完共享變量的值會和期望的不一致,如下圖(兩個線程同時對i進行累加)

image-20200712155421044

總線索就是來解決這個問題的:

所謂總線索就是使用處理器提供一個LOCK#信號,當一個處理器在總線上輸出此信號時,其他處理器的對緩存了該共享變量的緩存行的請求將被阻塞住,那么該處理器可以獨占共享內存。

使用緩存鎖保證原子性

在同一時刻,我們只需保證對某個內存地址的操作時原子性即可,但總線鎖定把CPU和內存之間的通信鎖住了,這使得鎖定期間,其他處理器不能操作其他內存地址的數據。總線鎖定的開銷是比較大的。目前處理器在某些場合下使用緩存鎖定代替總線鎖定來進行優化。

頻繁使用的內存會緩存在處理器的L1、L2和L3高速緩存里,那么原子操作就可以直接在處理器內存緩存中進行,并不需要聲明總線鎖,在Pentium6和目前的處理器中可以使用“緩存鎖定”的方式來實現復雜的原子性操作。所謂**“緩存鎖定”是指內存區域如果被緩存在處理器的緩存行中,并且在Lock操作期間被鎖定,那么當它執行鎖操作回寫到內存時,處理器不在總線上聲言LOCK#信號,而是修改內部的內存地址,并允許它的緩存一致性機制來保證操作的原子性。**這是因為緩存一致性會阻止同時修改由兩個以上處理器緩存的內存區域數據,當其他處理器回寫已被鎖定的緩存行的數據時,會使緩存行無效。

不能使用緩存鎖定的情況

  1. 當操作的數據不能被緩存在處理器內部,或操作的數據跨多個緩存行時,則處理器會調用總線鎖定
  2. 有些處理器不支持緩存鎖定。如Intel 486和Pentium處理器,就算鎖定的內存區域在處理器的緩存行中也會調用總線鎖定。

Java實現原子操作

Java可以通過鎖和CAS的方式實現原子操作。

循環CAS實現原子操作

JVM中的CAS操作正是利用處理器提供的CMPXCHG指令實現的。自旋CAS實現的基本思路就是循環進行CAS操作直到成功為止。從Java1.5開始,JDK并發包里提供了一些類來支持原子操作,如:AtomicBoolean,AtomicInteger等。

CAS實現原子性的三大問題

  • ABA問題。使用AtomicStampReference類來解決。其會檢查當前引用是否等于預期引用,當前標志是否等于預期標志。簡單理解就是加上時間戳或者加入一個標志變量(線程修改一次就加一),讓舊值無法復原。
  • 循環時間開銷大。這個暫時無法解決,《Java并發編程的藝術》一書給出的建議是JVM加入處理器提供的pause指令
  • 只能保證一個共享變量的原子操作。解決方法:1)將多個共享變量合并成有一個共享變量來操作;2)使用AtomicRederence類來保證引用對象之間的原子性,就可以把多個變量放在一個對象里進行CAS操作。

總結

以上是生活随笔為你收集整理的java虚拟机线程调优与底层原理分析_Java并发编程——多线程的底层原理的全部內容,希望文章能夠幫你解決所遇到的問題。

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