單例模式
單例簡介
因進程需要,有時我們只需要某個類同時保留一個對象,不希望有更多對象。
單例模式的特點:
1.單例模式只能有一個實例。
2.單例類必須創建自己的唯一實例。
3.單例類必須向其他對象提供這一實例。
懶漢式
特點:
線程不安全,如果多線程同時訪問,會創造出多個對象。
/**
* 通過提供一個靜態的對象 SINGLE_TON_DEMO_1
* 利用private修飾的構造方法和getInstance()提供一個單例
*
* 缺點:線程不安全,如果多個線程同時訪問,會構造出多個對象
*/
public class SingleTonDemo1 {
private static SingleTonDemo1 SINGLE_TON_DEMO_1 ;
private SingleTonDemo1(){}
public static SingleTonDemo1 getInstance(){
if (SINGLE_TON_DEMO_1==null) {
SINGLE_TON_DEMO_1 = new SingleTonDemo1();
}
return SINGLE_TON_DEMO_1;
}
}
實例化方法加synchronized的懶漢式
特點:
線程安全,但是效率不高。
由於併發並不是隨時都在發生,大多數情況下這個鎖佔用的資源都浪費了。
/**
* 併發不是隨時都在發生,大多數時候這個鎖佔用的資源都浪費了
* 雖然線程安全,但是效率不高
*/
public class SingleTonDemo2 {
private static SingleTonDemo2 singleTonDemo2;
private SingleTonDemo2(){}
public static synchronized SingleTonDemo2 getInstance(){
if (singleTonDemo2 == null) {
singleTonDemo2 = new SingleTonDemo2();
}
return singleTonDemo2;
}
}
餓漢式
特點:
線程安全
/**
* 線程安全
* 相比於靜態方類,內存常駐
*/
public class SIngleTonDemo3 {
private static SIngleTonDemo3 sIngleTonDemo3 = new SIngleTonDemo3();
private SIngleTonDemo3(){}
public static SIngleTonDemo3 getInstance(){
return sIngleTonDemo3;
}
}
靜態內部類加載
特點:
1.線程安全
2.使用靜態內部類的好處是,不會隨着類的加載而加載,而是在調用getInstance()方法時再加載,達到類似懶漢模式的效果,而這種方式是線程安全的
/**
* 線程安全
*
* 使用靜態內部類的好處是,不會隨着類的加載而加載,而是在調用getInstance()方法時
* 再加載,達到類似懶漢模式的效果,而這種方式是線程安全的
*/
public class SingleTonDemo4 {
public static class SingleTonHolder{
private static SingleTonDemo4 singleTonDemo4 = new SingleTonDemo4();
}
private SingleTonDemo4(){}
public static SingleTonDemo4 getInstance(){
return SingleTonHolder.singleTonDemo4;
}
}
枚舉方法
特點:
1.線程安全
2.自由串行化
3.保證只有一個單例
4.Effective java 作者Josh Bloch提倡的方式
/**
* Effective java 作者Josh Bloch提倡的方式,解決了以下三個問題
* 一、自由串行化
* 二、保證只有一個單例
* 三、線程安全
*/
public enum SingleTonDemo5 {
INSTANCE;
public static void main(String[] args) {
SingleTonDemo5 instance = SingleTonDemo5.INSTANCE;
}
}
雙重校驗鎖
特點:
1.通常線程安全,低概率線程不安全
2.併發情況下,可能會出現線程未拿到實例返回null的情況。
/**
* 通常線程安全,低概率線程不安全
*
* 當併發情況下
* 一、線程A進入getInstance()方法,此時單例還沒有實例化,進入了鎖定塊
* 二、線程B進入getInstance()方法,此時單例還沒有實例化,得以訪問接下來代碼塊,但是代碼塊已經被線程A鎖定
* 三、線程A進入下一判斷,因爲單例還沒有實例化,所以實例化單例,實例化後退出代碼塊,解除鎖定
* 四、線程B進入下一判斷,此時單例已經被實例化,退出代碼塊,解除鎖定
* 五、線程A拿到單例實例並返回,線程B未拿到實例返回null
*
*/
public class SingleTonDemo6 {
private static SingleTonDemo6 singleTonDemo6;
private SingleTonDemo6(){}
public static SingleTonDemo6 getInstance(){
if (singleTonDemo6 == null){
synchronized (SingleTonDemo6.class){
if (singleTonDemo6 == null){
singleTonDemo6 = new SingleTonDemo6();
}
}
}
return singleTonDemo6;
}
}
加volatile的雙重校驗鎖
特點:
1.volatile關鍵字此處的作用是防止指令重排
/**
* volatile關鍵字此處的作用是防止指令重排,把singleTonDemo7聲明爲volatile後,對它的寫操作就會
* 有一個內存屏障,這樣在它賦值完成之前,都不會調用讀操作。
*
* 注意:volatile阻止的不是singleTonDemo7 = new SingleTonDemo7(),而是在寫操作完成之前,不會調用
* if (singleTonDemo7 == null)這個讀操作
*/
public class SingleTonDemo7 {
private static volatile SingleTonDemo7 singleTonDemo7;
private SingleTonDemo7(){}
public static SingleTonDemo7 getInstance(){
if (singleTonDemo7 == null){
synchronized (SingleTonDemo7.class){
if (singleTonDemo7 == null){
singleTonDemo7 = new SingleTonDemo7();
}
}
}
return singleTonDemo7;
}
}
ThreadLocal
特點:
線程安全
隔離多個線程對數據的訪問衝突
/**
* 線程安全
* ThreadLocal會爲每個線程提供一個獨立的變量副本,從而隔離了多個線程對數據的訪問衝突
*
*
*/
public class SingleTonDemo8 {
private static final ThreadLocal<SingleTonDemo8> THREAD_LOCAL =
new ThreadLocal<SingleTonDemo8>(){
@Override
protected SingleTonDemo8 initialValue() {
return new SingleTonDemo8();
}
};
private SingleTonDemo8(){}
public static SingleTonDemo8 getInstance(){
return THREAD_LOCAL.get();
}
}
CAS鎖
特點:
線程安全
/**
* 線程安全
*
* CAS機制當中使用了3個基本操作數:內存地址V,舊的預期值A,要修改的新值B。
*更新一個變量的時候,只有當變量的預期值A和內存地址V當中的實際值相同時,纔會將內存地址V對應的值修改爲B。
* CAS屬於樂觀鎖,樂觀地認爲程序中的併發情況不那麼嚴重,所以讓線程不斷去嘗試更新
*
* 缺點:
* CPU開銷較大
* CAS機制所保證的只是一個變量的原子性操作,而不能保證整個代碼塊的原子性
*/
public class SingleTonDemo9 {
private static final AtomicReference<SingleTonDemo9> ATOMIC_REFERENCE = new AtomicReference<>();
private SingleTonDemo9(){}
public static final SingleTonDemo9 getInstance(){
// 相當於while(true)
for (;;){
SingleTonDemo9 currentDemo9 = ATOMIC_REFERENCE.get();
if (currentDemo9 != null){
return currentDemo9;
}
currentDemo9 = new SingleTonDemo9();
if (ATOMIC_REFERENCE.compareAndSet(null,currentDemo9)){
return currentDemo9;
}
}
}
public static void main(String[] args) {
SingleTonDemo9 testSingleTon = new SingleTonDemo9();
SingleTonDemo9 testSingleTon2 = new SingleTonDemo9();
System.out.println(testSingleTon == testSingleTon2); //false
}
}