1.volatile關(guān)鍵字: 當(dāng)把JVM設(shè)置為Server服務(wù)器的環(huán)境中苛败,線程會(huì)從自己的私有堆棧中讀取變量值满葛。這時(shí)如果其他線程更改了該變量,只是更改了公共堆棧罢屈,并沒(méi)有更改另一條線程的私有堆棧嘀韧。這樣就容易造成死循環(huán)狀態(tài)。
這個(gè)問(wèn)題其實(shí)就是私有堆棧中的值和公共堆棧中的值不同步造成的儡遮。解決這樣的問(wèn)題就要使用volatile關(guān)鍵字了乳蛾,它的主要作用就是當(dāng)線程訪問(wèn)變量時(shí),強(qiáng)制性從公共堆棧中進(jìn)行取值。
volatile和synchronized的比較:
1)關(guān)鍵字volatile是線程同步的輕量級(jí)實(shí)現(xiàn)肃叶,所以volatile性能肯定比synchronized要好蹂随,并且volatile只能修飾于變量,而synchronized可以修飾方法因惭,以及代碼塊岳锁。隨著JDK新版本的發(fā)布,synchronized關(guān)鍵字在執(zhí)行效率上得到很大提升蹦魔,在開(kāi)發(fā)中使用synchronized關(guān)鍵字的比率還是比較大的激率。
2)多線程訪問(wèn)volatile不會(huì)發(fā)生阻塞,而synchronized會(huì)出阻塞勿决。
3)volatile能保證數(shù)據(jù)可見(jiàn)性乒躺,但不能保證原子性;而synchronized可以保證原子性低缩,也可以間接保證可見(jiàn)性,因?yàn)樗鼤?huì)將私有內(nèi)存和公共內(nèi)存中的數(shù)據(jù)同步嘉冒。
關(guān)鍵字volatile雖然增加了實(shí)例變量在多個(gè)線程之間的可見(jiàn)性,但它卻不具備同步性咆繁,那么也就不具備原子性讳推。
public class MyThread extends Thread{
volatile public static int count;
private static void addCount(){
for(int i = 0;i<100;i++){
count++;
}
System.out.println("count=" + count);
}
@Override
public void run(){
addCount();
}
}
public static void main(String[] args){
MyThread[] mythreadArray = new MyThread[100];
for(int i = 0;i<100;i++){
mythreadArray[i] = new MyThread();
}
for(int i = 0;i<100;i++){
mythreadArray[i].start();
}
}
public class MyThread extends Thread{
volatile public static int count;
//注意一定要添加static關(guān)鍵字
//這樣synchronized與static鎖的內(nèi)容就是MyThread.class類了
//也就達(dá)到同步效果
synchronized private static void addCount(){
for(int i = 0;i<100;i++){
count++;
}
System.out.println("count=" + count);
}
@Override
public void run(){
addCount();
}
}
4)再次重申,關(guān)鍵字解決的是變量在多個(gè)線程之間的可見(jiàn)性玩般;而synchronized關(guān)鍵字解決的是多個(gè)線程之間訪問(wèn)資源的同步性银觅。
線程安全包含原子性和可見(jiàn)性兩個(gè)方面,Java的同步機(jī)制都是圍繞這兩個(gè)方面來(lái)確保線程安全的坏为。
但是volatile關(guān)鍵字最致命的缺點(diǎn)就是不支持原子性:關(guān)鍵字volatile雖然增加了實(shí)例變量在多個(gè)線程之間的可見(jiàn)性究驴,但它卻不具備同步性,那么不具備原子性匀伏。關(guān)鍵字volatile主要使用的場(chǎng)合是在多個(gè)線程中可以感知實(shí)例變量被更改了纳胧,并且可以獲得最新的值使用,也就是用多線程讀取共享變量時(shí)可以獲得最新值使用帘撰。
關(guān)鍵字volatile提示線程每次從共享內(nèi)存中讀取變量而不是從私有內(nèi)存中讀取,這樣就保證了同步數(shù)據(jù)的可見(jiàn)性万皿。但在這里需要注意的是:如果修改實(shí)例變量中的數(shù)據(jù)摧找,比如i++,也就是i=i+1牢硅,則這樣的操作其實(shí)并不是一個(gè)原子操作蹬耘,也就是非線程安全的。表達(dá)式i++的操作步驟分解如下:
1)從內(nèi)存中取出i的值减余。
2)計(jì)算i的值综苔。
3)將i的值寫(xiě)到內(nèi)存中。
假如在第2步計(jì)算值的時(shí)候,另外一個(gè)線程也修改i的值如筛,那么這個(gè)時(shí)候就會(huì)出現(xiàn)臟數(shù)據(jù)堡牡。解決的辦法其實(shí)就是使用synchronized關(guān)鍵字。所以說(shuō)volatile本身并不處理數(shù)據(jù)的原子性杨刨,而是強(qiáng)制對(duì)數(shù)據(jù)的讀寫(xiě)及時(shí)影響到主內(nèi)存的晤柄。
1)read和load階段:從主內(nèi)存復(fù)制變量到當(dāng)前線程工作內(nèi)存;
2)use和assign階段:執(zhí)行代碼妖胀,改變共享變量值芥颈;
3)store和write階段:用工作內(nèi)存數(shù)據(jù)刷新主內(nèi)存對(duì)應(yīng)變量的值。
多線程環(huán)境中赚抡,use和assign是多次出現(xiàn)的爬坑,但這一操作并不是原子性,也就是在read和load之后涂臣,如果主內(nèi)存count變量發(fā)生修改之后盾计,線程工作內(nèi)存中的值由于已經(jīng)加載,不會(huì)產(chǎn)生對(duì)應(yīng)變化肉康,也就是私有內(nèi)存和公共內(nèi)存中的變量不同步闯估,所以計(jì)算出來(lái)的結(jié)果會(huì)和預(yù)期的不一樣,也就出現(xiàn)了非線程安全問(wèn)題吼和。
對(duì)于volatile修飾的變量涨薪,JVM虛擬機(jī)只是保證從主內(nèi)存加載到線程工作內(nèi)存的值是最新的,例如線程1和線程2在進(jìn)行read和load的操作中炫乓,發(fā)現(xiàn)主內(nèi)存中count的值都是5刚夺,那么都加載這個(gè)最新的值。也就是說(shuō)末捣,volatile關(guān)鍵字解決的是變量讀時(shí)的可見(jiàn)性問(wèn)題侠姑,但無(wú)法保證原子性,對(duì)于多個(gè)線程訪問(wèn)一個(gè)實(shí)例變量還是需要加鎖同步箩做。
2.原子類: 除了在i++操作時(shí)使用synchronized關(guān)鍵字實(shí)現(xiàn)同步之外莽红,還可以使用AtomicInteger原子類進(jìn)行實(shí)現(xiàn)。
原子操作是不能分割的整體邦邦,沒(méi)有其他線程能夠中斷或檢查正在原子操作中的變量安吁。一個(gè)原子類型就是一個(gè)原子操作可用的類型,它在沒(méi)有鎖的情況下做到線程安全燃辖。
但是原子類的方法和方法之間的調(diào)用卻不是原子的鬼店,這樣可以加synchronized關(guān)鍵字來(lái)同步不同的addAndGet()方法。
3.synchronized代碼塊有volatile同步功能:關(guān)鍵字synchronized可以使多個(gè)線程訪問(wèn)同一個(gè)資源具有同步性黔龟,而且它還具有將線程工作內(nèi)存中的私有變量與公共內(nèi)存中的變量同步功能妇智。關(guān)鍵字synchronized可以保證在同一時(shí)刻滥玷,只有一個(gè)線程可以執(zhí)行某一個(gè)方法或某一個(gè)代碼塊。它包含兩個(gè)特征:互斥性和可見(jiàn)性巍棱。同步synchronized不僅可以解決一個(gè)線程看到對(duì)象處于不一致的狀態(tài)惑畴,還可以保證進(jìn)入同步方法或者同步代碼塊的每個(gè)線程,都看到由一個(gè)鎖保護(hù)之前所有的修改效果拉盾。