【java面試】java中,volatile和synchronized的區(qū)別
volatile
它所修飾的變量不保留拷貝,直接訪問主內(nèi)存中的變量。
在Java內(nèi)存模型中,有main memory,每個線程也有自己的memory (例如寄存器)。為了性能,一個線程會在自己的memory中保持要訪問的變量的副本。這樣就會出現(xiàn)同一個變量在某個瞬間,在一個線程的memory中的值可能與另外一個線程memory中的值,或者main memory中的值不一致的情況。 一個變量聲明為volatile,就意味著這個變量是隨時會被其他線程修改的,因此不能將它隱藏在線程memory中。
使用場景
您只能在有限的一些情形下使用 volatile 變量替代鎖。要使 volatile 變量提供理想的線程安全,必須同時滿足下面兩個條件:
1)對變量的寫操作不依賴于當(dāng)前值。
2)該變量沒有包含在具有其他變量的不變式中。
volatile最適用一個線程寫,多個線程讀的場合。
如果有多個線程并發(fā)寫操作,仍然需要使用鎖或者線程安全的容器或者原子變量來代替。
synchronized
當(dāng)它用來修飾一個方法或者一個代碼塊的時候,能夠保證在同一時刻最多只有一個線程執(zhí)行該段代碼。
當(dāng)兩個并發(fā)線程訪問同一個對象object中的這個synchronized(this)同步代碼塊時,一個時間內(nèi)只能有一個線程得到執(zhí)行。另一個線程必須等待當(dāng)前線程執(zhí)行完這個代碼塊以后才能執(zhí)行該代碼塊。
然而,當(dāng)一個線程訪問object的一個synchronized(this)同步代碼塊時,另一個線程仍然可以訪問該object中的非synchronized(this)同步代碼塊。
尤其關(guān)鍵的是,當(dāng)一個線程訪問object的一個synchronized(this)同步代碼塊時,其他線程對object中所有其它synchronized(this)同步代碼塊的訪問將被阻塞。
當(dāng)一個線程訪問object的一個synchronized(this)同步代碼塊時,它就獲得了這個object的對象鎖。結(jié)果,其它線程對該object對象所有同步代碼部分的訪問都被暫時阻塞。
volatile和synchronized
volatile是變量修飾符,而synchronized則作用于一段代碼或方法。
volatile只是在線程內(nèi)存和“主”內(nèi)存間同步某個變量的值;而synchronized通過鎖定和解鎖某個監(jiān)視器同步所有變量的值, 顯然synchronized要比volatile消耗更多資源。
volatile不會造成線程的阻塞;synchronized可能會造成線程的阻塞。
volatile保證數(shù)據(jù)的可見性,但不能保證原子性;而synchronized可以保證原子性,也可以間接保證可見性,因為它會將私有內(nèi)存中和公共內(nèi)存中的數(shù)據(jù)做同步。
volatile標(biāo)記的變量不會被編譯器優(yōu)化;synchronized標(biāo)記的變量可以被編譯器優(yōu)化。
線程安全包含原子性和可見性兩個方面,Java的同步機制都是圍繞這兩個方面來確保線程安全的。
關(guān)鍵字volatile主要使用的場合是在多個線程中可以感知實例變量被修改,并且可以獲得最新的值使用,也就是多線程讀取共享變量時可以獲得最新值使用。
關(guān)鍵字volatile提示線程每次從共享內(nèi)存中讀取變量,而不是私有內(nèi)存中讀取,這樣就保證了同步數(shù)據(jù)的可見性。但是要注意的是:如果修改實例變量中的數(shù)據(jù)
例如:i++,也就是i=i+1,則這樣的操作其實并不是一個原子操作,也就是非線程安全的。表達(dá)式i++操作步驟分解如下:
1)從內(nèi)存中取出i的值。
2)計算i的值;
3)將i的值寫到內(nèi)存中。
假如在第2步計算值得時候,另外一個線程也修改i的值,name這個時候就會出現(xiàn)臟讀數(shù)據(jù)。解決的辦法就是使用synchronized關(guān)鍵字。?所以說volatile本身并不處理數(shù)據(jù)的原子性,而是強制對數(shù)據(jù)的讀寫及時的影響到主內(nèi)存中。