一 JAVA內存模型JMM 安全
Java的內存模型,也就是JVM所設置的內存模型。Java內存模型分爲主存儲器(主內存)和工做存儲器(工做內存),這裏的存儲器與計算機硬件所講的不同。併發
主存儲器,就是實例位置所在的區域,全部的實例都存在主存儲器內,而且實例的字段也位於這裏。主存儲器爲全部的線程所共享,主內存主要對應於Java堆中對象的實例數據部分。app
工做存儲器,它是各個線程所擁有的獨立專門的做業區。在工做存儲器中,存在有主存儲器中必要的拷貝,稱爲工做拷貝或者變量副本。工做內存對應於虛擬機中的部分區域。ide
每一個線程都位於各自的工做存儲器中,每一個線程都不能直接的對存儲器中字段進行引用或者賦值操做。函數
當線程欲引用字段的值時候,會一次將值從主存儲器拷貝到工做存儲器中,而後再引用該工做拷貝的字段。當同一個線程再次引用同一個字段的值時候,可能會引用剛纔的工做拷貝,也可能會從新從主存儲器拷貝到工做存儲器。測試
當線程欲將值指定給字段的時候,會一次將值指定給位於工做存儲器上的工做拷貝。指定完後,工做拷貝的內容則會映射到主存儲器中。至於何時映射,是都JVM決定的。當同一個線程屢次對於同一個字段指定的時候,線程可能只會對工做拷貝進行指定,也有可能會每次指定後,立刻拷貝到主存儲器中。優化
二 內存間的交互操做spa
主內存與工做內存之間的交互操做定義了8種原子性操做。具體以下:線程
1 lock(鎖定):做用於主內存的變量,將一個變量標識爲一條線程獨佔狀態設計
2 unlock(解鎖):做用於主內存的變量,將一個處於鎖定狀態的變量釋放出來
3 read(讀取):做用於主內存的變量,把一個變量的值從主內存傳輸到線程的工做內存中
4 load(載入):做用於工做內存的變量,把read傳輸的變量值放入或者拷貝到工做內存的變量副本
5 use(使用):做用於工做內存的變量,表示線程引用工做內存中的變量值,將工做內存中的一個變量的值傳遞給執行引擎
6 assign(賦值):做用於工做內存的變量,表示線程將指定的值賦值給工做內存中的某個變量。
7 store(存儲):做用於工做內存的變量,把工做內存中的一個變量的值傳送給主內存中
8 write(寫入):做用於主內存的變量,將store傳遞的變量值放入到主內存中對應的變量裏
三 Java同步機制
Java中同步包括:線程同步和內存同步。
synchronized:線程同步和內存同步
線程的同步指的就是利用synchronized設置一個臨界區,使得只有同時一個線程在該臨界區執行。由synchronized所指定的臨界區,來控制線程的操做。
欲進入synchronized時候,線程的工做存儲器若是有未映射到主存儲器的工做拷貝,該內容就會被強制寫入主存儲器,而且會將工做存儲器的工做拷貝所有丟棄清除掉。
欲退出synchronized時候,線程會將工做存儲器中未映射到主存儲器的工做拷貝強制寫入主存儲器中。可是並不會清除或丟棄本身的工做存儲器。
在synchronized中,不論是方法仍是代碼塊,內存同步僅僅會在線程「欲進入」與「欲退出」synchronized時候進行內存同步。若是是「在synchronized內部」或「正在synchronized外部」,不必定會有內存的同步。
Volatile:內存同步
對於關鍵字Volatile,它僅僅是進行內存的同步,並不會涉及線程的同步,利用Volatile修飾的字段能夠容許多個線程同時訪問。當線程欲引用volatile字段的值,就會從主存儲器中拷貝到工做存儲器裏。對於指定給volatile字段值後,工做存儲器的內容都會馬上立刻映射到主存儲器中。
對於Volatile修飾的變量,在讀取的過程與非Volatile變量差很少,只是會在寫入操做上慢一點
一個變量爲Volatile類型具有的兩種特性:
1 保證此變量對全部線程的可見性。指的就是當一個線程修改了這個變量的值後,新值對於其餘線程來說師立刻能夠看到的。
2 禁止指令重排序優化。因爲指令會在執行中進行重排序進行優化,利用Volatile後,能夠保證該指定不會被從新排序,也就是在程序中位於Volatile以前的先執行,位於Volatile以後的在它以後執行,不會混合到前面或者後面指令的重排序中。
通常僅僅在如下場合中選擇使用volatiole:
1 對變量的寫入操做不依賴於變量的當前值
2 變量不會與其它狀態變量一塊兒歸入不變性條件中
3 在訪問變量的時候不須要進行加鎖
因爲long和double是64位的,JVM在處理這種類型的讀寫操做能夠劃分爲兩次的32位操做,因此必需要注意這兩種類型的共享操做
四 原子性、可見性和有序性
Java內存模型對於併發處理都是基於原子性、可見性和有序性進行設計的。
原子性 Java內部6種基本類型都是採用原子性操做的,當須要擴大原子性操做,就能夠利用Java內存模型中的lock和unlock來實現,這兩種方法對應高層次的字節碼指令是monitorenter和monitorexit,而這兩個字節碼指令反映到Java代碼中就是同步synchronized
可見性 指的就是當一個線程修改了共享變量後,其餘線程都立刻看到這個變量值。在Java是利用volatile、synchronized以及final,前兩種已經在前面說明了。對於final指的就是,凡是被final修飾的字段在構造器一旦被初始化完成後,那麼其餘線程就能看到這個final字段的值
有序性 Java運用的就是volatile和synchronized實現的,volatile保證了禁止指令重排序,保證了該指令在程序中原來的順序。而synchronized保證了一個時刻僅容許一個線程對其進行lock操做。
五 happens-before先行發生原則
「先行發生」,能夠主要用來判斷在併發中數據是否存在競爭,線程是否安全。
「先行發生」,是Java內存模型中定義的兩個操做之間的偏序關係。即操做A先行發生於操做B,那麼就是在發生操做B以前,操做A產生的影響可以被操做B觀察到。Java內存模型有幾個先行發生的關係。在併發測試中,若是兩個操做再也不如下類別中,那麼其實際執行就沒有順序保障,即線程是不安全的。
程序順序規則:若是程序中操做A在操做B以前,那麼在線程的內部中A操做將在B操做以前。
監視器規則:在監視器鎖上的解鎖操做先行發生於後面對於同一個鎖的枷鎖操做。
volatile變量規則:對於一個volatile變量的寫操做先行發生於對該變量的讀操做以前。
線程啓動規則:Thread對象的start()方法先行發生於此線程的每個動做。
線程結束規則:線程中全部的操做都先行發生於對此線程的終止測試。
線程中斷規則:當一個線程在另外一個線程上被調用interrupt時,必須在被中斷線程檢測到interrupt調用以前執行。
終結器規則:對象的構造器函數必須在啓動該對象的終結器finalize()方法以前完成。
傳遞性:若是操做A在操做B以前執行,操做B在操做C以前執行,那麼操做A必須在操做C以前執行
注意:凡是多個線程所共享的對象,會對對象的狀態進行修改等操做的時候,通常由synchronized或volatile來保護。