原子操作:不可被中斷的操作。要么全執行,要么全不執行。
現代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接口,那么這個類的實例就可能被序列化和復原。不管怎么樣如果你序列化一個單例類的對象,接下來復原那個對象,你就會有多個單例類的實例。