序言:*牢騷*
? ?相信大家也了解過OKhttp以及AsyncTask源碼搭盾,他們在線程池以及一些共享變量的操作都使用了一個(gè)關(guān)鍵字--》Volatile咳秉,對此我也是一知半解,我們在之前所了解的它是一個(gè)對線程保持可見性的功能定義鸯隅,卻不能深刻了解其含義與使用場景澜建。作為一直在公司忙于項(xiàng)目進(jìn)度的我,也是甚是羞愧滋迈,所以今天也是參考了一些大神的博客了解了一下霎奢。
為什么要使用Volatile關(guān)鍵字?可能有人會(huì)說別人那么用饼灿,照搬嘍幕侠,WTF,其實(shí)很簡單就是為了保證并發(fā)編程的安全性碍彭,那問題又來了晤硕,怎么保證并發(fā)編程訪問共享變量的安全性呢,那么我們就要從Java內(nèi)存模型來了解庇忌。
Java內(nèi)存模型:
1舞箍、所有的變量都存儲(chǔ)在主內(nèi)存中。
2皆疹、不同的線程都有自己的工作內(nèi)存疏橄。
3、不同線程對變量的操作必須在工作內(nèi)存中進(jìn)行略就。
4捎迫、不同線程間無法訪問對方的變量,所有線程間的數(shù)據(jù)傳遞必須在主內(nèi)存中進(jìn)行表牢。
舉個(gè)簡單的小例子:
線程A
int x = 10;
x=12;
線程B
x++;
y=x;
大家認(rèn)為如果多個(gè)線程操作共享變量x窄绒,線程A和B輸出x和y的值是多少,對A輸出的x=12崔兴,而B輸出的y=10;那這是為什么呢彰导?因?yàn)槎嗑€程操作x的時(shí)候A從主內(nèi)存中Copy了x的值蛔翅,然后執(zhí)行賦值,然而這個(gè)時(shí)候并沒有將數(shù)據(jù)寫入到主內(nèi)存中,B線程重新再主內(nèi)存Copy了一份原始值接著進(jìn)行賦值操作位谋,在這里只有int x =10是具有原子性山析,就是基本數(shù)據(jù)直接賦值的屬于原子性操作,變量賦值變量就不具備原子性倔幼。基于此種內(nèi)存模型盖腿,便產(chǎn)生了多線程中的數(shù)據(jù)“臟讀”問題。這也是著名的緩存一致性問題又稱為共享變量损同。
如何保證共享變量安全性呢翩腐,這里就涉及到了并發(fā)編程的三大概念:原子性、有序性膏燃、可見性茂卦。
● 原子性:一個(gè)或多個(gè)操作,要么全部成功组哩,要么全部失敗等龙,類似于我們數(shù)據(jù)庫事務(wù)
●可見性:多個(gè)線程訪問一個(gè)共享變量,當(dāng)一個(gè)線程訪問這個(gè)變量時(shí)伶贰,其他的線程應(yīng)該是立刻能得知這個(gè)變量最新修改的值蛛砰。
? ? ? ①由于可見性對共享變量帶來的數(shù)據(jù)安全問題,Java官方也推薦使用Volatile修飾共享變量,保證被修改的值能立即刷新到主內(nèi)存黍衙。
? ? ? ? 需要注意的是:普通變量 無法保證可見性
●有序性:
? ? ?指令重排序泥畅??什么鬼琅翻,不要慌位仁,其實(shí)就是系統(tǒng)為了優(yōu)化代碼而進(jìn)行的順序排序
? ? ? 定義:處理器為了提高程序運(yùn)行效率,可能會(huì)對輸入代碼進(jìn)行優(yōu)化方椎,它不保證程序中各個(gè)語句的執(zhí)行先后順序同代碼中的順序一致聂抢,但是他會(huì)保證最終執(zhí)行結(jié)果和代碼順序執(zhí)行的結(jié)果是一致的。
核心:處理器在進(jìn)行重哦愛須是會(huì)考慮指令之間的數(shù)據(jù)依賴性棠众。
指令重排序不會(huì)影響單個(gè)線程的執(zhí)行琳疏,但是會(huì)影響并發(fā)線程執(zhí)行的正確性。
并發(fā)線程必須保證原子性闸拿,可見性以及有序性轿亮。還要保證happens-before原則(先行發(fā)生原則)大家可以自己去了解一下,很簡單的胸墙。
延伸:Volatile、Synchronized和ReentrantLock
Synchronized:同步代碼塊和同步方法《Synchronized的用法》
ReentrantLock:是一個(gè)接口
viod lock():線程執(zhí)行此方法按咒,如果鎖空閑迟隅,當(dāng)前線程就會(huì)獲取鎖但骨,反之就會(huì)禁用該線程一直處于等待狀態(tài),直到鎖釋放智袭。
void tryLock():只是嘗試獲取鎖奔缠,并不會(huì)導(dǎo)致當(dāng)前線程被禁用。
void unLock():必須由持有者釋放吼野,反之如果線程不持有執(zhí)行該方法可能導(dǎo)致異常校哎。
概念:重入鎖與公平鎖
所謂的重入鎖就是自己獲取自己的鎖以后再次獲取自己內(nèi)部的鎖。
公平鎖就是cpu在調(diào)度線程是在等待隊(duì)列里隨機(jī)挑選一個(gè)線程去執(zhí)行瞳步,那么優(yōu)先級低的可能就會(huì)一直無法獲取就會(huì)發(fā)生饑餓現(xiàn)象闷哆。
ReentrantLock通過構(gòu)造器傳入true或者false區(qū)分公不公平
Synchronized與ReentrantLock區(qū)別:
①Lock是一個(gè)接口,而Synchronized是Java關(guān)鍵字
②Synchronized發(fā)生異常時(shí)會(huì)自動(dòng)釋放線程占有鎖不會(huì)死鎖单起,而lock發(fā)生異常時(shí)抱怔,如果用戶沒有主動(dòng)通過Unlock去釋放鎖,很可能造成死鎖嘀倒。
③lock可以知道有沒有成功獲取鎖屈留,而Synchronized不可以。
Java1.5時(shí)测蘑,Synchronized是性能低耗的灌危,操作都需要轉(zhuǎn)入內(nèi)核去操作。
Java1.6時(shí)碳胳,Synchronized優(yōu)化了大量的性能勇蝙,而官方也提倡在Synchronized能實(shí)現(xiàn)需求下,優(yōu)先使用Synchronized固逗。
最后感謝本文博文作者Ruheng的文章你真的了解Volatile嗎
大家可以參考一下浅蚪,謝謝,輕噴