/**
* 懶漢式
* 實例在用到的時候才去創建,用的時候才去檢查有沒有實例,如果有則返回,沒有則新建。有線程安全和線程不安全兩種寫法,區別就是synchronized關鍵字
*/
public class LazyMode {
private static LazyMode lazyMode;
private LazyMode() {
}
public static synchronized LazyMode getLazyModeInstance() {
if (lazyMode == null) {
lazyMode = new LazyMode();
}
return lazyMode;
}
}
/**
* 餓漢式
* 實例在初始化的時候就已經建好了,不管有沒有用到先建好了再說。好處是沒有線程安全的問題,壞處是浪費內存空間。
*/
public class HungryMode {
private static HungryMode mode = new HungryMode();
private HungryMode() {
}
public static HungryMode getHungryModeInstance() {
return mode;
}
}
/**
* 雙重檢查鎖
* 結合懶漢式和餓漢式兩者的優缺點,特點是在synchronized關鍵字內外都加了一層if條件判斷,這樣既保證了線程安全,又比直接上鎖提高了執行效率,還節省了內存空間。
*
* 問題1: 為什么兩次判空
* 第一個判斷避免了不必要的同步,減少性能開銷
* 第二個判空避免生成多個對象實例
*
* 比如三個線程A,B,C,假設線程A和線程B同時調用getSingleton()時,判斷第一層if判斷都為空,這時線程A先拿到鎖,線程B在代碼塊外層等待。
* 線程A進行第二層if判斷,new了一個新對象,創建完成釋放鎖,線程B拿到鎖,進行第二層if判斷,singleton不為空,直接返回singleton釋放鎖,避免生成多個對象實例。
* 線程線C調用getSingleton時第一層判斷不成立,直接拿到singleton對象返回,避免進入鎖,減少性能開銷。
*
* 問題2:為什么要用volatile關鍵字
* 如果不用volatile,并發情況下會出現問題,線程A進入new TestInstance() 的時候,分為:1.分配內存、2.初始化對象、3.mInstance指向內存 三步,
* 此時若發生指令重排,執行順序是132,執行到第3的時候,線程B剛好進來了,并且執行到注釋2,并沒有進入synchronized代碼塊,synchronized修飾的是代碼塊,不是整個方法。這時候判斷mInstance不為空,直接使用一個未初始化的對象會報錯。
*/
public class DoubleCheckMode {
private static volatile DoubleCheckMode mode;
private DoubleCheckMode() {
}
public static DoubleCheckMode getDoubleCheckModeInstance() {
if (mode == null) { //2
synchronized (DoubleCheckMode.class) {
if (mode == null) {
mode = new DoubleCheckMode();
}
}
}
return mode;
}
}
/**
* 靜態內部類
* 因為內部類是私有的,所以除了get方法外,沒有辦法訪問它,同時static和final是JVM本身的機制保證了線程的安全,同時它在性能上也沒有損耗
*/
public class Singleton {
public Singleton() {
}
public static Singleton getInnerStaticMode() {
return SingletonHolder.INSTANCE;
}
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
}
/**
* 枚舉
* 自動支持序列化機制,絕對防止多次實例化。同時包括靜態內部類的優點
*/
public enum EnumSingleton {
INSTANCE;
public void doSomeThing() {
}
}
http://www.lxweimin.com/p/f3fae8658f13 如何實現一個線程安全的單例,前提是不能加鎖
面試官:說說多線程并發問題 - 掘金 (juejin.cn)