挑戰常規--這樣寫單例是錯的!
阿新 • • 發佈:2018-11-12
說到單例,網上教程和很多人信手拈來:
public class Single { private volatile static Single instance; private Single() { System.out.println("建立單例"); } public static Single getInstance() { if (instance == null) { synchronized (Single.class) { if (instance == null) { instance = new Single(); } } } return instance; } }
自信滿滿,稱之為懶漢載入模式,言之節省記憶體,用時才會自動建立。在我看來,這種寫法完全是錯誤的,愚蠢至極,這不是脫褲子放屁,這是脫完褲子再提取褲子再放屁。
正確寫法就是最簡單的:
public class Single { private static Single instance=new Single(); private Single() { System.out.println("建立單例"); } public static Single getInstance() { return instance; } }
下面駁斥所謂的省記憶體。
public class SingleTest { public static void main(String[] args) throws Exception { System.out.println("啟動執行緒"); System.in.read(); } }
首先,不用單例時,難道別的寫法就會載入單例?沒有引用使用單例類時,當然都不會載入單例。
看圖,並不會載入建立單例,控制檯也不會輸出建立單例。
其次,既然預定要使用單例,那麼都會載入建立一次。
public class SingleTest { public static void main(String[] args) throws Exception { System.out.println("啟動執行緒"); Single.getInstance(); System.in.read(); } }
看圖,無論哪種模式單例都會被引用載入
是不是用synchronized建立單例就沒有用處了呢?
並不是,synchronized是用於解決多執行緒訪問問題。帶引數的單例建立才應該使用懶漢模式
因為並不能預期,什麼時候引數被傳入。簡單模式下並不清楚傳入什麼引數,或引數物件未初始化。
public class Single { private volatile static Single instance ; public Single(Context context) { System.out.println("建立單例"); } public static Single getInstance(Context context) { if (instance == null) { synchronized (Single.class) { if (instance == null) { instance = new Single(context); } } } return instance; } }
為什麼說這個是為了解決多執行緒訪問呢,先看如果不加鎖會發生什麼
public class Single { private static Single instance ; public Single(Context context) { System.out.println("建立單例"); } public static Single getInstance(Context context) { if (instance == null) { instance = new Single(context); } return instance; } }
public class SingleTest { public static void main(String[] args) throws Exception { ExecutorService pool = Executors.newCachedThreadPool(); ArrayList<Callable<Void>> runners=new ArrayList<>(); for(int i=0;i<10;i++) { runners.add(()->{ Single.getInstance(new Context()); return null; }); } System.out.println("啟動執行緒"); pool.invokeAll(runners); pool.shutdown(); System.in.read(); } }
不加鎖情況,結果看圖
加鎖情況,結果看圖
總結,無參構造單例無需複雜的加入synchronized,而未確定的傳參單例需要加synchronized保證多執行緒訪問安全。
思考,如果傳入的引數確定,怎麼寫才最優呢?下面類似寫法是否合理:
public class Single { private static Single instance =new Single(Context.instance); public Single(Context context ) { System.out.println("建立單例"); } public static Single getInstance( ) { return instance; } }
@WebListener public class MyServletContextListener implements ServletContextListener { public void contextDestroyed(ServletContextEvent sce) { } public void contextInitialized(ServletContextEvent sce) { Single.getInstance(sce); } }