保證一個類僅有一個實例,並提供一個訪問它的全局訪問點。編程
一般咱們可讓一個全局變量使得一個對象被訪問,但它不能防止你實例化多個對象。一個最好的辦法就是,讓類自身負責保存它的惟一實例。這個類能夠保證沒有其餘實例能夠建立,而且它能夠提供一個訪問該實例的方法。設計模式
/** * Created by callmeDevil on 2019/8/17. */ public class Singleton { private static Singleton instance; private Singleton(){} //構造方法私有,防止外界建立實例 // 得到本類實例的惟一全局訪問點 public static Singleton getInstance(){ if (instance == null) { //若實例不存在,則建立一個新實例,不然直接返回已有實例 instance = new Singleton(); } return instance; } }
public class Test { public static void main(String[] args) { Singleton s1 = Singleton.getInstance(); Singleton s2 = Singleton.getInstance(); if (s1 == s2) { System.out.println("兩個對象是相同的實例"); } } }
兩個對象是相同的實例
在沒有併發問題的狀況下,這種方式也是使用比較多的。但缺點也很明顯,多線程下根本無法用。服務器
/** * Created by callmeDevil on 2019/8/17. */ public class SingletonOnLock { private static SingletonOnLock instance; private SingletonOnLock(){} public static SingletonOnLock getInstance(){ // 同步代碼塊,只有一個線程能進入,其餘阻塞 synchronized (SingletonOnLock.class){ if(instance == null){ instance = new SingletonOnLock(); } } return instance; } }
當存在對象實例時,徹底不用擔憂併發時致使堆中建立多個實例,但每次調用 getInstance() 方法時都被加鎖,是會影響性能的,所以這個類能夠繼續改良。多線程
/** * Created by callmeDevil on 2019/8/17. */ public class SingletonOnDoubleCheckLock { private static SingletonOnDoubleCheckLock instance; private SingletonOnDoubleCheckLock(){} public static SingletonOnDoubleCheckLock getInstance(){ // 先判斷實例是否存在,不存在再考慮併發問題 if (instance == null) { synchronized (SingletonOnDoubleCheckLock.class){ if(instance == null){ instance = new SingletonOnDoubleCheckLock(); } } } return instance; } }
當實例存在時,就直接返回,這是沒有問題的。當實例爲空而且有兩個線程調用 getInstance() 方法時,它們均可以經過第一重 instace == null 的判斷,而後因爲 synchronized 機制,只有一個線程能夠進入,另外一個阻塞,必需要在同步代碼塊中的線程出來後,另外一個線程纔會進入。而此時若是沒有第二重的判斷,那第二個線程仍然會建立實例,這就達不到單例的目的了。併發
但這種方式是最讓人「詬病」的一種不推薦方式,技巧看上去很好,但實際上一樣影響性能。性能
/** * 該類聲明爲final ,阻止派生,由於派生可能會增長實例 * Created by callmeDevil on 2019/8/17. */ public final class SingletonStatic { // 第一次引用類的任何成員時就建立好實例,同時沒有併發問題 private static final SingletonStatic instance = new SingletonStatic(); private SingletonStatic(){} public static SingletonStatic getInstance(){ return instance; } }
JVM第一次加載類的時候就已經建立好了實例,若是接下來的很長時間都沒有用到的話,佔用的內存至關於被浪費了,也不是最讓人推薦的一種方式。固然如今的服務器容量也愈來愈大,單單一個實例的內存也並非任何狀況都要考慮節省。除非追求極致。。測試
/** * Created by callmeDevil on 2019/8/17. */ public class SingletonStaticClass { private SingletonStaticClass() {} public SingletonStaticClass getInstande() { return InterClass.instance; } // 靜態內部類,沒有併發問題 private static final class InterClass { public static SingletonStaticClass instance = new SingletonStaticClass(); } }
JVM第一次加載外部的 SingletonStaticClass 時,並不會直接實例化,因此這種方式也屬於「懶漢式」。只有在第一次調用 getInstance() 方法時,JVM纔會加載內部類 InterClass,接着才實例化靜態變量,也就是咱們須要的外部類的單例。這樣不只延時了實例化,同時也解決了併發訪問的問題,所以該方式是最爲推薦的一種方式。線程