android volatile的使用
今天,簡(jiǎn)單講講android里的volatile的使用。
這個(gè)其實(shí)很簡(jiǎn)單,而且我基本沒有用到,但是還是記錄一下。volatile的作用基本和sychronized相似,但是不能替代sychronized。
? volatile用處說明
? ? 在JDK1.2之前,Java的內(nèi)存模型實(shí)現(xiàn)總是從主存(即共享內(nèi)存)讀取變量,是不需要進(jìn)行特別的注意的。而隨著JVM的成熟和優(yōu)化,現(xiàn)在在多線程環(huán)境下volatile關(guān)鍵字的使用變得非常重要。
在當(dāng)前的Java內(nèi)存模型下,線程可以把變量保存在本地內(nèi)存(比如機(jī)器的寄存器)中,而不是直接在主存中進(jìn)行讀寫。這就可能造成一個(gè)線程在主存中修改了一個(gè)變量的值,而另外一個(gè)線程還繼續(xù)使用它在寄存器中的變量值的拷貝,造成數(shù)據(jù)的不一致。
這個(gè)基本就是volatile的使用,可以修飾變量,當(dāng)多線程訪問時(shí),直接訪問變量的內(nèi)存而不是緩存,可以使用與多線程并發(fā)。但是存在問題,下面說一下:
volatile為什么不能保證原子性?
現(xiàn)在我們的手機(jī)都是多核的,也就是說同時(shí)有好幾顆CPU在工作,每顆CPU都有自己的Cache高速緩存,因?yàn)镃PU的速度特別快,而內(nèi)存的讀取操作相對(duì)于CPU的運(yùn)算速度來說很慢,所以就會(huì)拖累CPU的效率,引入Cache就是為了解決這個(gè)問題的,CPU先把需要的數(shù)據(jù)從內(nèi)存中讀到Cache中,然后直接和Cache來打交道,Cache的速度很快,因此可以保證CPU的工作效率,當(dāng)Cache中的數(shù)據(jù)改變后,再將被改變的數(shù)據(jù)寫回內(nèi)存中。
首先我們分析一下多線程在訪問一個(gè)普通的(沒有加volatile修飾符)的變量的過程
1.CPU1和CPU2先將count變量讀到Cache中,比如內(nèi)存中count=1,那么現(xiàn)在兩個(gè)CPU中Cache中的count都等于1
2.CPU1和CPU2分別開啟一個(gè)線程來對(duì)count進(jìn)行自增操作,每個(gè)線程都有一塊自己的獨(dú)立內(nèi)存空間來存放count的副本。
3.線程1對(duì)副本count進(jìn)行自增操作后,count=2 ; 線程2對(duì)副本count進(jìn)行自增操作后,count=2
4.線程1和線程2將操作后的count寫回到Cache緩存的count中
5.CPU1和CPU2將Cache中的count寫回內(nèi)存中的count。
那么問題就來了,線程1和線程2操作的count都是一個(gè)count副本,這兩個(gè)副本的值是一樣的,所以進(jìn)行了兩次自增后,寫回內(nèi)存的count=2。而正確的結(jié)果應(yīng)該為count=3。這就是多線程并發(fā)所帶來的問題
如果變量count用volatile修飾了可以解決這個(gè)問題嗎?
如果一個(gè)變量加了volatile關(guān)鍵字,就會(huì)告訴編譯器和JVM的內(nèi)存模型:這個(gè)變量是對(duì)所有線程共享的、可見的,每次JVM都會(huì)讀取最新寫入的值并使其最新值在所有CPU可見。我們?cè)賮砜匆幌戮€程在訪問一個(gè)加了volatile修飾符的變量的過程
當(dāng)count用volatile關(guān)鍵字修飾后,CPU1對(duì)count的值更新后,在寫回內(nèi)存的同時(shí)會(huì)通知CPU2 count值已經(jīng)更新了,你需要從內(nèi)存中獲取count最新的值!
注意:這里說CPU1通知CPU2其實(shí)是不嚴(yán)謹(jǐn)?shù)?#xff0c;其實(shí)這是緩存一致性機(jī)制在其作用,緩存一致性機(jī)制會(huì)阻止同時(shí)修改由兩個(gè)以上處理器緩存的內(nèi)存區(qū)域數(shù)據(jù),當(dāng)其他處理器回寫已被鎖定的緩存行數(shù)據(jù)時(shí),會(huì)使緩存行無效,當(dāng)CPU1將新數(shù)據(jù)寫回內(nèi)存后,會(huì)修改該數(shù)據(jù)在內(nèi)存中的內(nèi)存地址,CPU2通過嗅探在總線上傳播的數(shù)據(jù)來檢查自己的緩存行對(duì)應(yīng)的內(nèi)存地址是否被修改,如果被修改則將CPU2的該數(shù)據(jù)緩存行設(shè)置成無效狀態(tài),當(dāng)處理器對(duì)這個(gè)數(shù)據(jù)進(jìn)行修改操作的時(shí)候,會(huì)重新從系統(tǒng)內(nèi)存中把數(shù)據(jù)讀到CPU2的緩存行里。其實(shí)并不是CPU1通知CPU2,而是CPU2自己去嗅探。
其實(shí)大家只要明白了原理,怎么說也無所謂,就像好多地方都說volatile修飾的變量,線程直接和內(nèi)存交互,不會(huì)保存副本。而實(shí)際上線程還是會(huì)保存副本,只不過CPU每次都會(huì)從內(nèi)存中拿到最新的值,并且改變數(shù)據(jù)之后立馬寫回內(nèi)存,看上去就像線程直接和內(nèi)存交互一樣。
然后CPU2中的線程如果需要使用到count的時(shí)候,就會(huì)再從內(nèi)存中讀取count的值來更新自己的Cache。這看上去似乎解決了我們的問題,其實(shí)問題依然存在,我們來分析一下:
比如當(dāng)前count=1,CPU1和CPU2的Cache中的count都等于1,CPU1中的線程1對(duì)count進(jìn)行了自增操作,然后CPU1更新了內(nèi)存中count的值,并且通知CPU2 count的值已經(jīng)改變,然后CPU2從內(nèi)存中將count=2讀到了Cache中,并且線程2開始執(zhí)行count的自增操作,而就在CPU2剛剛將count的值讀回Cache的時(shí)候,CPU1又更新了count的值,此時(shí)count=3,并且通知CPU2,但是此時(shí)線程2已經(jīng)開始執(zhí)行了,線程2已經(jīng)將count=2拷貝到自己的內(nèi)存空間中了,所以即使CPU2再次更新自己Cache中的count=3,也來不及了,線程2操作的是他自己內(nèi)存空間中的count副本,所以線程2給count做完自增操作后,將count=3并且寫回Cache,CPU2更新內(nèi)存中的count。此時(shí)count的值應(yīng)該是4,然而CPU2更新完count的值后仍然等于3,這樣就出現(xiàn)了錯(cuò)誤。我們考慮的是只有兩顆CPU的情況,但是現(xiàn)在市面上已經(jīng)有8核手機(jī)了!如果8顆CPU同時(shí)工作的話,錯(cuò)誤會(huì)更嚴(yán)重!
Volatile一般情況下不能代替sychronized,因?yàn)関olatile不能保證操作的原子性,即使只是i++,實(shí)際上也是由多個(gè)原子操作組成:read i; inc; write i,假如多個(gè)線程同時(shí)執(zhí)行i++,volatile只 ? ?能保證他們操作的i是同一塊內(nèi)存,但依然可能出現(xiàn)寫入臟數(shù)據(jù)的情況。如果配合Java 5增加的atomic wrapper classes,對(duì)它們的increase之類的操作就不需要sychronized。
總結(jié)一下,volatile可以修飾變量,用于多線程的訪問,但是在多核的情況下,多線程并發(fā)還是存在問題。所以最好使用sychronized。
android volatile的使用就講完了。
就這么簡(jiǎn)單。
總結(jié)
以上是生活随笔為你收集整理的android volatile的使用的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: android 动态修改控件的宽高
- 下一篇: android edittext 不可编