1.概述數據庫
a.多任務處理的必要性:數組
b.硬件的效率與一致性緩存
爲了更好的理解Java內存模型,先理解物理計算機中的併發問題,二者有很高的可比性。安全
爲了平衡計算機的存儲設備與處理器的運算速度之間幾個數量級的差距,引入一層高速緩存(Cache)來做爲內存與處理器之間的緩衝:網絡
可是基於高速緩存的存儲交互在多處理器系統中會帶來緩存一致性(Cache Coherence)的問題。這是由於每一個處理器都有本身的高速緩存,而它們又共享同一主內存(Main Memory),當多個處理器的運算任務都涉及同一塊主內存區域時,就可能致使各自的緩存數據不一致。解決辦法就是須要各個處理器訪問緩存時都遵循一些協議,在讀寫時要根據協議來進行操做。以下圖。多線程
所以,這裏所說的內存模型能夠理解爲:在特定的操做協議下,對特定的內存或高速緩存進行讀寫訪問的過程抽象。併發
2.Java內存模型(Java Memory Model,JMM)oop
a.目的:屏蔽掉各類硬件和操做系統的內存訪問差別,實現Java程序在各類平臺下都能達到一致的內存訪問效果。post
b.方法:經過定義程序中各個變量的訪問規則,即在虛擬機中將變量存儲到內存和從內存中取出變量這樣的底層細節。性能
注意:這裏的變量與Java中說的變量不一樣,而指的是實例字段、靜態字段和構成數組對象的元素,但不包括局部變量與方法參數,由於後者是線程私有的,不會被共享,天然就不會存在競爭問題。
c.結構:模型結構如圖,和上張圖進行類比。
注意:這裏的主內存、工做內存與要點提煉| 理解JVM以內存管理說的Java內存區域中的Java堆、棧、方法區等並非同一個層次的內存劃分。
注意:
- 線程對變量的全部操做都必須在工做內存中進行,而不能直接讀寫主內存中的變量。
- 不一樣的線程之間也沒法直接訪問對方工做內存中的變量,線程間變量值的傳遞必須經過主內存來完成。
lock
):把變量標識爲一條線程獨佔的狀態。unlock
):把處於鎖定狀態的變量釋放出來。read
):把變量的值從主內存傳輸到線程的工做內存中,以便隨後的load
動做使用。load
):把read
操做從主內存中獲得的變量值放入工做內存的變量副本中。use
):把工做內存中一個變量的值傳遞給執行引擎。assign
):把從執行引擎接收到的值賦給工做內存的變量。store
):把工做內存中變量的值傳送到主內存中,以便隨後的write
操做使用。write
):把store
操做從工做內存中獲得的變量的值放入主內存的變量中。結論:注意是順序非連續
- 若是要把變量從主內存複製到工做內存,那就要順序地執行
read
和load
。- 若是要把變量從工做內存同步回主內存,就要順序地執行
store
和write
。
d.確保併發操做安全的原則:
①在Java內存模型中規定了執行上述8種基本操做時須要知足以下規則:
read
和load
、store
和write
操做之一單獨出現,即不容許一個變量從主內存讀取了但工做內存不接受,或者從工做內存發起回寫了但主內存不接受的狀況出現。assign
操做,即變量在工做內存中改變了以後必須把該變化同步回主內存。assign
操做就把數據從線程的工做內存同步回主內存中。load
或assign
)的變量,即對一個變量實施use
、store
操做以前必須先執行過了assign
和load
操做。lock
操做,但lock
操做能夠被同一條線程重複執行屢次,屢次執行lock
後,只有執行相同次數的unlock
操做,變量纔會被解鎖。lock
操做,那將會清空工做內存中此變量的值,在執行引擎使用這個變量前,須要從新執行load
或assign
操做初始化變量的值。lock
操做鎖定,那就不容許對它執行unlock
操做,也不容許去unlock
一個被其餘線程鎖定住的變量。unlock
操做以前,必須先把此變量同步回主內存中。可見這麼多規則很是繁瑣,實踐也麻煩,下面再介紹一個等效判斷原則--先行發生原則。
②先行發生原則:是Java內存模型中定義的兩項操做之間的偏序關係。下面例舉一些「自然的」先行發生關係,無須任何同步器協助就已經存在,能夠在編碼中直接使用。
start()
先行發生於此線程的每個動做。Thread.join()
結束、Thread.isAlive()
的返回值等手段檢測到線程已經終止執行。interrupt()
的調用先行發生於被中斷線程的代碼檢測到中斷事件的發生。可經過Thread.interrupted()
檢測到是否有中斷髮生。finalize()
的開始。e.Java內存模型保證併發過程的原子性、可見性和有序性的措施:
read
、load
、assign
、use
、store
和write
,所以可認爲基本數據類型的訪問讀寫是具有原子性的。monitorenter
和monitorexit
來隱式地使用lock
和unlock
這兩個操做,反映到Java代碼中就是同步代碼塊synchronized
關鍵字。volatile
能保證新值能當即同步到主內存,且每次使用前當即從主內存刷新;synchronized
對一個變量執行unlock操做以前能夠先把此變量同步回主內存中;被final
修飾的字段在構造器中一旦初始化完成且構造器沒有把this
的引用傳遞出去,就能夠在其餘線程中就能看見final字段的值。volatile
自己就包含了禁止指令重排序的語義;synchronized
保證一個變量在同一個時刻只容許一條線程對其進行lock操做,使得持有同一個鎖的兩個同步塊只能串行地進入。3.Java與線程
a.線程實現的三種方式
①使用內核線程(Kernel-Level Thread,KLT)
②使用用戶線程(User Thread,UT)
③使用用戶線程加輕量級進程混合
那麼Java線程的實現是選擇哪種呢?答案是不肯定的。操做系統支持怎樣的線程模型,在很大程度上決定了Java虛擬機的線程是怎樣映射的。線程模型只對線程的併發規模和操做成本產生影響,而對Java程序的編碼和運行過程來講,這些差別都是透明的。
b.Java線程調度的兩種方式
線程調度:指系統爲線程分配處理器使用權的過程。
①協同式線程調度(Cooperative Threads-Scheduling)
②搶佔式線程調度(Preemptive Threads-Scheduling)
可是線程優先級並非太靠譜,一方面由於Java的線程是經過映射到系統的原生線程上來實現的,因此線程調度最終仍是取決於操做系統,在一些平臺上不一樣的優先級實際會變得相同;另外一方面優先級可能會被系統自行改變。
c.線程的五種狀態
在任意一個時間點,一個線程只能有且只有其中的一種狀態:
Object.wait()
Thread.join()
LockSupport.park()
Thread.sleep()
Object.wai()
Thread.join()
LockSupport.parkNanos()
LockSupport.parkUntil()
注意區別:
- 阻塞狀態:在等待獲取到一個排他鎖,在另一個線程放棄這個鎖的時候發生;
- 等待狀態:在等待一段時間或者喚醒動做的發生,在程序等待進入同步區域的時候發生。
下圖是線程狀態之間的轉換: