每個(gè)線程都有自己的執(zhí)行空間(即工作內(nèi)存)粱甫,線程執(zhí)行的時(shí)候用到某變量搂漠,首先要將變量從主內(nèi)存拷貝的自己的工作內(nèi)存空間袱结,然后對(duì)變量進(jìn)行操作:讀取亮隙,修改,賦值等垢夹,這些均在工作內(nèi)存完成溢吻,操作完成后再將變量寫回主內(nèi)存。當(dāng)多個(gè)線程同時(shí)讀寫某個(gè)內(nèi)存數(shù)據(jù)時(shí)果元,就會(huì)涉及到線程并發(fā)的問(wèn)題促王。涉及到三個(gè)特 性:原子性,有序性而晒,可見(jiàn)性蝇狼。
簡(jiǎn)單說(shuō)下這個(gè)三個(gè)特性的概念:
switch(線程特性){
case (可見(jiàn)性):
一個(gè)數(shù)據(jù)在多個(gè)線程中都存在副本的時(shí)候,任何一個(gè)線程對(duì)共享變量的修改欣硼,其它線程都應(yīng)該看到被修改之后的值题翰。
break;
case(有序性):
線程的有序性即按代碼的先后順序執(zhí)行恶阴。很經(jīng)典的就是銀行的存錢取錢問(wèn)題,比如A線程負(fù)責(zé)取錢豹障,B線程負(fù)責(zé)取錢冯事,賬戶里面有100塊,這時(shí)候B和A都讀取了賬戶余額血公,100塊昵仅,這時(shí)B取出了10塊,寫入主內(nèi)存后這時(shí)候賬戶還有90塊累魔,但A知道的是100塊然后存了10塊摔笤,再寫入主內(nèi)存就是110塊,這顯然是不對(duì)的垦写,沒(méi)有保存線程的有序性吕世。
break;
case ( 原子性):
原子性是指一個(gè)操作是不可中斷的。即使是在多個(gè)線程一起執(zhí)行的時(shí)候梯投,一個(gè)操作一旦開始命辖,就不會(huì)被其它線程干擾。
Java中的原子操作包括:
1)除long和double之外的基本類型的賦值操作
2)所有引用reference的賦值操作
3)java.concurrent.Atomic.* 包中所有類的一切操作分蓖。
線程之間的交互只能通過(guò)共享數(shù)據(jù)來(lái)實(shí)現(xiàn)尔艇,而不能直接傳遞數(shù)據(jù)。
同步是為了解決多個(gè)線程對(duì)共享數(shù)據(jù)的訪問(wèn)和操作混亂達(dá)不到預(yù)期效果這種情況而引入的機(jī)制么鹤。
break;
}
Synchronized
看如下代碼:在main方法中:
for (int index = 0; index < 3; index++) {
new Thread() {
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace(); }
incTestNum(); }
} }.start();
}
new Thread() {
@Override
public void run() {
for (int i = 0; i < 300; i++) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace(); }
getTestNum(); }
}}.start();
private static void incTestNum() {
i++; j++;}
private static void getTestNum() {
System.out.println("i===========" + i + ";j============" + j);}
我們得到 的結(jié)果是:
i===========63;j============63
i===========93;j============93
i===========121;j============122
i===========151;j============152
i===========180;j============182
i===========210;j============212
i===========240;j============242
i===========267;j============270
可以看到j(luò)有可能會(huì)比i大终娃,這是在多線程并發(fā)操作i和j 的時(shí)候,并沒(méi)有同步線程蒸甜,此時(shí)同時(shí)操作i和j不具有原子性棠耕。并且i++本身也不是原子操作,先讀取i,再執(zhí)行i+1;然后再賦值給i;然后再寫入內(nèi)存迅皇。但直觀來(lái)說(shuō)應(yīng)該i比j大昧辽,這是應(yīng)該在讀取i之后和讀取j之前加操作又執(zhí)行了多次。導(dǎo)致看到的j比i大登颓。
當(dāng)我們加上在線程里面加上synchronized之后:
private synchronized static void incTestNum() {
i++; j++;}
private synchronized static void getTestNum() {
System.out.println("i===========" + i + ";j============" + j);}
結(jié)果:
i===========92;j============92
i===========121;j============121
i===========150;j============150
i===========182;j============182
i===========209;j============209
i===========240;j============240
i===========269;j============269
......
i===========3000;j============3000
從結(jié)果可以看出搅荞,用synchronized鎖住的方法是同步執(zhí)行的,并且得到了正確的結(jié)果框咙。
使用synchronized修飾的方法或者代碼塊可以看成是一個(gè)原子操作咕痛。如果一個(gè)線程獲取了鎖,其它線程需要獲取鎖來(lái)執(zhí)行的時(shí)候喇嘱,其它線程就進(jìn)入了等待鎖的釋放茉贡。這個(gè)過(guò)程是阻塞的。
一個(gè)線程執(zhí)行互斥代碼過(guò)程如下:
- 獲得同步鎖者铜;
- 清空工作內(nèi)存腔丧;
- 從主內(nèi)存拷貝對(duì)象副本到工作內(nèi)存放椰;
- 執(zhí)行代碼(計(jì)算或者輸出等);
- 刷新主內(nèi)存數(shù)據(jù)愉粤;
- 釋放同步鎖砾医。
所以,synchronized既保證了多線程的并發(fā)有序性衣厘,又保證了多線程的內(nèi)存可見(jiàn)性如蚜。
如果在靜態(tài)方法上加synchronized,那么作用等同于:
void method{
synchronized(Obl.class)
}
}
既然要同步我們就要用線程之間的同享對(duì)象作為鎖,所以下面方式是錯(cuò)誤的使用:
Object lock=new Object();
synchronized(lock){
}
使用同步方法的時(shí)候:
private synchronized void test(){ }
等價(jià)于:
private void test(){
synchronized(this){
}
}
jdK1.5之后影暴,對(duì)synchronized同步機(jī)制做了很多優(yōu)化错邦,如:自適應(yīng)的自旋鎖、鎖粗化型宙、鎖消除撬呢、輕量級(jí)鎖等,使得它的性能明顯有了很大的提升妆兑。
volatile
關(guān)于volatile的實(shí)現(xiàn)原理可以看看這篇文章:
深入分析Volatile的實(shí)現(xiàn)原理
volatile告訴jvm倾芝, 它所修飾的變量不保留拷貝,直接訪問(wèn)主內(nèi)存中的箭跳。這就保證了可見(jiàn)性。
我們?cè)谏厦鎮(zhèn)€例子的共享變量加上volatile關(guān)鍵:
static volatile int i = 0, j = 0;
再來(lái)看看運(yùn)行結(jié)果:
i===========241;j============241
i===========272;j============271
i===========301;j============301
i===========329;j============330
i===========359;j============360
i===========390;j============390
......
i===========2984;j============2993
這看起來(lái)加了volatile和沒(méi)有加是一樣的效果潭千,看起來(lái)線程都沒(méi)有同步谱姓。原因是volatile不能保證操作的原子性。也就不能保證i++和j++的原子性刨晴,當(dāng)A屉来,B線程讀取i的值假設(shè)此時(shí)i=10,然后A線程執(zhí)行+1再寫入,刷入主內(nèi)存中狈癞,此時(shí)主內(nèi)存i的值是11茄靠,然后現(xiàn)在B線程再執(zhí)行+1寫入主內(nèi)存,此時(shí)主內(nèi)存中i的值被還是11蝶桶,但此時(shí)正常情況應(yīng)該是12 的慨绳。這就是為什么最后的結(jié)果i和j都比3000小。
聲明為volatile的簡(jiǎn)單變量如果當(dāng)前值與該變量以前的值相關(guān)真竖,那么volatile關(guān)鍵字不起作用脐雪。如i++;i=i+1;
當(dāng)且僅當(dāng)滿足以下所有條件時(shí),才應(yīng)該使用volatile變量:
- 對(duì)變量的寫入操作不依賴變量的當(dāng)前值恢共,或者你能確保只有單個(gè)線程更新變量的值战秋。
- 該變量沒(méi)有包含在具有其他變量的不變式中。
- 訪問(wèn)變量不需要加鎖
通常使用在如下情況:
static class StopTester implements Runnable {
private boolean stop = false;
public void stopMe() {
stop=true;
}
@Override
public void run() {
while(!stop){
//TODO
}
}
}
volatile與synchronized的區(qū)別
- volatile修飾的變量存取時(shí)比一般變量消耗的資源要多一點(diǎn)讨韭,因?yàn)榫€程有它自己的變量拷貝更為高效脂信。
- volatile本質(zhì)是在告訴jvm當(dāng)前變量在寄存器中的值是不確定的,需要從主存中讀取,synchronized則是鎖定當(dāng)前變量,只有當(dāng)前線程可以訪問(wèn)該變量,其他線程被阻塞住.
- volatile僅能使用在變量級(jí)別,synchronized則可以使用在變量,方法.
- volatile僅能實(shí)現(xiàn)變量的修改可見(jiàn)性,但不具備原子特性,而synchronized則可以保證變量的修改可見(jiàn)性和原子性.
- volatile不會(huì)造成線程的阻塞,而synchronized可能會(huì)造成線程的阻塞.
- volatile標(biāo)記的變量不會(huì)被編譯器優(yōu)化,而synchronized標(biāo)記的變量可以被編譯器優(yōu)化.