Synchronized、volatile與ThreadLocal區(qū)別及使用場景

1.JMM(java內(nèi)存模型)

Java內(nèi)存模型(Java Memory Model)描述了Java程序中各種變量(線程共享變量)的訪問規(guī)則,以及在JVM中將變量存儲到內(nèi)存和從內(nèi)存中讀取出變量這樣的底層細(xì)節(jié)。

多個線程同時對主內(nèi)存的一個共享變量進(jìn)行讀取和修改時,首先會讀取這個變量到自己的工作內(nèi)存中成為一個副本,對這個副本進(jìn)行改動之后,再更新回主內(nèi)存中變量。

(由于CPU時間片是以線程為最小單位,這里的工作內(nèi)存實(shí)際上就是指的物理緩存,CPU運(yùn)算時獲取數(shù)據(jù)的地方;而主內(nèi)存也就是指的是內(nèi)存,也就是原始的共享變量存放的位置)

image

JMM 關(guān)鍵技術(shù)點(diǎn)都是圍繞多線程的原子性、可見性、有序性來建立的。

原子性(Atomicity)

原子性是指一個原子操作在cpu中不可以暫停然后再調(diào)度。要么執(zhí)行完成,要么不執(zhí)行。

x++(包含三個原子操作)
a.將變量x 值取出放在寄存器中
b.將將寄存器中的值+1
c.將寄存器中的值賦值給x

可見性(Visibility)

如果一個線程對成員變量的修改,能夠及時的被其他線程看到,叫做成員變量的可見性。
禁止編譯器對成員變量進(jìn)行優(yōu)化,每次線程訪問成員變量時,都強(qiáng)迫從內(nèi)存中重讀該成員變量的值;而且,當(dāng)成員變量發(fā)生變化時,強(qiáng)迫線程將變化值回寫到共享內(nèi)存。

有序性(Ordering)

有序性問題的原因是因?yàn)槌绦蛟趫?zhí)行時,可能會進(jìn)行指令重排,重排后的指令與原指令的順序未必一致。
本線程內(nèi)觀察,所有操作都是有序的;如果在一個線程中觀察另一個線程,所有操作都是無序的。

兩條規(guī)定:

a.線程對共享變量的所有操作必須在工作內(nèi)存中進(jìn)行,不能直接操作主內(nèi)存

b.不同線程間不能訪問彼此的工作內(nèi)存中的變量,線程間變量值的傳遞都必須經(jīng)過主內(nèi)存


2.Synchronized(同步鎖)

Synchronized 實(shí)際上是對訪問修改共享變量的代碼塊進(jìn)行加互斥鎖,多個線程對Synchronized代碼塊的訪問時,某一時刻僅僅有一個線程在訪問和修改代碼塊中的內(nèi)容(加鎖),其他所有的線程等待該線程離開代碼塊時(釋放鎖)才有機(jī)會進(jìn)入Synchronized代碼塊。
Synchronized關(guān)鍵字保證了數(shù)據(jù)讀寫一致和可見性等問題,“以時間換空間”。
修飾一個類

class ClassName {
    public void method() {
       synchronized(ClassName.class) {
          // todo
       }
    }
 }

修飾一個方法

public synchronized void method()
{
   // todo
}

修飾一個代碼塊

public  void method()
{
   synchronized(this){
     // todo
   }
}

修飾一個靜態(tài)的方法

public synchronized static void method() {
   // todo
}

3.volatile

volatile如何實(shí)現(xiàn)可見性?

volatile變量每次被線程訪問時,都強(qiáng)迫線程從主內(nèi)存中重讀該變量的最新值,而當(dāng)該變量發(fā)生修改變化時,也會強(qiáng)迫線程將最新的值刷新回主內(nèi)存中。這樣一來,不同的線程都能及時的看到該變量的最新值。

但是volatile不能保證變量更改的原子性!
比如count++,這個操作實(shí)際上是三個操作的集合,volatile只能保證每一步的操作對所有線程是可見的,但是假如兩個線程都需要執(zhí)行count++,那么這一共6個操作集合,之間是可能會交叉執(zhí)行的,那么最后導(dǎo)致xx的結(jié)果可能會不是所期望的。有可能1號線程在即將進(jìn)行寫操作時count值為3;而2號線程就恰好獲取了寫操作之前的值3,所以1號線程在完成它的寫操作后count值就為4了,而在2號線程中count的值還為4,即使2號線程已經(jīng)完成了寫操作count還是為4。

public class Counter {
    private volatile int count;

    public int getCount(){
        return count;
    }
    public void increment(){
        count++;
    }
}

所以對于count++這種非原子性操作,推薦用synchronized:

public class Counter {
    private volatile int count;

    public int getCount(){
        return count;
    }
    public synchronized void increment(){
        count++;
    }
}
volatile適用情況

(1)對變量的寫入操作不依賴當(dāng)前值
比如自增自減、count = count + 5等(不滿足)

(2)當(dāng)前volatile變量不依賴于別的volatile變量


3.synchronized和volatile比較

  • synchronized既能保證共享變量可見性,也可以保證鎖內(nèi)操作的原子性;volatile只能保證可見性;
  • volatile不需要同步操作,所以效率更高,不會阻塞線程,但是適用情況比較窄

4.ThreadLocal

ThreadLocal不是為了解決多線程訪問共享變量,而是為每個線程創(chuàng)建一個單獨(dú)的變量副本,提供了保持對象的方法和避免參數(shù)傳遞的復(fù)雜性。

顧名思義它是local variable(線程局部變量)。它的功用非常簡單,就是為每一個使用該變量的線程都提供一個變量值的副本,是每一個線程都可以獨(dú)立地改變自己的副本,而不會和其它線程的副本沖突。從線程的角度看,就好像每一個線程都完全擁有該變量。(ThreadLocal采用了“以空間換時間”的方式)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容