為什么需要單例模式
1.在日常web系統(tǒng)開發(fā)中,如果使用spring框架作為ioc容器,默認(rèn)情況下所有的bean都是以單例模式構(gòu)建的,如我們寫的service,dao,controller這樣的類,其實(shí)一般都是與線程無關(guān)的,也就是無狀態(tài)的,我們沒有必要在每一次用戶請求時(shí)都去新生成一個(gè)controller,一個(gè)service。。。spring為全局生成一個(gè)唯一實(shí)例,所有請求都復(fù)用就可以了,節(jié)約內(nèi)存。
2.如果某各類本身的實(shí)例化過程需要涉及很多的計(jì)算,很多的資源初始化開銷,我們肯定要讓其在全局只初始化一次,節(jié)約性能。
3.當(dāng)我們的代碼是有狀態(tài)的,涉及到并發(fā)時(shí),我們一定會(huì)加鎖進(jìn)行并發(fā)訪問控制來保證線程安全性。
public class ThreadSafeTest {
private static int VARIBLE = 1;
public synchronized void add(){
VARIBLE += 1;
}
}
以上代碼用synchronized來保證了VARIBLE增加的安全性,但是如果ThreadSafeTest類在系統(tǒng)中存在大于一個(gè)的實(shí)例,VARIBLE變量是類靜態(tài)屬性,而synchronized鎖住的是實(shí)例對象本身,這個(gè)add方法將不再安全,以上代碼要變成線程安全有幾種改法:1.VARIBLE設(shè)置為原子變量,2.add方法的鎖改為類鎖,3.把ThreadSafeTest類改為單例。
n種實(shí)現(xiàn)方法
1.最基本的非線程安全的懶漢式
public class Singleton {
private static Singleton instance;
private Singleton (){}
public static Singleton getInstance() {
if (instance == null) { //這里先判斷后執(zhí)行非原子操作,線程不安全
instance = new Singleton();
}
return instance;
}
}
2.線程安全的懶漢式
public class Singleton {
private static Singleton instance;
private Singleton (){}
//synchronized控制了線程安全性,但是直接鎖住整個(gè)方法,性能將會(huì)嚴(yán)重下降
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
3.餓漢式,線程安全
public class Singleton {
//類裝載時(shí)進(jìn)行實(shí)例化,如果實(shí)例化過程特別耗費(fèi)資源,將會(huì)導(dǎo)致啟動(dòng)緩慢
private static Singleton instance = new Singleton();
private Singleton (){}
public static Singleton getInstance() {
return instance;
}
}
4.餓漢式變種
public class Singleton {
private static Singleton instance;
{ //沒啥好說的,和3一毛一樣
instance = new Singleton();
}
private Singleton (){}
public static Singleton getInstance() {
return instance;
}
}
5.靜態(tài)內(nèi)部類實(shí)現(xiàn)
public class Singleton {
//static修飾該類,為類級別內(nèi)部類,調(diào)用時(shí)才進(jìn)行裝在,初始化
private static class SingletonHolder {
//jvm保證只會(huì)初始化一次INSTANCE屬性
private static final Singleton INSTANCE = new Singleton();
}
private Singleton (){}
public static final Singleton getInstance() {
//調(diào)用SingletonHolder.INSTANCE時(shí),jvm才會(huì)裝載初始化SingletonHolder類,保證了線程安全。
return SingletonHolder.INSTANCE;
}
}
6.雙重鎖檢測
public class Singleton {
//volatile關(guān)鍵字保證singleton被修改后內(nèi)存可見性
private volatile static Singleton singleton;
private Singleton (){}
//不對方法進(jìn)行加鎖
public static Singleton getSingleton() {
//先判斷singleton是否為空,volatile關(guān)鍵字保證為空可見性,只有前幾次請求會(huì)走到下面的分支
if (singleton == null) {
//對類進(jìn)行加鎖,并準(zhǔn)備對singleton進(jìn)行初始化
synchronized (Singleton.class) {
//獲取到鎖后需要再次判斷singleton是否已經(jīng)被實(shí)例化過了,因?yàn)樵谧约号抨?duì)獲得鎖的過程中很有可能別的線程已經(jīng)得到鎖并實(shí)例化過了
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
7.枚舉實(shí)現(xiàn)(effectiv java中推薦的高級方式,牛逼閃閃)
public enum Singleton {
INSTANCE;
//以下是Singleton類本身具有的各個(gè)方法具體邏輯
public void whateverMethod1() {
}
// ...其他的更多的自身方法
}
這種方式確實(shí)叼,腦洞大開啊。。。。它不僅能避免多線程同步問題,而且還能防止反序列化重新創(chuàng)建新的對象,而且實(shí)現(xiàn)起來非常非常簡單,但是很多人都覺得枚舉里面有太多的業(yè)務(wù)邏輯很奇怪~~~確實(shí)。。
反序列化和不同的類加載器可能破壞單例模式的解決方式
1.反序列化。
public class Singleton implements java.io.Serializable {
//這里只是以餓漢式舉例,其他任何方式都可以
public static Singleton INSTANCE = new Singleton();
protected Singleton() {
}
//readResolve返回的對象替換反序列化創(chuàng)建的實(shí)例;
private Object readResolve() {
return INSTANCE;
}
}
2.classloader。
stackverflow等地方給出的答案,但是我沒能理解怎么用。。。。。:
private static Class getClass(String clazz) throws ClassNotFoundException {
ClassLoader loader = Thread.currentThread().getContextClassLoader();
if(loader == null)
loader = YourSingleton.class.getClassLoader();
return (loader.loadClass(clazz));
}
}
3.clone
單例類,不應(yīng)該實(shí)現(xiàn)clone方法,只要不實(shí)現(xiàn),就沒辦法clone的