1、概念
進(jìn)程和線程都是一個(gè)時(shí)間段的描述急波,是CPU工作時(shí)間段的描述。兩者顆粒度不同瘪校。
進(jìn)程是CPU資源分配的最小單位澄暮,可以理解為一個(gè)應(yīng)用程序。
線程是CPU調(diào)度的最小單位阱扬,是建立在進(jìn)程的基礎(chǔ)上的一次程序運(yùn)行單位泣懊。
2、三個(gè)核心
原子性
一個(gè)操作麻惶,要么全部執(zhí)行馍刮,要不么全部不執(zhí)行。
簡(jiǎn)單的說(shuō)窃蹋,就是在一個(gè)線程對(duì)共享變量進(jìn)行操作時(shí)卡啰,阻塞其他線程對(duì)該變量的操作。
可見(jiàn)性
當(dāng)線程操作某個(gè)變量時(shí)警没,順位為:
1匈辱、將變量從主內(nèi)存拷貝到工作內(nèi)存中。
2杀迹、執(zhí)行代碼亡脸,操作共享變量值。
3树酪、將工作內(nèi)存的數(shù)據(jù)刷新到主內(nèi)存中浅碾。
多個(gè)線程并發(fā)訪問(wèn)共享變量時(shí),一個(gè)線程對(duì)共享變量的操作嗅回,其他線程能夠立刻看到及穗。
每個(gè)線程讀取共享變量時(shí),都會(huì)將該變量加載進(jìn)其對(duì)應(yīng)CPU的高速緩存里绵载,修改該變量后埂陆,CPU會(huì)立即更新該緩存苛白,但并不一定會(huì)立即將其寫(xiě)回主內(nèi)存(實(shí)際上寫(xiě)回主內(nèi)存的時(shí)間不可預(yù)期)。此時(shí)其它線程(尤其是不在同一個(gè)CPU上執(zhí)行的線程)訪問(wèn)該變量時(shí)焚虱,從主內(nèi)存中讀到的就是舊的數(shù)據(jù)购裙,而非第一個(gè)線程更新后的數(shù)據(jù)。
順序性
程序的執(zhí)行順序按照代碼的先后順序執(zhí)行鹃栽。
int a,b;
a++;
b++;
if(b==1){
print(a);
}
在理想情況下躏率,當(dāng)b=1時(shí),a=1,但是實(shí)際情況中民鼓,JVM在執(zhí)行代碼的過(guò)程中薇芝,并不一定按照代碼的順序執(zhí)行,有可能先執(zhí)行b++,后執(zhí)行a++丰嘉。
happens-before 原則
1夯到、程序次序規(guī)則:一個(gè)線程內(nèi),按照代碼順序饮亏,書(shū)寫(xiě)在前面的操作先行發(fā)生于書(shū)寫(xiě)在后面的操作耍贾。
2、鎖定規(guī)則:一個(gè)unlock操作一定發(fā)生在lock操作之前路幸。
3荐开、volatile變量規(guī)則:對(duì)一個(gè)變量的寫(xiě)操作先行發(fā)生于后面的讀操作。
4简肴、傳遞規(guī)則:如果操作A先行發(fā)生于操作B晃听,而操作B又先行發(fā)生于操作C,則可以得出操作A先行發(fā)生于操作C着帽。
5杂伟、線程啟動(dòng)規(guī)則:Thread對(duì)象的所有操作都發(fā)生在start()之后。
6仍翰、線程中斷規(guī)則:線程interrupt()方法的調(diào)用先行發(fā)生于被中斷線程的代碼檢測(cè)到中斷事件的發(fā)生。
7观话、線程終結(jié)規(guī)則:線程中所有的操作都先行發(fā)生于線程的終止檢測(cè)予借。
8、對(duì)象終結(jié)規(guī)則:一個(gè)對(duì)象的初始化完成先行發(fā)生于他的finalize()方法的開(kāi)始频蛔。
對(duì)于程序次序規(guī)則灵迫,應(yīng)該理解為jvm保證最終執(zhí)行的結(jié)果與程序順序執(zhí)行的結(jié)果一致。jvm有可能對(duì)不存在數(shù)據(jù)依賴性的指令進(jìn)行重排序晦溪。實(shí)際上瀑粥,這個(gè)規(guī)則是用來(lái)保證單線程中執(zhí)行結(jié)果的正確性,但無(wú)法保證程序在多線程中執(zhí)行的正確性三圆。
3狞换、線程狀態(tài)
- INIT : 線程對(duì)象進(jìn)行new初始化后避咆,此時(shí)還未調(diào)用start()。
- NEW : 線程對(duì)象調(diào)用start()方法后修噪,進(jìn)去可運(yùn)行狀態(tài)查库。如果處于RUNABLED狀態(tài)的線程調(diào)用yield()后,會(huì)釋放占用的資源黄琼,重新進(jìn)入NEW狀態(tài)樊销。
- RUNABLED : 線程獲取到CPU時(shí)間片,進(jìn)入運(yùn)行狀態(tài)脏款。
- BLOCKED : 線程調(diào)用sleep()或者join()方法后围苫,進(jìn)去阻塞狀態(tài),此時(shí)線程不釋放所占有的系統(tǒng)資源撤师。當(dāng)sleep()結(jié)束或者join()等到其他線程到來(lái)够吩,當(dāng)前線程進(jìn)入RUNABLED狀態(tài)。
- TIME WAITING : 線程進(jìn)入到RUNABLED狀態(tài)丈氓,還未開(kāi)始運(yùn)行的時(shí)候周循,發(fā)現(xiàn)要獲取的資源處于同步狀態(tài),該線程就會(huì)進(jìn)入TIME WAITING狀態(tài)万俗,等待資源釋放湾笛;當(dāng)前線程使用wait()方法后,進(jìn)入TIME WAITING狀態(tài)闰歪,只有在獲得notify()或者notifyAll()通知后嚎研,才會(huì)進(jìn)入WAITING狀態(tài)。
4库倘、關(guān)鍵字
synchronized
當(dāng)synchronized修飾一個(gè)方法或者代碼塊的時(shí)候临扮,保證同時(shí)只有一個(gè)線程可以訪問(wèn)該方法或代碼塊。保證了線程的執(zhí)行順序性和可見(jiàn)性教翩。
synchronized(鎖){
臨界區(qū)代碼
}
public void synchronized method(){
方法體
}
synchronized修飾代碼塊杆勇,鎖就是這個(gè)對(duì)象;
synchronized修飾方法饱亿,鎖就是這個(gè)class蚜退。
理論上所有對(duì)象都可以成為鎖,但是能被多個(gè)線程共享的鎖才有意義彪笼。
每個(gè)鎖對(duì)象有兩個(gè)隊(duì)列钻注,一個(gè)是就緒隊(duì)列,一個(gè)是阻塞隊(duì)列配猫。就緒隊(duì)列存儲(chǔ)了即將獲取鎖的線程幅恋,阻塞隊(duì)列存儲(chǔ)被阻塞的線程。
java內(nèi)置鎖是可重入鎖泵肄,子類(lèi)可以獲得父類(lèi)的鎖資源捆交。
synchronized是一種悲觀鎖淑翼,會(huì)導(dǎo)致其他所有需要鎖的線程掛起,等待持有鎖的線程釋放鎖零渐。這樣的鎖對(duì)性能不夠友好窒舟。
volatile
volatile關(guān)鍵字可以保證可見(jiàn)性,當(dāng)使用volatile來(lái)修飾某個(gè)共享變量時(shí)诵盼,會(huì)保證該變量的修改會(huì)立刻更新到主內(nèi)存中惠豺,并且將其他緩存中對(duì)該變量的緩存設(shè)置為無(wú)效,其他線程需要重新從主內(nèi)存讀取該變量风宁。
volatile關(guān)鍵字可以禁止進(jìn)行指令重排序洁墙。
單線程下
x = 1; //語(yǔ)句1
y=0; //語(yǔ)句2
volatile flag = true; //語(yǔ)句3
x = 2; //語(yǔ)句4
y = 4; //語(yǔ)句5
使用volatile修飾flag后,jvm在進(jìn)行指令重排序時(shí)戒财,不會(huì)將語(yǔ)句4热监,5放在語(yǔ)句3之前,也不會(huì)將語(yǔ)句1饮寞,2放在語(yǔ)句3之后孝扛。
多線程下
//線程1
object = loadObject(); //語(yǔ)句1
init = true; //語(yǔ)句2
//線程2
while(!init){
...
}
doSomething(object);
在多線程的情況下,線程1有可能先執(zhí)行語(yǔ)句2幽崩,假如此時(shí)線程1進(jìn)入阻塞苦始,線程2開(kāi)始執(zhí)行,但此時(shí)語(yǔ)句1還沒(méi)執(zhí)行慌申,obejct沒(méi)有被初始化陌选,導(dǎo)致程序出錯(cuò)。
這里用volatile修飾init蹄溉,可以保證語(yǔ)句1先執(zhí)行咨油。
原理
加入volatile關(guān)鍵字時(shí),會(huì)多出一個(gè)lock前綴指令柒爵。
lock前綴指令相當(dāng)于一個(gè)內(nèi)存屏障役电,有3個(gè)功能:
(1)、確保指令重排序時(shí)不會(huì)把內(nèi)存屏障之后的指令排在內(nèi)存屏障之前餐弱,也不會(huì)把內(nèi)存屏障之前的指令排在內(nèi)存屏障之后宴霸。
(2)、強(qiáng)制將對(duì)緩存的修改操作立即寫(xiě)入主內(nèi)存膏蚓。
(3)、如果是寫(xiě)操作畸写,會(huì)導(dǎo)致其他CPU中對(duì)應(yīng)的緩存行無(wú)效驮瞧。
Lock
java.util.concurrent.lock中的lock框架是鎖定的一個(gè)抽象,它允許把鎖定的實(shí)現(xiàn)作為java類(lèi)枯芬。它擁有與synchronized相同的并發(fā)性和內(nèi)存語(yǔ)義论笔,但是添加了類(lèi)似鎖投票采郎、定時(shí)鎖等候和中斷鎖等候的一些特性。此外狂魔,它在激烈爭(zhēng)用的情況下具有更加的性能蒜埋。
不要忘了在finally中釋放lock
讀寫(xiě)鎖
ReentrantReadWriteLock
悲觀鎖和樂(lè)觀鎖
共享鎖和排他鎖
CAS
Compare And Swape,比較并交換最楷。目前CAS被廣泛應(yīng)用于硬件層面的并發(fā)操作整份。
樂(lè)觀鎖的機(jī)制就是CAS,樂(lè)觀鎖就是每次不加鎖籽孙,假設(shè)沒(méi)有沖突的去完成某項(xiàng)操作烈评,如果因?yàn)闆_突失敗就重試,直到成功為止犯建。
CAS操作包含三個(gè)操作數(shù)--內(nèi)存位置V讲冠,預(yù)期原值A(chǔ)和新值B。如果內(nèi)存位置的值與預(yù)期原值匹配适瓦,那么將該位置替換為新值竿开。否則,處理器不作任何處理玻熙。
利用CPU的CAS指令否彩,同時(shí)借助JNI來(lái)完成JAVA的非阻塞算法。其他原子操作都是利用類(lèi)似的特性完成的揭芍。而整個(gè)JUC都是建立在CAS的基礎(chǔ)上的胳搞。
缺點(diǎn)
CAS雖然具有很高效的原子操作,但是CAS仍然存在三大問(wèn)題称杨。
(1)肌毅、ABA問(wèn)題。如果一個(gè)值原來(lái)是A姑原,變成了B悬而,又變成了A,那么使用CAS檢查時(shí)會(huì)認(rèn)為它的值沒(méi)有發(fā)生變化锭汛,但是實(shí)際上發(fā)生了變化笨奠。解決思路就是加版本號(hào),1A-2B-3A唤殴。
(2)般婆、循環(huán)時(shí)間長(zhǎng)開(kāi)銷(xiāo)大。CAS是自旋鎖朵逝,如果長(zhǎng)時(shí)間不成功蔚袍,會(huì)給CPU帶來(lái)非常大的執(zhí)行開(kāi)銷(xiāo)。
(3)、只能保證一個(gè)共享變量的原子操作啤咽。
Double-Check
在單例模式的懶漢式中晋辆,存在雙重檢查,這種方式在多線程中是不安全的宇整。
public Singleton getResource(){
if (resource == null){ //語(yǔ)句1
synchronized(this){
if (resource == null) { //語(yǔ)句2
resource = new Singleton(); //語(yǔ)句3
}
}
}
return resource;
}
假設(shè)線程1執(zhí)行到了語(yǔ)句1瓶佳,執(zhí)行了new Resource()指令,但是還未給resource賦值鳞青,此時(shí)線程1阻塞霸饲,線程2開(kāi)始執(zhí)行,在判斷resource == null是盼玄,因?yàn)橐呀?jīng)分配了內(nèi)存空間(未賦值)贴彼,該語(yǔ)句為false,就返回了未完成初始化的resource埃儿,造成程序錯(cuò)誤器仗。
改進(jìn)方法
(1)、在方法上加synchronized童番。
public synchronized Singleton getResource(){
if (resource == null){
resource = new Singleton();
}
return resource;
}
(2)精钮、使用volatile
private valotile Singleton resource = null;
public synchronized Singleton getResource(){
if (resource == null){
resource = new Singleton();
}
return resource;
}