一 JAVA記憶體模型JMM
Java的記憶體模型,也就是JVM所設定的記憶體模型。Java記憶體模型分為主存儲器(主記憶體)和工作存儲器(工作記憶體),這裡的存儲器與計算機硬體所講的不一樣。
主存儲器,就是執行個體位置所在的區域,所有的執行個體都存在主存儲器内,并且執行個體的字段也位于這裡。主存儲器為所有的線程所共享,主記憶體主要對應于Java堆中對象的執行個體資料部分。
工作存儲器,它是各個線程所擁有的獨立專門的作業區。在工作存儲器中,存在有主存儲器中必要的拷貝,稱為工作拷貝或者變量副本。工作記憶體對應于虛拟機中的部分區域。
每個線程都位于各自的工作存儲器中,每個線程都不能直接的對存儲器中字段進行引用或者指派操作。
當線程欲引用字段的值時候,會一次将值從主存儲器拷貝到工作存儲器中,然後再引用該工作拷貝的字段。當同一個線程再次引用同一個字段的值時候,可能會引用剛才的工作拷貝,也可能會重新從主存儲器拷貝到工作存儲器。
當線程欲将值指定給字段的時候,會一次将值指定給位于工作存儲器上的工作拷貝。指定完後,工作拷貝的内容則會映射到主存儲器中。至于什麼時候映射,是都JVM決定的。當同一個線程多次對于同一個字段指定的時候,線程可能隻會對工作拷貝進行指定,也有可能會每次指定後,馬上拷貝到主存儲器中。
二 記憶體間的互動操作
主記憶體與工作記憶體之間的互動操作定義了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來保護。
本文轉自 zhao_xiao_long 51CTO部落格,原文連結:http://blog.51cto.com/computerdragon/1208398