溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

volatile和synchronize有哪些區(qū)別

發(fā)布時間:2020-11-20 09:46:56 來源:億速云 閱讀:401 作者:小新 欄目:編程語言

小編給大家分享一下volatile和synchronize有哪些區(qū)別,相信大部分人都還不怎么了解,因此分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后大有收獲,下面讓我們一起去了解一下吧!

區(qū)別:1、volatile不會造成線程的阻塞;synchronized可能會造成線程的阻塞。2、volatile保證數(shù)據(jù)的可見性,但不能保證原子性;而synchronized可以保證原子性,也可以間接保證可見性。

可見性(visibility)

可見性:一個線程對共享變量做了修改之后,其他的線程立即能夠看到(感知到)該變量這種修改(變化)。
Java內(nèi)存模型是通過將在工作內(nèi)存中的變量修改后的值同步到主內(nèi)存,在讀取變量前從主內(nèi)存刷新最新值到工作內(nèi)存中,這種依賴主內(nèi)存的方式來實(shí)現(xiàn)可見性的。

原子性(atomicity)

原子性:一個操作不能被打斷,要么全部執(zhí)行完畢,要么不執(zhí)行。
java內(nèi)存模型所保證的是,同線程內(nèi),所有的操作都是由上到下的,但是多個線程并行的情況下,則不能保證其操作的有序性。

有序性

有序性:在本線程內(nèi)觀察,操作都是有序的;如果在一個線程中觀察另外一個線程,所有的操作都是無序的。

java內(nèi)存模型所保證的是,同線程內(nèi),所有的操作都是由上到下的,但是多個線程并行的情況下,則不能保證其操作的有序性。
計(jì)算機(jī)在執(zhí)行程序時,為了提高性能,編譯器個處理器常常會對指令做重排,一般分為以下 3 種
volatile和synchronize有哪些區(qū)別
單線程環(huán)境里面確保程序最終執(zhí)行的結(jié)果和代碼執(zhí)行的結(jié)果一致
處理器在進(jìn)行重排序時必須考慮指令之間的數(shù)據(jù)依賴性
多線程環(huán)境中線程交替執(zhí)行,由于編譯器優(yōu)化重排的存在,兩個線程中使用的變量能否保證用的變量能否一致性是無法確定的,結(jié)果無法預(yù)測
考試先做會做的,不會做的后做。

public void mySort(){
int x = 11; //1
int y = 12; //2
x= x+5; // 3
y = x*x;//4

可能的順序1234 2134 1324,不可能的屬性4在1 和3前,因?yàn)橛袛?shù)據(jù)依賴性。

volatile和synchronize有哪些區(qū)別
volatile禁止指令重排。

public class ReSortSeqDemo {
    int a = 0;
    boolean flag = false;
    
    public void method01() {
        a = 1;           // flag = true;
                         // ----線程切換----
        flag = true;     // a = 1;
    }

    public void method02() {
        if (flag) {
            a = a + 3;
            System.out.println("a = " + a);
        }
    }

}

如果兩個線程同時執(zhí)行,method01 和 method02 如果線程 1 執(zhí)行 method01 重排序了,然后切換的線程 2 執(zhí)行 method02 就會出現(xiàn)不一樣的結(jié)果。

禁止指令排序

volatile 實(shí)現(xiàn)禁止指令重排序的優(yōu)化,從而避免了多線程環(huán)境下程序出現(xiàn)亂序的現(xiàn)象

先了解一個概念,內(nèi)存屏障(Memory Barrier)又稱內(nèi)存柵欄,是一個 CPU 指令,他的作用有兩個:

保證特定操作的執(zhí)行順序
保證某些變量的內(nèi)存可見性(利用該特性實(shí)現(xiàn) volatile 的內(nèi)存可見性)
由于編譯器個處理器都能執(zhí)行指令重排序優(yōu)化,如果在指令間插入一條 Memory Barrier 則會告訴編譯器和 CPU,不管什么指令都不能個這條 Memory Barrier 指令重排序,也就是說通過插入內(nèi)存屏障禁止在內(nèi)存屏障前后執(zhí)行重排序優(yōu)化。內(nèi)存屏障另一個作用是強(qiáng)制刷出各種 CPU 緩存數(shù)據(jù),因此任何 CPU 上的線程都能讀取到這些數(shù)據(jù)的最新版本。

下面是保守策略下,volatile寫插入內(nèi)存屏障后生成的指令序列示意圖:
volatile和synchronize有哪些區(qū)別
下面是在保守策略下,volatile讀插入內(nèi)存屏障后生成的指令序列示意圖:
volatile和synchronize有哪些區(qū)別

線程安全性保證

工作內(nèi)存與主內(nèi)存同步延遲現(xiàn)象導(dǎo)致可見性問題
可以使用 synchronzied 或 volatile 關(guān)鍵字解決,它們可以使用一個線程修改后的變量立即對其他線程可見
對于指令重排導(dǎo)致可見性問題和有序性問題
可以利用 volatile 關(guān)鍵字解決,因?yàn)?volatile 的另一個作用就是禁止指令重排序優(yōu)化

volatile

它所修飾的變量不保留拷貝,直接訪問主內(nèi)存中的。

在Java內(nèi)存模型中,有main memory,每個線程也有自己的memory (例如寄存器)。為了性能,一個線程會在自己的memory中保持要訪問的變量的副本。這樣就會出現(xiàn)同一個變量在某個瞬間,在一個線程的memory中的值可能與另外一個線程memory中的值,或者main memory中的值不一致的情況。 一個變量聲明為volatile,就意味著這個變量是隨時會被其他線程修改的,因此不能將它c(diǎn)ache在線程memory中。

