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