原子操作和volatile關鍵字


原子操作:不可被中斷的操作。要么全執行,要么全不執行。

現代CPU讀取內存,通過讀取緩存再寫入主存。先去主存讀--->寫入緩存---->運行線程--->寫入緩存---->寫入主存

多cpu時會出現緩存一致性和總線鎖的問題。

只有簡單的讀取,賦值操作,即一步完成的操作才是原子操作。

volatile,synchronized,lock 能保證可見性,

volatile保證修改的值立即更新到主存,synchronized和lock保證同一時刻只有一個線程操作變量,在鎖被釋放前會將新值寫入內存。

線程的有序性:

java內存模型具備一些先天的有序性,即happensbefore(先行發生)原則

1,程序次序規則,一個線程內,按照代碼書寫順序執行

2,鎖定規則,一個解鎖操作ounlock先行發生於后面對同一個鎖的lock操作

3,volatile變量規則,對一個變量的寫操作先行發生於后面對這個變量的讀操作

4,傳遞規則,A先發生於B,B先發生於C,則A先發生於C

5,線程啟動規則,Thread對象的start()方法先行發生於此線程的每一個操作

6,線程中斷規則,對線程interrupt()方法的調用先行發生於被中斷線程的代碼檢測到中斷事件的發生,即中斷的發生先於中斷被檢測到

7,線程終結規則,線程中所有的操作都先行發生於線程的中止檢測,可以通過Thread.join()方法結束,Thread.isAlive()的返回值手段檢測到線程已經終止

8,對象終結規則,一個對象的初始化完成先行發生於他的finalize()方法的開始

1·8·來源於《深入理解java虛擬機》

 

在單個線程中,虛擬機有可能對指令進行重排序。雖然進行重排序,但是最終執行的結果與程序順序執行的結果是一致的。它只會對不存在數據依賴性的指令進行重排序。

在多線程中則能保證執行順序。

一旦一個共享變量,比如類的成員變量,類的靜態成員變量,被volatile修飾之后,那么它就具備了兩層語義:

1,保證了不同線程對這個變量進行操作時是可見的,即一個線程修改了某個變量的值,這個新值對其他線程來說是立即可見的。

2,禁止進行指令重排序。

但是volatile不保證操作的原子性,比如對自增操作等不能保證原子性。

對自增操作,可以用synchronized和lock保證操作的原子性。也可以用AtomicInteger操作來進行自增。原因是atomic是利用CAS的原理實現原子操作。CAS利用處理器提供的CMPXCHG指令實現,處理器執行CMPXCHG指令是一個原子操作。

 

long或double的賦值操作不是原子操作,比如在32位機上對long和double的讀寫將會分成兩步進行。用volatile修飾時,long和double的讀寫將會變成原子的。

 

線程局部(本地)變量是指局限於線程內部的變量,不與其他線程共享。

java提供ThreadLocal類來支持線程局部變量,它好似一種實現線程安全的方式。

在web服務器上使用線程局部變量的時候,它的生命周期比任何應用程序的變量的生命周期都要長,所以如果使用完畢后而不釋放,救會有內存泄露的風險。

 

sleep()只是短暫休眠線程,並不會釋放鎖。

wait()指等待某條件。只有釋放掉鎖,其他等待的線程才能在滿足條件時獲取到該鎖,所以wait()又叫條件等待。

 

單例的目的是為了保證運行時Singleton類只有一個實例,因為instance = new Singleton()的性能開銷較大。最常用的地方比如獲取數據庫連接,spring中創建beanFactory等。

 

單例模式的七種寫法:

1,懶漢式,線程不安全,在多線程下不能正常工作:

public class Singleton{

private static Singleton instance;

private Singleton(){};

public static Singleton getInstance(){

if(instance == null){

instance = new Singleton ();

}

return instance;

}

}

 

2,懶漢式,線程安全,在多線程下也正常工作,但是效率低下,99%的情況不需要同步

/***lazy **thread safety*/
public class Singleton{
private static Singleton instance;
private Singleton(){};
public static synchronized Singleton getInstance(){
if(instance == null){
instance = new Singleton();
}
return instance;
}
}

 

3,餓漢式,基於classloader機制避免了線程同步,但是沒有lazy loading的效果,在類裝載時就實例化

public class Singleton{
private static Singleton instance = new Singleton();
private Singleton(){};
public static Singleton getInstance(){
return instance;
}
}

 

4,餓漢式的變種,也是在類加載時即實例化,沒有實現懶加載

public class Singleton{
private static Singleton instance = null;
static{
instance = new Singleton();
}
private Singleton(){};
public static Singleton getInstance(){
return instance;
}
}

 

 

5,靜態內部類,這是一種推薦的寫法,懶加載且單線程,只有在顯式調用getInstance()的時候SingletonHolder類才被裝載

public class Singleton{
private static class SingletonHolder{
private static final Singleton INSTANCE = new Singleton();
}
private Singleton(){};
public static Singleton getInstance(){
return SingletonHolder.INSTANCE;
}
}

 

6,枚舉,effective java推薦的寫法,線程安全的單例模式最推薦的寫法

避免了多線程同步,防止反序列化重新創建新的對象

因為JVM類初始化是線程安全的,所以可以采用枚舉類實現一個線程安全的單例模式

public enum Singleton{

INSTANCE;

public void whateverMethod(){

}

}

 

 

7,雙重校驗鎖

public class Singleton{
private volatile static Singleton instance;
private Singleton(){};
public static Singleton getInstance(){
if(instance == null){
synchronized(Singleton.class){
if(instance == null){
instance = new Singleton();
}
}
}
return instance;
}
}

 

雙重校驗鎖是不安全的,java內存模型允許"無序寫入",無序寫入將會導致二次檢查失敗。JDK5以后可以使用雙重校驗鎖,但是不推薦。

任何時候都不應該使用雙重校驗鎖,因為無法保證它能在任何JVM上都能順利運行。

之所以會有雙重校驗鎖,是因為以下代碼:

if(instance == null ){//兩個線程在初始化判斷的時候同時進入

synchronized(Singleton.class){//線程2在此時被鎖定

instace = new Singleton();

}//線程1 執行實例化完畢喚醒線程2,線程2 將不會經過檢查,而是直接再次執行實例化動作,造成出錯。因為檢查這一步已經過了

retunr instance;

}

 

如果一個單例類由多個不同的類裝載器裝載,比如有連個servlet訪問同一個單例類,則這兩個servlet會生成各自的實例。

如果單例類Singleton實現了serializedble接口,那么這個類的實例就可能被序列化和復原。不管怎么樣如果你序列化一個單例類的對象,接下來復原那個對象,你就會有多個單例類的實例。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM