Java---線程多(工作內存)和內存模型(主內存)分析


首先解讀Java內存模型(這里區別於JVM的內存模型,堆、棧、工作區)

  Java 內存模型來屏蔽掉各種硬件和操作系統的內存差異,達到跨平台的內存訪問效果。JLS(Java語言規范)定義了一個統一的內存管理模型JMM(Java Memory Model)

  Java內存模型規定了所有的變量都存儲在主內存中,此處的主內存僅僅是虛擬機內存的一部分,而虛擬機內存也僅僅是計算機物理內存的一部分(為虛擬機進程分配的那一部分)。

  Java內存模型分為主內存,和工作內存。主內存是所有的線程所共享的,工作內存是每個線程自己有一個,不是共享的。

  每條線程還有自己的工作內存,線程的工作內存中保存了被該線程使用到的變量的主內存副本拷貝。線程對變量的所有操作(讀取、賦值),都必須在工作內存中進行,而不能直接讀寫主內存中的變量。不同線程之間也無法直接訪問對方工作內存中的變量,線程間變量值的傳遞均需要通過主內存來完成,線程、主內存、工作內存三者之間的交互關系如下圖:

 

 

Java內存間交互操作

    JLS定義了線程對主存的操作指令:lockunlockreadloaduseassignstorewrite。這些行為是不可分解的原子操作,在使用上相互依賴,read-load從主內存復制變量到當前工作內存,use-assign執行代碼改變共享變量值,store-write用工作內存數據刷新主存相關內容。

  • read(讀取):作用於主內存變量,把一個變量值從主內存傳輸到線程的工作內存中,以便隨后的load動作使用
  • load(載入):作用於工作內存的變量,它把read操作從主內存中得到的變量值放入工作內存的變量副本中。
  • use(使用):作用於工作內存的變量,把工作內存中的一個變量值傳遞給執行引擎,每當虛擬機遇到一個需要使用變量的值的字節碼指令時將會執行這個操作。
  • assign(賦值):作用於工作內存的變量,它把一個從執行引擎接收到的值賦值給工作內存的變量,每當虛擬機遇到一個給變量賦值的字節碼指令時執行這個操作。
  • store(存儲):作用於工作內存的變量,把工作內存中的一個變量的值傳送到主內存中,以便隨后的write的操作。
  • write(寫入):作用於主內存的變量,它把store操作從工作內存中一個變量的值傳送到主內存的變量中。

 

為什么需要要多線程 (充分利用CPU)

  舉一個栗子,假設現在要10000條數據,總共需要100分鍾。如果是單線程的串行操作,需要100分鍾。那么如果同時開10個線程,每一個線程運行100條數據,那么只需要10分鍾就可以完成所有的操作。(總之是充分利用物理資源CPU)

 

多線程的三個特性

1、原子性(Atomicity)
  原子性是指一個原子操作在cpu中不可以暫停然后再調度,既不被中斷操作,要不執行完成,要不就不執行。原子操作保證了原子性問題。

  x++(包含三個原子操作)a.將變量x 值取出放在寄存器中 b.將將寄存器中的值+1 c.將寄存器中的值賦值給x

由Java內存模型來直接保證的原子性變量操作包括read、load、use、assign、store和write六個,大致可以認為基礎數據類型的訪問和讀寫是具備原子性的。如果應用場景需要一個更大范圍的原子性保證,Java內存模型還提供了lock和unlock操作來滿足這種需求,盡管虛擬機未把lock與unlock操作直接開放給用戶使用,但是卻提供了更高層次的字節碼指令monitorenter和monitorexit來隱匿地使用這兩個操作,這兩個字節碼指令反映到Java代碼中就是同步塊---synchronized關鍵字,因此在synchronized塊之間的操作也具備原子性。

2、可見性(Visibility)

  java 內存模型的主內存工作內存,解決了可見性問題。
  volatile賦予了變量可見——禁止編譯器對成員變量進行優化,它修飾的成員變量在每次被線程訪問時,都強迫從內存中重讀該成員變量的值;而且,當成員變量發生變化時,強迫線程將變化值回寫到共享內存,這樣在任何時刻兩個不同線程總是看到某一成員變量的同一個值,這就是保證了可見性。

可見性就是指當一個線程修改了線程共享變量的值,其它線程能夠立即得知這個修改。無論是普通變量還是volatile變量都是如此,普通變量與volatile變量的區別是volatile的特殊規則保證了新值能立即同步到主內存,以及每使用前立即從內存刷新。因為我們可以說volatile保證了線程操作時變量的可見性,而普通變量則不能保證這一點。

3、有序性(Ordering)
  Java內存模型中的程序天然有序性可以總結為一句話:如果在本線程內觀察,所有操作都是有序的;如果在一個線程中觀察另一個線程,所有操作都是無序的。前半句是指“線程內表現為串行語義”,后半句是指“指令重排序”現象和“工作內存主主內存同步延遲”現象。

 

線程狀態

