Java內存模型
JMM(Java Memory Model)是JVM定義的內存模型,用來屏蔽各類硬件和操做系統的內存訪問差別。數據庫
- 主內存:全部的變量都存儲在主內存(Main Memory,類比物理內存)中。
- 工做內存:每條線程有本身的工做內存(Working Memory,類比處理器高速緩存),線程的工做內存中保存了被該線程使用到的變量的主內存副本拷貝,線程對變量的全部操做(讀取、賦值等)都必須在工做內存中進行,而不能直接讀寫主內存中的變量。不一樣的線程之間也沒法直接訪問對方工做內存中的變量,線程間變量值的傳遞均須要經過主內存來完成。
內存間的交互操做
- lock(鎖定):做用於主內存的變量,它把一個變量標識爲一條線程獨佔的狀態。
- unlock(解鎖):做用於主內存的變量,它把一個處於鎖定狀態的變量釋放出來,釋放後的變量才能夠被其餘線程鎖定。
- read(讀取):做用於主內存的變量,它把一個變量的值從主內存傳輸到線程的工做內存中,以便隨後的load動做使用。
- load(載入):做用於工做內存的變量,它把read操做從主內存中獲得的變量值放入工做內存的變量副本中。
- use(使用):做用於工做內存的變量,它把工做內存中一個變量的值傳遞給執行引擎,每當虛擬機遇到一個須要使用到變量的值的字節碼指令時將會執行這個操做。
- assign(賦值):做用於工做內存的變量,它把一個從執行引擎接收到的值賦給工做內存的變量,每當虛擬機遇到一個給變量賦值的字節碼指令時執行這個操做。
- store(存儲):做用於工做內存的變量,它把工做內存中一個變量的值傳送到主內存中,以便隨後的write操做使用。
- write(寫入):做用於主內存的變量,它把store操做從工做內存中獲得的變量的值放入主內存的變量中。
其中read和load,store和write必須成對使用,順序但補必定連續的執行。通俗的說,就是執行了read,後面必定會執行load,但不必定read以後立馬load;store和write也同樣。lock和unlock也是成對出現,一個變量在同一時間點只能有一個線程對其進行lock。緩存
-
對於普通變量的操做: 建立變量,是在主內存中初始化。 線程用到的變量,會先從主內存中拷貝(read
)出來,加載(load
)到工做內存,而後引用(use
)變量並運算賦值(assign
)。而後存儲(store
)到工做內存,而後更新(write
)掉原來的變量。安全
普通變量的值在線程間傳遞均須要經過主內存來完成。例如,線程A修改一個普通變量的值,而後向主內存進行回寫,另一條線程B在線程A回寫完成了以後再從主內存進行讀取操做,新變量值纔會對線程B可見。多線程
-
對於volatile
修飾的變量:過程和普通變量同樣。但保證變量對全部線程的可見性,而且會禁止指令重排序的優化。併發
volatile的特殊規則保證了新值能當即同步到主內存,以及每次使用前當即從主內存刷新。所以,能夠說volatile保證了多線程操做時變量的可見性,而普通變量則不能保證這一點。app
先行發生原則(happens-before)
它是判斷數據是否存在競爭、線程是否安全的主要依據,依靠這個原則,咱們能夠經過幾條規則一攬子地解決併發環境下兩個操做之間是否可能存在衝突的全部問題。oop
先行發生是Java內存模型中定義的兩項操做之間的偏序關係,若是說操做A先行發生於操做B,其實就是說在發生操做B以前,操做A產生的影響能被操做B觀察到,「影響」包括修改了內存中共享變量的值、發送了消息、調用了方法等。性能
線程
線程是比進程更輕量級的調度執行單位,線程的引入,能夠把一個進程的資源分配和執行調度分開,各個線程既能夠共享進程資源(內存地址、文件I/O等),又能夠獨立調度(線程是CPU調度的基本單位)。優化
實現線程的3種方式:
- 使用內核線程實現
- 內核線程(Kernel-Level Thread,KLT)就是直接由操做系統內核(Kernel,下稱內核)支持的線程,這種線程由內核來完成線程切換,內核經過操縱調度器(Scheduler)對線程進行調度,並負責將線程的任務映射到各個處理器上。每一個內核線程能夠視爲內核的一個分身,這樣操做系統就有能力同時處理多件事情,支持多線程的內核就叫作多線程內核(Multi-Threads Kernel)。
- 程序通常不直接使用內核線程,而是輕量級進程(通俗意義上的線程)。此2者
1:1
對應關係。建立,調用同步等都由系統執行,代價較高(須要在內核態和用戶態之間來回切換),每一個輕量級進程會消耗必定的內核資源(如內核線程的棧空間),所以一個系統支持的輕量級進程時有限的。
- 使用用戶線程實現
- 廣義來講,一個線程只要不是內核線程,就能夠認爲是用戶線程。所以,輕量級進程也屬於用戶線程,但輕量級進程的實現始終是創建在內核之上的,許多操做都要進行系統調用,效率會受到限制。
- 狹義的說,用戶線程指的是徹底創建在用戶空間的線程庫上,系統內核不能感知線程存在的實現。用戶線程的創建、同步、銷燬和調度徹底在用戶態中完成,不須要內核的幫助。若是程序實現得當,這種線程不須要切換到內核態,所以操做能夠是很是快速且低消耗的,也能夠支持規模更大的線程數量,部分高性能數據庫中的多線程就是由用戶線程實現的。這種進程與用戶線程之間
1:N
的關係稱爲一對多的線程模型。
- 使用用戶線程的優點在於不須要系統內核支援,劣勢也是沒有系統內核的支援。全部的線程操做都須要用戶程序本身處理,實現會很複雜,因此如今不多使用了。
- 使用用戶線程加輕量級進程混合實現
- 既存在用戶線程,也存在輕量級進程。用戶線程仍是徹底創建在用戶空間中,所以用戶線程的建立、切換、析構等操做依然廉價,而且能夠支持大規模的用戶線程併發。而操做系統提供支持的輕量級進程則做爲用戶線程和內核線程之間的橋樑,這樣可使用內核提供的線程調度功能及處理器映射,而且用戶線程的系統調用要經過輕量級線程來完成,大大下降了整個進程被徹底阻塞的風險。在這種混合模式中,用戶線程與輕量級進程的數量比是不定的,即爲
N:M
的關係,這種就是多對多的線程模型。
Java線程實現:JDK1.2以前是用戶線程,1.2和以後的版本,使用操做系統原生線程模型(內核線程)。操作系統
Java線程調度
線程調度是指系統爲線程分配處理器使用權的過程,主要調度方式有兩種:
- 協同式線程調度(Cooperative Threads-Scheduling):線程的執行時間由線程自己來控制,線程把本身的工做執行完了以後,要主動通知系統切換到另一個線程上。
- 好處:實現簡單,並且因爲線程要把本身的事情幹完後纔會進行線程切換,切換操做對線程本身是可知的,因此沒有什麼線程同步的問題。
- 壞處:線程執行時間不可控制,甚至若是一個線程編寫有問題,一直不告知系統進行線程切換,那麼程序就會一直阻塞在那裏。
- 搶佔式線程調度(Preemptive Threads-Scheduling):每一個線程將由系統來分配執行時間,線程的切換不禁線程自己來決定(在Java中,
Thread.yield()
可讓出執行時間,可是要獲取執行時間的話,線程自己是沒有什麼辦法的)。在這種實現線程調度的方式下,線程的執行時間是系統可控的,也不會有一個線程致使整個進程阻塞的問題,Java使用的線程調度方式就是搶佔式調度 。
雖然Java線程調度是系統自動完成的,可是咱們仍是能夠「建議」系統給某些線程多分配一點執行時間,另外的線程少分配一點——經過設置線程優先級的方式(兩個線程同時處於Ready狀態時,優先級越高的線程越容易被系統選擇執行),不過這方法不是很可靠,由於系統線程優先級和Java的10種線程優先級不必定一一對應。
線程狀態
在任意時間點,一個線程只有一種狀態
- 新建(New):建立後還沒有啓動
- 運行(Runable):正在執行或正在等待CPU爲它分配執行時間
- 等待(Waiting):
- 無限等待(Waiting):線程不會被分配CPU執行時間,等待被其餘線程顯式地喚醒。
- 期限等待(Timed Waiting):線程不會被分配CPU執行時間,無須等待被其餘線程顯式地喚醒,在必定時間後它們會由系統自動喚醒。
- 阻塞(Blocked):被阻塞
- 阻塞和等待的區別:
阻塞狀態
在等待着獲取到一個排他鎖,這個事件將在另一個線程放棄這個鎖的時候發生;而等待狀態
則是在等待一段時間,或者喚醒動做的發生。在程序等待進入同步區域的時候,線程將進入這種狀態。
- 結束(Terminated):已終止