前言:
單例設(shè)計(jì)模式也是非常常用的設(shè)計(jì)模式,比如我們所熟知的servlet他在Tomcat中是一個(gè)單例設(shè)計(jì)模式的實(shí)現(xiàn),那到底單例設(shè)計(jì)模式是什么,有什么用呢?咱們一起來學(xué)習(xí)
1.什么是單例設(shè)計(jì)模式
有些對象我們只需要一個(gè),比如配置文件,工具類,線程池,緩存,日志對象等等。如果我們創(chuàng)造多個(gè)實(shí)例會造成很多問題,比如占用資源過多,不一致的結(jié)果等。我們的單例設(shè)計(jì)模式就是保證實(shí)例只有一個(gè),就能很好地避免這些問題
單例設(shè)計(jì)模式有兩種:
- 餓漢式
- 懶漢式
2.餓漢式的實(shí)現(xiàn)
public class Singleton {
//1.將構(gòu)造方法設(shè)為私有,不允許外部直接創(chuàng)建對象
private Singleton(){
}
//2.創(chuàng)建一個(gè)實(shí)例
private static Singleton instance = new Singleton();
//3提供一個(gè)用于獲取實(shí)例的方法
public static Singleton getInstance(){
return instance;
}
}
public class Test {
public static void main(String[] args) {
Singleton s1 = Singleton.getInstance();
Singleton s2 = Singleton.getInstance();
//看是否指向同一個(gè)對象
System.out.println(s1 == s2 ? "是同一個(gè)實(shí)例" :"不是同一個(gè)實(shí)例");
}
}

從上面我們知道:
1.將構(gòu)造方法設(shè)為私有,不允許外部直接創(chuàng)建對象
2.創(chuàng)建一個(gè)靜態(tài)私有的實(shí)例
3.對外提供一個(gè)靜態(tài)的方法,返回這個(gè)實(shí)例
4.訪問的時(shí)候通過類名.靜態(tài)方法獲取
static修飾的是從類加載的時(shí)候就加載的,所以說當(dāng)Singleton類加載的時(shí)候就加載了這個(gè)實(shí)例;我們可以形象地把加載的這個(gè)過程稱為餓漢,因?yàn)樗I了,所以想快點(diǎn)吃到東西(從類加載的時(shí)候就加載進(jìn)來,迫不及待!imagine一下~)
3.懶漢式
public class Singleton2 {
//將構(gòu)造方法私有化
private Singleton2(){
}
//聲明類的唯一實(shí)例
private static Singleton2 instance;
//提供一個(gè)對外獲取實(shí)例的方法
public static Singleton2 getSingleton2() {
if(instance == null)
instance = new Singleton2();
return instance;
}
}
public class Test {
public static void main(String[] args) {
//餓漢式
Singleton s1 = Singleton.getInstance();
Singleton s2 = Singleton.getInstance();
//看是否指向同一個(gè)對象
System.out.println(s1 == s2 ? "是同一個(gè)實(shí)例" :"不是同一個(gè)實(shí)例");
//懶漢式
Singleton2 s3 = Singleton2.getInstance();
Singleton2 s4 = Singleton2.getInstance();
System.out.println(s3 == s4 ? "是同一個(gè)實(shí)例" :"不是同一個(gè)實(shí)例");
}
}

從上面我們知道:
1.私有化構(gòu)造函數(shù)
2.聲明一個(gè)實(shí)例
3.提供對外的方法,當(dāng)?shù)谝粋€(gè)用戶訪問的時(shí)候就實(shí)例化instance對象,當(dāng)?shù)诙€(gè)再訪問的時(shí)候就不用再創(chuàng)建了,直接返回??雌饋硎遣皇呛軕校亢竺娴娜硕疾幌雱?chuàng)建了,都拜托第一個(gè)訪問的人了!!
4.兩者的區(qū)別
1.餓漢模式加載類的時(shí)候比較慢,但運(yùn)行的時(shí)候獲取對象的速度比較快,線程安全
2.懶漢模式加載類的時(shí)候比較快,但是在運(yùn)行時(shí)獲取對象的速度比較慢,因?yàn)槲覀兊谝淮卧L問的時(shí)候要創(chuàng)建對象,所以比較慢,同時(shí)懶漢模式線程是不安全的
3.為什么說餓漢模式的線程是安全的,懶漢模式線程是不安全的?舉個(gè)例子,現(xiàn)在有兩個(gè)線程thread1,thread2,他們都訪問了上述的兩種模式的對象,因?yàn)轲I漢模式在加載的時(shí)候就實(shí)例化了,所以取到的值只有唯一一個(gè),而懶漢模式,thread1有可能在if(instance == null)的時(shí)候還沒開始實(shí)例化,thread2也加進(jìn)來,這個(gè)時(shí)候就會有兩個(gè)對象
測試餓漢式
public class TestThread implements Runnable {
@Override
public void run() {
// 測試餓漢式
Singleton s1 = Singleton.getInstance();
System.out.println("對象被創(chuàng)建" + s1 + " 當(dāng)前線程" + Thread.currentThread().getName());
System.out.println("hashCode" + s1.hashCode());
}
public static void main(String[] args) {
TestThread t1 = new TestThread();
TestThread t2 = new TestThread();
new Thread(t1).start();
new Thread(t2).start();
}
}

測試懶漢式
public class TestThread implements Runnable {
@Override
public void run() {
// 測試餓漢式
Singleton2 s2 = Singleton2.getInstance();
System.out.println("對象被創(chuàng)建" + s2 + " 當(dāng)前線程" + Thread.currentThread().getName());
System.out.println("hashCode" + s2.hashCode());
}
public static void main(String[] args) {
TestThread t1 = new TestThread();
TestThread t2 = new TestThread();
new Thread(t1).start();
new Thread(t2).start();
}
}

Look看到效果了吧!