單例設(shè)計(jì)模式的意思就是,確保一個(gè)類(lèi)只能有一個(gè)對(duì)象。任何別的方法什么的對(duì)該類(lèi)的操作全是對(duì)這一個(gè)對(duì)象的操作。如同我們電腦上的回收站。無(wú)論那個(gè)盤(pán)都有一個(gè)回收站的空間。但都是同一個(gè)??梢栽诨厥照纠锶珓h除了。Jvm里面的GC也是一個(gè)意思。
單例設(shè)計(jì)模式有兩種情況。
一、懶漢式
- 聲明一個(gè)私有的靜態(tài)變量
- 構(gòu)造器私有化
- 創(chuàng)建一個(gè)對(duì)外的公有靜態(tài)方法訪(fǎng)問(wèn)該變量,如果變量不存在,創(chuàng)建該對(duì)象。如果存在則返回這個(gè)私有的靜態(tài)變量。這樣就能保證外部在使用該類(lèi)的對(duì)象時(shí)只會(huì)用同一個(gè)。
基本的懶漢式
class Jvm{
//聲明一個(gè)私有的靜態(tài)變量
private static Jvm instance;
//構(gòu)造器私有化
private Jvm(){
}
//創(chuàng)建一個(gè)對(duì)外的公有靜態(tài)方法訪(fǎng)問(wèn)該變量,如果變量不存在,創(chuàng)建該對(duì)象
public static Jvm getJvm(){
if(null==instance){
instance=new Jvm();
}
return instance;
}
}
這樣在執(zhí)行下列語(yǔ)句
Jvm jvm1=Jvm.getJvm();
Jvm jvm2=Jvm.getJvm();
System.out.println(jvm1);
System.out.println(jvm2);
他們得到的對(duì)象都是一樣的了。地址一樣即為同一個(gè)對(duì)象。
synTest.Jvm@15db9742
synTest.Jvm@15db9742
但是這樣的懶漢式卻是不安全的。如果是多線(xiàn)程來(lái)使用這個(gè)類(lèi),就會(huì)存在延遲??赡軙?huì)產(chǎn)生多個(gè)對(duì)象。
這里我們放大他的延遲時(shí)間更容易的得到錯(cuò)誤的結(jié)果。
修改上面的Jvm類(lèi)。給他設(shè)置一個(gè)延遲時(shí)間
class Jvm1{
private static Jvm1 instance;
private Jvm1() {
}
public static Jvm1 getJvm(long time) {
if (null == instance) {
try {
Thread.sleep(time); //這里由外部給定延遲時(shí)間
} catch (InterruptedException e) {
e.printStackTrace();
}
instance = new Jvm1();
}
return instance;
}
}
創(chuàng)建使用這個(gè)Jvm1的線(xiàn)程
class JvmThread extends Thread{
private long time;
public JvmThread() {
}
//為方便查看,這里構(gòu)造器也傳個(gè)name,給這個(gè)線(xiàn)程設(shè)置名字。
public JvmThread(long time,String name){
this.time=time;
//父類(lèi)繼承了私有的name,直接設(shè)置即可
super.setName(name);
}
public void run() {
System.out.println(Thread.currentThread().getName()+"創(chuàng)建了:"+Jvm1.getJvm(this.time));
//這里直接在線(xiàn)程里面輸出地址
}
}
執(zhí)行下列語(yǔ)句
JvmThread jth1=new JvmThread(500,"a");
JvmThread jth2=new JvmThread(1000,"b");
jth1.start();
jth2.start();
上面兩個(gè)線(xiàn)程都使用了Jvm1類(lèi)。構(gòu)造了。按照單例模式的要求,對(duì)象地址應(yīng)該是一樣的。但結(jié)果如下
a創(chuàng)建了:synTest.Jvm1@43e8772d
b創(chuàng)建了:synTest.Jvm1@20b708e4
地址卻不一樣,下面分析

所以這里就需要用到同步措施,使線(xiàn)程變得安全。
比較簡(jiǎn)單的方法是直接使用同步方法,或同步塊
class Jvm1{
private static Jvm1 instance;
private Jvm1() {
}
//這個(gè)方法改成同步方法即可
public static synchronized Jvm1 getJvm1(long time) {
if (null == instance) {
try {
Thread.sleep(time); //這里由外部給定延遲時(shí)間
} catch (InterruptedException e) {
e.printStackTrace();
}
instance = new Jvm1();
}
return instance;
}
}
或者同步塊
public static Jvm1 getJvm1(long time) {
synchronized(Jvm1.class){
if (null == instance) {
try {
Thread.sleep(time); //這里由外部給定延遲時(shí)間
} catch (InterruptedException e) {
e.printStackTrace();
}
instance = new Jvm1();
}
return instance;
}
}
但是這樣子設(shè)置會(huì)出現(xiàn)一個(gè)問(wèn)題,每一個(gè)線(xiàn)程調(diào)用這個(gè)方法都得等待,即使已經(jīng)有對(duì)象了,還需要等待。
稍作改進(jìn),人稱(chēng)double check
public static Jvm1 getJvm1(long time) {
if(null==instance){
synchronized(Jvm1.class){
if (null == instance) {
try {
Thread.sleep(time); //這里由外部給定延遲時(shí)間
} catch (InterruptedException e) {
e.printStackTrace();
}
instance = new Jvm1();
}
}
}
return instance;
}
這樣子,如果線(xiàn)程調(diào)用時(shí),已經(jīng)有對(duì)象的時(shí)候,就可以直接返回啦。不需要進(jìn)去等待。而如果多個(gè)線(xiàn)程同時(shí)在沒(méi)有對(duì)象時(shí)調(diào)用。也會(huì)進(jìn)入那個(gè)synchronized塊。不會(huì)生成多個(gè)對(duì)象。
<br />
二、餓漢式
1.構(gòu)造器私有化
2.聲明私有的靜態(tài)屬性,同時(shí)創(chuàng)建對(duì)象
3.對(duì)外提供訪(fǎng)問(wèn)屬性的靜態(tài)方法
餓漢式由于對(duì)象直接構(gòu)建了,所以調(diào)用都是調(diào)用那個(gè)對(duì)象,不存在線(xiàn)程安全問(wèn)題了。
餓漢式也有普通的和完善的。
基本餓漢式
class MyJvm{
//聲明私有的靜態(tài)變量,同時(shí)構(gòu)建對(duì)象
private static MyJvm instance=new MyJvm();
//構(gòu)造器私有
private MyJvm() {
}
public static MyJvm getMyJvm(){
return instance;
}
}
這里有一點(diǎn)不好的就是,在聲明MyJvm時(shí)候,就構(gòu)造了他的對(duì)象。如果MyJvm里面還有其他的靜態(tài)方法的話(huà),并且也只使用其他方法,那么那個(gè)對(duì)象的必要性就沒(méi)有了。所以做如下改進(jìn)。
class MyJvm2{
//這里定義一個(gè)靜態(tài)內(nèi)部類(lèi)。類(lèi)是在調(diào)用的時(shí)候才會(huì)加載的。
//當(dāng)調(diào)用了下面的getMyJvm2方法時(shí),才會(huì)用到MyJvm2Holder類(lèi),才會(huì)加載這個(gè)類(lèi)。就會(huì)有那個(gè)對(duì)象了。
private static class MyJvm2Holder{
private static MyJvm2 instance=new MyJvm2();
}
private MyJvm2() {
}
public static MyJvm2 getMyJvm2(){
return MyJvm2Holder.instance;
}
}