1. 新建狀態(New):新創建了一個線程對象。
2. 就緒狀態(Runnable):線程對象創建后,其他線程調用了該對象的start()方法。該狀態的線程位於可運行線程池中,變得可運行,等待獲取CPU的使用權。
3. 運行狀態(Running):就緒狀態的線程獲取了CPU,執行程序代碼。
4. 阻塞狀態(Blocked):阻塞狀態是線程因為某種原因放棄CPU使用權,暫時停止運行。直到線程進入就緒狀態,才有機會轉到運行狀態。阻塞的情況分三種:
(一)、等待阻塞:運行的線程執行wait()方法,JVM會把該線程放入等待池中。
(二)、同步阻塞:運行的線程在獲取對象的同步鎖時,若該同步鎖被別的線程占用,則JVM會把該線程放入鎖池中。
(三)、其他阻塞:運行的線程執行sleep()或join()方法,或者發出了I/O請求時,JVM會把該線程置為阻塞狀態。當sleep()狀態超時、join()等待線程終止或者超時、或者I/O處理完畢時,線程重新轉入就緒狀態。
5. 死亡狀態(Dead):線程執行完了或者因異常退出了run()方法,該線程結束生命周期。

 

多線程安全

synchronized

  Synchronized關鍵字保證了數據讀寫一致和可見性等問題,但是他是一種阻塞的線程控制方法,在關鍵字使用期間,所有其他線程不能使用此變量,這就引出了一種叫做非阻塞同步的控制線程安全的需求;(同步機制采用了“以時間換空間”的方式)

volatile

  Java語言規范中指出:為了獲得最佳速度,允許線程保存共享成員變量的私有拷貝,而且只當線程進入或者離開同步代碼塊時才與共享成員變量的原始值對比。這樣當多個線程同時與某個對象交互時,就必須要注意到要讓線程及時的得到共享成員變量的變化。而volatile關鍵字就是提示VM:對於這個成員變量不能保存它的私有拷貝,而應直接與共享成員變量交互。使用建議:在兩個或者更多的線程訪問的成員變量上使用volatile。當要訪問的變量已在synchronized代碼塊中,或者為常量時,不必使用。由於使用volatile屏蔽掉了VM中必要的代碼優化,所以在效率上比較低,因此一定在必要時才使用此關鍵字。 就跟C中的一樣 禁止編譯器進行優化~~~~

ThreadLocal

  ThreadLocal不是為了解決多線程訪問共享變量,而是為每個線程創建一個單獨的變量副本,提供了保持對象的方法和避免參數傳遞的復雜性。

  顧名思義它是local variable(線程局部變量)。它的功用非常簡單,就是為每一個使用該變量的線程都提供一個變量值的副本,是每一個線程都可以獨立地改變自己的副本,而不會和其它線程的副本沖突。從線程的角度看,就好像每一個線程都完全擁有該變量。(ThreadLocal采用了“以空間換時間”的方式)

 

Long和Double 64Bit的特殊類型

   對long和double的簡單操作之外,volatile並不能提供原子性。所以,就算你將一個變量修飾為volatile,但是對這個變量的操作並不是原子的,在並發環境下,還是不能避免錯誤的發生!

  Java內存模型lock、unlock、read、load、assign、user、store、write這8個操作都有原子性,但是java內存模型將沒有被volatile修飾的64位的數據的讀寫操作划分為兩次32為的操作來進行,這樣的話,多線程並發,就會存在線程可能讀取到“半個變量”的值,不過,這種情況非常罕見,目前各平台的商用虛擬機幾乎都選擇把64位的讀寫作為原子操作來實現規范的。

 

 

 

 

 

 

 

Java內存大小

  1. -Xms 為jvm啟動時分配的內存,比如-Xms200m,表示分配200M
  2. -Xmx 為jvm運行過程中分配的最大內存,比如-Xms500m,表示jvm進程最多只能夠占用500M內存
  3. -Xss 為jvm啟動的每個線程分配的內存大小,默認JDK1.4中是256K,JDK1.5+中是1M

 

 

 

 

 

note : 解釋多線程

  1. Java語言作為高級語言支持多線程的操作,主要是為了解決單線程因阻塞而帶來的效率問題,同時也充分利用多核CPU的優勢。使用多線程也帶了問題,線程之間如何通信?線程之間如何和同步?

  2. 線程之間的通信是依靠共享內存和線程方法的調用來實現。在多線程的體系下,Java的內存模型分為主內存和共享內存,通過內存之間的數據交換,依賴多線程的可見性,實現線程之間的通信;線程具有基本狀態,主動調用線程的wait、notify方法也可以實現線程之間的通信。

    3.線程的同步也是並發線程操作共享變量所帶來的問題。多線程允許使用synchronize、volatile、ThreadLocal來保證多線程的安全。synchronize是一個重量級的鎖,直接使得線程阻塞,單線程順利執行,對於更新變量不會有並發操作,很大程度的降低的系統的性能。volatile是一個輕量級的原子鎖,對於volatile修飾的變量,每一次的讀和寫,都必須和主內存交互,他禁止了編譯器和處理器的一些重排序優化。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM