天天看點

Java線程的關鍵字

synchronized

synchronized關鍵字解決的是多個線程之間通路資源的同步性,可以保證被它修飾的⽅法或者代碼塊在任意時刻隻能有⼀個線程執⾏。

synchronized使用方式

修飾執行個體方法: 作⽤于目前對象執行個體加鎖,進⼊同步代碼前要獲得目前對象執行個體的鎖

修飾靜态方法: 也就是給目前類加鎖,會作⽤于類的所有對象執行個體,因為靜态成員不屬于任何⼀個執行個體對象,是類成員( static 表明這是該類的⼀個靜态資源,不管new了多少個對象,隻有⼀份)。是以如果⼀個線程A調⽤⼀個執行個體對象的⾮靜态 synchronized ⽅法,⽽線程B需要調⽤這個執行個體對象所屬類的靜态 synchronized ⽅法,是允許的,不會發⽣互斥現象,因為通路靜态synchronized ⽅法占⽤的鎖是目前類的鎖,⽽通路⾮靜态 synchronized ⽅法占⽤的鎖是目前執行個體對象鎖。

修飾代碼塊: 指定加鎖對象,對給定對象加鎖,進⼊同步代碼庫前要獲得給定對象的鎖。

volatile的作用

保證變量可見性:把變量聲明為volatile,這就訓示 JVM,這個變量是不穩定的,每次使⽤它都到主存中進⾏讀取。

原因:在目前的 Java 記憶體模型下,線程可以把變量儲存本地記憶體(⽐如機器的寄存器)中,⽽不是直接在主存中進⾏讀寫。這就可能造成⼀個線程在主存中修改了⼀個變量的值,⽽另外⼀個線程還繼續使⽤它在寄存器中的變量值的拷⻉,造成資料的不⼀緻。

防止指令重排序:Java 在編譯器以及運⾏期間的優化,代碼的執⾏順序未必就是編寫代碼時候的順序,比如說new一個對象一共分為三步:

1.堆中開辟一塊區域,為對象配置設定記憶體空間;

2.初始化對象;

3.将對象指定配置設定的記憶體位址。

但是由于 JVM 具有指令重排的特性,執⾏順序有可能變成 1>3>2。在多線程的場景下,例如,線程 1 執⾏了 1 和 3,此時線程2擷取對象時,對象還并沒有執行第2步初始化,最終隻能得到一個未被初始化的對象。

synchronized和volatile的差別

1.多線程通路volatile關鍵字不會發⽣阻塞,⽽synchronized關鍵字可能會發⽣阻塞;

2.volatile比synchronized更輕量級,性能更好;

3.volatile隻能作用于變量,而synchronized能作用于類、對象和方法;

4.volatile關鍵字能保證資料的可⻅性,但不能保證資料的原⼦性。synchronized關鍵字兩者都能

保證。

并發程式設計的三個重要特性

1.原子性 : ⼀個的操作或者多次操作,要麼所有的操作全部都得到執⾏并且不會收到任何因素的⼲擾⽽中斷,要麼所有的操作都執⾏,要麼都不執⾏。 synchronized 可以保證代碼⽚段的原⼦性。

2. 可見性 :當⼀個變量對共享變量進⾏了修改,那麼另外的線程都是⽴即可以看到修改後的最新值。 volatile 關鍵字可以保證共享變量的可⻅性。

3. 有序性 :代碼在執⾏的過程中的先後順序,Java 在編譯器以及運⾏期間的優化,代碼的執⾏順序未必就是編寫代碼時候的順序。 volatile 關鍵字可以禁⽌指令進⾏重排序優化。

ThreadLocal

ThreadLocal可以使每一個線程都有自己的本地變量。

如果你建立了⼀個 ThreadLocal 變量,那麼通路這個變量的每個線程都會有這個變量的本地副本,這也是 ThreadLocal 變量名的由來。他們可以使⽤ get() 和 set()⽅法來擷取預設值或将其值更改為目前線程所存的副本的值,從⽽避免了線程安全問題。

ThreadLocal 記憶體洩露問題

ThreadLocalMap 中使⽤的 key 為 ThreadLocal 的弱引⽤,⽽ value 是強引⽤。是以,如果ThreadLocal 沒有被外部強引⽤的情況下,在垃圾回收的時候,key 會被清理掉,⽽ value 不會被清理掉。這樣⼀來, ThreadLocalMap 中就會出現key為null的Entry。假如我們不做任何措施的話,value 永遠⽆法被GC 回收,這個時候就可能會産⽣記憶體洩露。ThreadLocalMap實作中已經考慮了這種情況,在調⽤ set() 、 get() 、 remove() ⽅法的時候,會清理掉 key 為 null 的記錄。使⽤完ThreadLocal ⽅法後 最好⼿動調⽤ remove() ⽅法。