使用場景

您只能在有限的一些情形下使用 volatile 變量替代鎖。要使 volatile 變量提供理想的線程安全,必須同時滿足下面兩個條件:
1)對變量的寫操作不依賴于當(dāng)前值。
2)該變量沒有包含在具有其他變量的不變式中。

volatile最適用一個線程寫,多個線程讀的場合。
如果有多個線程并發(fā)寫操作,仍然需要使用鎖或者線程安全的容器或者原子變量來代替。

synchronized

當(dāng)它用來修飾一個方法或者一個代碼塊的時候,能夠保證在同一時刻最多只有一個線程執(zhí)行該段代碼。

  1. 當(dāng)兩個并發(fā)線程訪問同一個對象object中的這個synchronized(this)同步代碼塊時,一個時間內(nèi)只能有一個線程得到執(zhí)行。另一個線程必須等待當(dāng)前線程執(zhí)行完這個代碼塊以后才能執(zhí)行該代碼塊。
  2. 然而,當(dāng)一個線程訪問object的一個synchronized(this)同步代碼塊時,另一個線程仍然可以訪問該object中的非synchronized(this)同步代碼塊。
  3. 尤其關(guān)鍵的是,當(dāng)一個線程訪問object的一個synchronized(this)同步代碼塊時,其他線程對object中所有其它synchronized(this)同步代碼塊的訪問將被阻塞。
  4. 當(dāng)一個線程訪問object的一個synchronized(this)同步代碼塊時,它就獲得了這個object的對象鎖。結(jié)果,其它線程對該object對象所有同步代碼部分的訪問都被暫時阻塞。
    volatile和synchronize有哪些區(qū)別
    共享資源及增刪改的對象。

Lock

從jdk 5.0開始,java提供了更強(qiáng)大的線程同步機(jī)制-通過顯示定義同步鎖對象來實(shí)現(xiàn)同步,同步鎖使用Lock對象充當(dāng)。

java.util.concurrent.Locks.Lock接口是控制多個線程對共享資源進(jìn)行訪問的工具。鎖提供了對共享資源的獨(dú)占訪問,每次只能有一個線程對Lock對象加鎖,線程開始訪問共享資源之前應(yīng)先獲得Lock對象。

ReentrantLock類實(shí)現(xiàn)了Lock,它擁有與synchronized相同的并發(fā)性和內(nèi)存語義,在實(shí)現(xiàn)線程安全的控制中,比較常用的是ReentrantLock,可以顯示加鎖、釋放鎖。

volatile和synchronize有哪些區(qū)別

區(qū)別

volatile和synchronized

  • volatile是變量修飾符,而synchronized則作用于一段代碼或方法。

  • volatile只是在線程內(nèi)存和“主”內(nèi)存間同步某個變量的值;而synchronized通過鎖定和解鎖某個監(jiān)視器同步所有變量的值, 顯然synchronized要比volatile消耗更多資源。

  • volatile不會造成線程的阻塞;synchronized可能會造成線程的阻塞。

  • volatile保證數(shù)據(jù)的可見性,但不能保證原子性;而synchronized可以保證原子性,也可以間接保證可見性,因?yàn)樗鼤⑺接袃?nèi)存中和公共內(nèi)存中的數(shù)據(jù)做同步。

  • volatile標(biāo)記的變量不會被編譯器優(yōu)化;synchronized標(biāo)記的變量可以被編譯器優(yōu)化。

線程安全包含原子性和可見性兩個方面,Java的同步機(jī)制都是圍繞這兩個方面來確保線程安全的。
關(guān)鍵字volatile主要使用的場合是在多個線程中可以感知實(shí)例變量被修改,并且可以獲得最新的值使用,也就是多線程讀取共享變量時可以獲得最新值使用。

關(guān)鍵字volatile提示線程每次從共享內(nèi)存中讀取變量,而不是私有內(nèi)存中讀取,這樣就保證了同步數(shù)據(jù)的可見性。但是要注意的是:如果修改實(shí)例變量中的數(shù)據(jù)

例如:i++,也就是i=i+1,則這樣的操作其實(shí)并不是一個原子操作,也就是非線程安全的。表達(dá)式i++操作步驟分解如下:
1)從內(nèi)存中取出i的值。
2)計(jì)算i的值;
3)將i的值寫到內(nèi)存中。
假如在第2步計(jì)算值得時候,另外一個線程也修改i的值,name這個時候就會出現(xiàn)臟讀數(shù)據(jù)。解決的辦法就是使用synchronized關(guān)鍵字。 所以說volatile本身并不處理數(shù)據(jù)的原子性,而是強(qiáng)制對數(shù)據(jù)的讀寫及時的影響到主內(nèi)存中。

synchronized 和Lock

Lock是顯示鎖(手動開啟和關(guān)閉,別忘記關(guān)閉鎖),synchronized是隱式鎖,出了作用域自動釋放鎖。
Lock只有代碼塊鎖,synchronized可以作用代碼塊和方法。
使用Lock鎖,jvm花費(fèi)較少的時間來調(diào)度線程,性能更好。并且具有更好的擴(kuò)展性(提供更多的子類)。
使用順序:Lock->同步代碼塊(已經(jīng)進(jìn)入了方法體,分配了相應(yīng)資源)->同步方法(在方法體之外)。

以上是volatile和synchronize有哪些區(qū)別的所有內(nèi)容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內(nèi)容對大家有所幫助,如果還想學(xué)習(xí)更多知識,歡迎關(guān)注億速云行業(yè)資訊頻道!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI