單例模式
什么是單例模式慌申?單例模式就是只允許生成一個(gè)實(shí)例的類(lèi)。
一般來(lái)說(shuō)厨幻,被創(chuàng)建出的單例類(lèi)的對(duì)象都是由單例類(lèi)本身持有,然后也是由單例類(lèi)本身來(lái)創(chuàng)建况脆,也就是說(shuō)單例類(lèi)的構(gòu)造器必須是私有的。
單例類(lèi)主要用來(lái)解決全局類(lèi)被頻繁的創(chuàng)建和銷(xiāo)毀格了,應(yīng)用場(chǎng)景:上數(shù)據(jù)庫(kù)連接驅(qū)動(dòng)捏雌,數(shù)據(jù)庫(kù)連接池等
懶漢模式
懶漢模式指在單例類(lèi)加載時(shí)不進(jìn)行實(shí)例初始化,當(dāng)需要使用實(shí)例時(shí)才進(jìn)行實(shí)例初始化性湿,獲取對(duì)象較慢满败,類(lèi)加載較快
線程不安全
/**
* @Auther: Lee
* @Date: 2018/6/11 10:26
* @Description: 懶漢模式的單例類(lèi) 線程不安全
*/
public class LazyDemo {
private LazyDemo() {
System.out.println("你開(kāi)始了肤频?");
}
public static LazyDemo lazyDemo = null;
public static LazyDemo getInstance() {
//因?yàn)橹荒軇?chuàng)建一個(gè)實(shí)例變量所以需要判斷靜態(tài)引用變量是否為null
//當(dāng)多個(gè)線程同時(shí)進(jìn)行到這個(gè)步驟,判定都為null算墨,則都會(huì)生成新的實(shí)例
if (lazyDemo == null){
lazyDemo = new LazyDemo();
}
return lazyDemo;
}
}
以上就實(shí)現(xiàn)了一個(gè)簡(jiǎn)單的單例類(lèi)宵荒,但是上面這種方式在多線程環(huán)境下可能會(huì)出現(xiàn)線程不安全的情況净嘀,
比如:在判斷if (lazyDemo == null)處,如果多個(gè)線程同時(shí)執(zhí)行完這個(gè)判斷挖藏,就是線程A執(zhí)行完這個(gè)判斷要進(jìn)行下一步時(shí)膜眠,跳到了另外的線程溜嗜,這個(gè)時(shí)候另外的線程還是判斷引用的是null,那么這些線程都會(huì)執(zhí)行下一步的生成實(shí)例代碼炸宵,并將應(yīng)用變量指向生成的實(shí)例地址谷扣。這樣的話這個(gè)引用變量引用的就不止一個(gè)示例變量了。
線程安全(synchronized關(guān)鍵字)
解決這個(gè)問(wèn)題就需要用到線程同步關(guān)鍵字synchronized会涎;給getInstance方法加鎖,
/**
* @Auther: Lee
* @Date: 2018/6/11 11:41
* @Description: 方法上加鎖的單例類(lèi)
*/
public class LazyLockDemo {
private LazyLockDemo() {
System.out.println("我開(kāi)始了老哥!");
}
public static LazyLockDemo LazyLockDemo = null;
//這種方式解決了線程的問(wèn)題蛔溃,但是每次執(zhí)行g(shù)etInstance都需要獲得鎖篱蝇,其他的線程等待 類(lèi)似串行執(zhí)行性能不好
public static synchronized LazyLockDemo getInstance(){
if (LazyLockDemo==null){
LazyLockDemo = new LazyLockDemo();
}
return LazyLockDemo;
}
}
上面這種方式對(duì)獲取對(duì)象的整個(gè)方法加鎖,效率較低
雙重檢查鎖
那么可以將鎖的范圍縮小零截,僅僅在new新的對(duì)象那里使用同步涧衙。
/**
* @Auther: Lee
* @Date: 2018/6/11 11:49
* @Description: 雙重檢查鎖哪工,
*/
public class LazyDoubleLockDemo {
private LazyDoubleLockDemo() {
System.out.println("我開(kāi)始了老哥雁比!");
}
public static volatile LazyDoubleLockDemo lazyDoubleLockDemo = null;
public static LazyDoubleLockDemo getInstance(){
if (lazyDoubleLockDemo == null){
// 在使用構(gòu)造方法生成實(shí)例時(shí)獲得鎖撤嫩,獲得鎖之后再進(jìn)行一次判斷是否有實(shí)例
// 這種方式是減少加鎖的范圍,由于syn關(guān)鍵字是可重入鎖序攘,所以?xún)纱渭渔i性能消耗并不會(huì)太多
// 這種方式在語(yǔ)句轉(zhuǎn)化成計(jì)算機(jī)指令時(shí),還是會(huì)出先線程不安全問(wèn)題丈牢,
synchronized (LazyDoubleLockDemo.class){
if (lazyDoubleLockDemo ==null){
lazyDoubleLockDemo = new LazyDoubleLockDemo();
}
}
}
return lazyDoubleLockDemo;
}
}
可以看到瞄沙,上面靜態(tài)引用變量添加了volatile修飾符修飾朴皆,
因?yàn)榉捍猓@里會(huì)涉及到一個(gè)指令重排序問(wèn)題。
instance = new Singleton2(); 其實(shí)可以分為下面的步驟:
1.申請(qǐng)一塊內(nèi)存空間馏谨;
2.在這塊空間里實(shí)例化對(duì)象;
3.instance的引用指向這塊空間地址钾怔;
指令重排序存在的問(wèn)題是:
對(duì)于以上步驟蒙挑,指令重排序很有可能不是按上面123步驟依次執(zhí)行的。
比如矾利,先執(zhí)行1申請(qǐng)一塊內(nèi)存空間馋袜,然后執(zhí)行3步驟,instance的引用去指向剛剛申請(qǐng)的內(nèi)存空間地址察皇,
那么泽台,當(dāng)它再去執(zhí)行2步驟,判斷instance時(shí)怀酷,由于instance已經(jīng)指向了某一地址,它就不會(huì)再為null了因篇,
因此笔横,也就不會(huì)實(shí)例化對(duì)象了。這就是所謂的指令重排序安全問(wèn)題吹缔。所以厢塘,我們需要在創(chuàng)建引用變量時(shí)加上volatile關(guān)鍵字肌幽,因?yàn)関olatile可以禁止指令重排序抓半。
靜態(tài)內(nèi)部類(lèi)
除了上面兩種方式可以解決線程安全問(wèn)題外,還有一種靜態(tài)內(nèi)部類(lèi)的方式
/**
* @Auther: Lee
* @Date: 2018/6/11 11:57
* @Description: 靜態(tài)內(nèi)部類(lèi) 應(yīng)該還是餓漢
*/
public class LazyInnerClassDemo {
private LazyInnerClassDemo(){
System.out.println("我開(kāi)始了老哥笛求!");
}
private static class InnerClass{
private static final LazyInnerClassDemo LAZY_DOUBLE_LOCK_DEMO = new LazyInnerClassDemo();
}
public static LazyInnerClassDemo getInstance(){
return InnerClass.LAZY_DOUBLE_LOCK_DEMO;
}
}
餓漢模式
除了上面幾種懶漢模式的實(shí)現(xiàn)外探入,還有餓漢模式,餓漢模式指的是當(dāng)加載類(lèi)時(shí)就初始化實(shí)例蜂嗽,類(lèi)加載較慢,獲取對(duì)象較快辱揭,與懶漢模式相比隆嗅,餓漢模式是線程安全的
/**
* @Auther: Lee
* @Date: 2018/6/11 14:40
* @Description: 餓漢式單例模式 類(lèi)加載時(shí)就保存一個(gè)實(shí)例對(duì)象
*/
public class HungerDemo {
private HungerDemo() {
System.out.println("我好了侯繁,你呢?");
}
private static final HungerDemo HUNGER_DEMO = new HungerDemo();
public static HungerDemo getInstance() {
return HUNGER_DEMO;
}
}
枚舉實(shí)現(xiàn)
還有推薦使用的一種:枚舉實(shí)現(xiàn)的單例類(lèi)丽焊, 線程安全咕别,速度很快
/**
* @Auther: Lee
* @Date: 2018/6/12 14:40
* @Description: 使用枚舉實(shí)現(xiàn)單例
*/
public enum EnumDemo {
INSTANCE;
EnumDemo(){
System.out.println("我好了!");
}
public void doSomething(){
System.out.println("我干活了惰拱!");
}
}
總結(jié):
懶漢模式是時(shí)間換空間,餓漢模式是空間換時(shí)間偿短,
懶漢線程不安全,需要使用線程鎖和雙重檢查實(shí)現(xiàn)線程安全降传,餓漢是線程安全的勾怒。