【转载】JAVA内存模型和线程安全
本文轉載自http://shift-alt-ctrl.iteye.com/blog/1845309
?
一.JAVA內存模型(JMM,JAVA Memory Model):
??? 運行時涉及到兩種內存,主內存和工作區內存,其中工作區內存通常為CPU的高速緩存區用來加快內存數據讀取操作的(各線程獨立).所有的變量內容都存在主內存中,當需要對內存數據進行操作時,數據將會從主存中load到工作區緩存并由CPU計算和賦值操作,然后再由工作區內存write到主存中,讀取時如果工作區內存中已經有(loaded)則直接使用;工作區內存保存了線程使用的變量的副本,線程不可以直接操作主內存,只能操作工作區內存,對于需要變更的變量,需要通過一系列回寫指令集同步到主內存中.且工作區內存是線程獨占的,主內存是線程共享的.如下為操作集:
其中read->load,store->write指令必須按照順序執行,即不能load一個沒有被read操作指定的變量,也不能write一個沒有被store操作指定的變量,不過這read + load/store + write不一定必須是連續的,其中間仍然可以有其他指令.(volatile有特例)
??? volatile是java提供的輕量級變量同步機制,它確保了變量可見性,即任何一個線程修改了volatile修飾的變量,其他線程將立即可見.對于普通變量因為存在主內存和工作區內存的復制和同步,所以無法具備此特性.volatile變量存儲在主內中,任何線程需要使用此變量時,必須再次read,因為其他線程對此變量的更改,將會被其他線程在使用此變量時立即獲得新值.
??? volatile只是保證了可見性,但是它并非線程安全,因為如果線程一旦read到此值然后進行計算,在尚未write到主內存時其他線程也做了同樣的操作,那么volatile變量的最終結果將無法達到預期..如果期望volatile變量線程安全,必須同步或者CAS.volatile變量操作時,read->load->use三個操作是連續的,assign->store->write三個操作是連續的.
? ? 通常volatile對“值”類型的對象是有效的,對引用類型是沒有意義的。
?
二.線程安全
????線程是執行任務的最小調度單元,內核線程是OS創建和管理的線程,它將有內核完成線程的切換以及調度(CPU調度).任何一個java線程都對應一個內核線程,即java線程的所有特性都基于內核并受制于內核.在linux和windows系統中,一個java線程就是底層的一個內核線程.java對線程的調度基于內核,在主流的系統中,廣泛采用了"搶占式"調度機制,即線程都以"爭搶CPU資源"的姿態來運行,最終被運行的線程將有內核的調度算法來決定,如果線程沒有獲得運行資源,那么線程將被"暫停".."協同式"調度已經不適合多線程(進程)的系統,它表現為線程之間互相"謙讓",如果一個線程獲得運行資源,那么它將一直運行下去直到結束,如果一個線程是"長時間"的,那么極有可能這個線程將獨占一個CPU,而其他線程無法獲得資源..
?? 線程狀態:
??? 在JAVA中(甚至任何語言或者平臺中)確保線程安全的方式,無外乎"同步鎖"和"CAS","同步鎖"是一種粗暴而嚴格的同步手段,它強制對資源的訪問必須隊列化,一個資源在任何時候只能有一個線程可訪問.在java中"synchronized"修飾詞可以用來同步方法的調用,synchronized可以指定需要同步的對象,如果 沒有指定,默認為當前對象,如果是static方法,則表示對Class同步.synchronized關鍵詞在編譯之后,最終會生成2個指令:monitorenter和monitorexit,執行引擎如果遇到monitorenter指令,將會嘗試獲取對象鎖,如果獲取成功,則鎖計數器+1,同時工作區中的對象值將視為無效,重新從主存中load;monitorexit將導致鎖計數器-1,即釋放鎖,此時將會把對象值從工作區緩存中write到主存中;如果計數器為0,則表示此對象沒有被任何線程加鎖.如果獲取鎖失敗,當前線程阻塞.此外synchronized本身具有"重入性"語義,如果此對象上的monitor是當前線程,那么鎖獲取操作將直接成功.
?? 我們不再爭論synchronized鎖和ReentrantLock API鎖誰更優秀,這一把雙刃劍,性能方面兩者在普通情況下(即無復雜遞深的lock調用或者多層synchronized)性能幾乎差不多,synchronized稍微優秀一些.但是ReentrantLock提供了多樣化的控制以及Condition機制,可以幫助我們有效的控制并發環境中,讓線程遵循條件的阻塞和喚醒;例如BlockingQueue的實現機制.
??? CAS(Compare and swap),設計方式上更像一種"樂觀鎖",通過"比較"-"更新"這種無阻塞的手段實現數據在多線程下的"安全性".在JAVA中CAS操作遍布Atomic包下的API中,底層使用一個閉源的Unsafe.compareAndSwapInt(Object,valueOffset,expect,update),其中需要告知對象的內存地址.CAS會出現一個有趣的問題,就是ABA,即A變量被更改為B之后,再次被更改為A,此時對于持有A數據的線程嘗試更改值是可以成功了,就像B值從來就沒有出現過一樣..其實吧,這個問題不是問題,既然有線程把數據更改為A,那么后續的線程操作就應該遵守現在的結果,而無需關注過去的過程.
總結
以上是生活随笔為你收集整理的【转载】JAVA内存模型和线程安全的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java服务器端编程
- 下一篇: IOS开发知识(二)