- 目錄
- 一.什么是單例吗购?
- 二.有幾種医男?
- 三.應(yīng)用場(chǎng)景
- 四.注意的地方
一.什么是單例?
單例模式 保證一個(gè)類在內(nèi)存中只有一個(gè)實(shí)例(對(duì)象)捻勉,并提供一個(gè)訪問它的全局訪問點(diǎn)镀梭。
通常我們可以讓一個(gè)全局變量使得一個(gè)對(duì)象被訪問,但它不能防止你實(shí)例化多個(gè)對(duì)象踱启。一個(gè)最好的辦法就是报账,讓類自身負(fù)責(zé)保存它的唯一實(shí)例。這個(gè)類可以保證沒有其他實(shí)例可以被創(chuàng)建埠偿,并且它可以提供一個(gè)訪問該實(shí)例的方法 —— 大話設(shè)計(jì)模式--第21章
二.單例有幾種透罢?
具體區(qū)分為下面幾種:
- 餓漢式
-
懶(飽)漢式
- 線程不安全(單線程下操作的)
-
線程安全
- 方法加鎖式
- 雙重判斷加鎖式DCL(Double-Check Locking)--- 方法加鎖式加強(qiáng)版
- 靜態(tài)嵌套類式(推薦使用)
- 枚舉式(推薦使用)
- 使用容器單例
不和你多bb ,上代碼
1.餓漢式
public class Signleton{
//對(duì)于一個(gè)final變量冠蒋。
// 如果是基本數(shù)據(jù)類型的變量羽圃,則其數(shù)值一旦在初始化之后便不能更改;
// 如果是引用類型的變量抖剿,則在對(duì)其初始化之后便不能再讓其指向另一個(gè)對(duì)象朽寞。
private final static Signleton instance = new Signleton();
private Signleton(){
}
public static Signleton getInstance(){
return instance;
}
}
解釋: 拿時(shí)間換空間识窿,因?yàn)閷?shí)例對(duì)象在類加載過程中就會(huì)被創(chuàng)建,在getInstance()方法中只是直接返回對(duì)象引用脑融。因?yàn)檫@種創(chuàng)建實(shí)例對(duì)象方式比較‘急’喻频,所以稱為餓漢式。
優(yōu)點(diǎn):快很準(zhǔn)肘迎,無需關(guān)心線程安全問題半抱。
缺點(diǎn):
??1.無論對(duì)象會(huì)不會(huì)被使用,在類加載的時(shí)候就創(chuàng)建對(duì)象了膜宋,這樣會(huì)降低內(nèi)存的使用率窿侈。
?? 2.如果在一個(gè)大環(huán)境下使用了過多的餓漢單例,則會(huì)生產(chǎn)出過多的實(shí)例對(duì)象秋茫,無論你是否要使用他們
2.懶(飽)漢式
(1)線程不安全(單線程下操作的)
public class Signleton{
private static Signleton instance = null;
private Signleton(){
}
public static Signleton getInstance(){
if(instance==null){
instance = new Signleton();
}
return instance;
}
}
解釋: Singleton的靜態(tài)屬性instance中史简,只有instance為null的時(shí)候才創(chuàng)建一個(gè)實(shí)例,構(gòu)造函數(shù)私有肛著,確保每次都只創(chuàng)建一個(gè)圆兵,避免重復(fù)創(chuàng)建。之所以被稱為“懶漢”枢贿,因?yàn)樗軕醒撑患敝a(chǎn)實(shí)例,在需要的時(shí)候才會(huì)生產(chǎn)局荚。
優(yōu)點(diǎn):延遲加載
缺點(diǎn):只在單線程的情況下正常運(yùn)行超凳,在多線程的情況下,就會(huì)出問題耀态。例如:當(dāng)兩個(gè)線程同時(shí)運(yùn)行到判斷instance是否為空的if語句轮傍,并且instance確實(shí)沒有創(chuàng)建好時(shí),那么兩個(gè)線程都會(huì)創(chuàng)建一個(gè)實(shí)例首装。
(2)線程安全
方法加鎖式
public class Signleton{
private static Signleton instance = null;
private Signleton(){
}
//給方法加鎖 若有ABCD線程使用 A線程先進(jìn)入 BCD線程都需要等待A線程執(zhí)行完畢釋放鎖才能獲得鎖執(zhí)行該方法
//這樣效率較低
public syschronized static Signleton getInstance(){
if(instance==null){
instance = new Signleton();
}
return instance;
}
}
解釋:給方法添加[synchronized],使之成為同步函數(shù)创夜。兩個(gè)線程同時(shí)想創(chuàng)建實(shí)例,由于在一個(gè)時(shí)刻只有一個(gè)線程能得到同步鎖仙逻,當(dāng)?shù)谝粋€(gè)線程加上鎖以后驰吓,第二個(gè)線程只能等待。第一個(gè)線程發(fā)現(xiàn)實(shí)例沒有創(chuàng)建系奉,創(chuàng)建之檬贰。第一個(gè)線程釋放同步鎖,第二個(gè)線程才可以加上同步鎖喜最,執(zhí)行下面的代碼偎蘸。由于第一個(gè)線程已經(jīng)創(chuàng)建了實(shí)例,所以第二個(gè)線程不需要?jiǎng)?chuàng)建實(shí)例。保證在多線程的環(huán)境下也只有一個(gè)實(shí)例迷雪。
優(yōu)點(diǎn): 保證了線程安全限书。延時(shí)加載,用的時(shí)候才會(huì)生產(chǎn)對(duì)象章咧。
缺點(diǎn): 需要保證同步倦西,付出效率的代價(jià)。加鎖是很耗時(shí)的赁严。扰柠。。
雙重判斷加鎖式DCL
public class Signleton {
private static Signleton instance = null;
private Signleton(){
}
//假設(shè)有ABCD 個(gè)線程 使用這個(gè)方法
public static Signleton getInstance(){
//BCD都進(jìn)入了這個(gè)方法
if(instance==null){
//而A線程已經(jīng)給第二個(gè)的判斷加鎖了
syschronized(Signleton.class){
//這時(shí)A掛起,對(duì)象instance還沒創(chuàng)建 疼约,故BCD都進(jìn)入了第一個(gè)判斷里面卤档,并排隊(duì)等待A釋放鎖
//A喚醒繼續(xù)執(zhí)行并創(chuàng)建了instance對(duì)象,執(zhí)行完畢釋放鎖程剥。
//此時(shí)到B線程進(jìn)入到第二個(gè)判斷并加鎖劝枣,但由于B進(jìn)入第二個(gè)判斷時(shí)instance 不為null了 故需要再判斷多一次 不然會(huì)再創(chuàng)建一次實(shí)例
if(instance==null){
instance = new Signleton();
}
}
}
return instance;
}
}
解釋:方法加鎖式的優(yōu)化。只有當(dāng)instance為null時(shí)织鲸,需要獲取同步鎖舔腾,創(chuàng)建一次實(shí)例。當(dāng)實(shí)例被創(chuàng)建搂擦,則無需試圖加鎖稳诚。。
優(yōu)點(diǎn): 保證了線程安全瀑踢。延時(shí)加載扳还,用的時(shí)候才會(huì)生產(chǎn)對(duì)象。進(jìn)行雙重判斷丘损,當(dāng)已經(jīng)創(chuàng)建過實(shí)例對(duì)象后就無需加鎖普办,提高效率工扎。
缺點(diǎn): 編寫復(fù)雜徘钥、難記憶。雖然是優(yōu)化加鎖式肢娘,但加鎖始終會(huì)耗時(shí)呈础。
3.靜態(tài)嵌套類式(推薦使用)
public class Signleton{
private Signleton{
}
//靜態(tài)嵌套類 這里給個(gè)鏈接 區(qū)分靜態(tài)嵌套類和內(nèi)部類[靜態(tài)嵌套類和內(nèi)部類](http://blog.csdn.net/iispring/article/details/46490319)
private static class SignletonHolder{
public static final Signleton instance = new Signleton();
}
public static Signleton getInstance(){
return SignletonHolder.instance;
ins't
}
}
解釋: 定義一個(gè)私有的靜態(tài)嵌套類,在第一次用這個(gè)嵌套類時(shí)橱健,會(huì)創(chuàng)建一個(gè)實(shí)例而钞。而類型為SingletonHolder的類,只有在Singleton.getInstance()中調(diào)用拘荡,由于私有的屬性臼节,他人無法使用SingleHolder,不調(diào)用Singleton.getInstance()就不會(huì)創(chuàng)建實(shí)例。
優(yōu)點(diǎn): 保證了線程安全网缝。無需加鎖巨税,延遲加載,第一次調(diào)用Singleton.getInstance才創(chuàng)建實(shí)例粉臊。推薦使用草添。
缺點(diǎn): 無。
4.枚舉式(推薦使用)
為了方便理解枚舉式 這邊簡(jiǎn)單介紹一下枚舉(在jdk1.5后)
//枚舉Type
public enum Type{
A,B,C;
private String type;
Type(type){
this.type = type;
}
public String getType(){
return type;
}
}
//可認(rèn)為等于下面的
public class Type{
public static final Type A=new Type(A);
public static final Type B=new Type(B);
public static final Type C=new Type(C);
ins't
}
所以Type.A.getType()為A.
推薦去了解一下
Java學(xué)習(xí)整理系列之Java枚舉類型的使用
Java學(xué)習(xí)整理系列之Java枚舉類型的原理
好了 開始介紹枚舉式了 看代碼
public class Signleton{
public static Signleton getInstance(){
return SignletonEnum.INSTANCE.getInstance();
}
public enum SignletonEnum{
INSTANCE;
private Signleton instance;
//由于JVM只會(huì)初始化一次枚舉實(shí)例扼仲,所以instance無需加static
private SignletonEnum(){
instance = new Signleton();
}
public getInstance(){
return instance;
}
}
}
解釋: 定義內(nèi)部的枚舉远寸,由于類加載時(shí)JVM只會(huì)初始化一次枚舉實(shí)例,所以在構(gòu)造函數(shù)中創(chuàng)建Signgleton對(duì)象并保證了這個(gè)對(duì)象實(shí)例唯一屠凶。
通過調(diào)用枚舉INSTANCE方法getInstance (SignletonEnum.INSTANCE.getInstance())獲取實(shí)例對(duì)象社搅。
優(yōu)點(diǎn): 枚舉提供了序列化機(jī)制--例如在我們要通過網(wǎng)絡(luò)傳輸一個(gè)數(shù)據(jù)庫(kù)連接的句柄,會(huì)提供很多幫助磅崭。
單元素的枚舉類型已經(jīng)成為實(shí)現(xiàn)Singleton的最佳方法筋帖。Effective Java
5.使用容器單例
public class Singleton {
//用Map保存該單例
private static Map<String, Object> objMap = new HashMap<>();
private Singleton() {
}
public static void putObject(String key, String instance){
if(!objMap.containsKey(key)){
objMap.put(key, instance);
}
}
public static Object getObject(String key){
return objMap.get(key);
}
}
解釋: 在程序開始的時(shí)候?qū)卫愋妥⑷氲揭粋€(gè)容器之中 ,在使用的時(shí)候再根據(jù) key 值獲取對(duì)應(yīng)的實(shí)例,這種方式可以使我們很方便的管理很多單例對(duì)象,也對(duì)用戶隱藏了具體實(shí)現(xiàn)類,降低了耦合度。
缺點(diǎn): 會(huì)造成內(nèi)存泄漏贱枣。(所以我們一般在生命周期銷毀的時(shí)候也要去銷毀它) 监署。
三.應(yīng)用場(chǎng)景
一般創(chuàng)建一個(gè)對(duì)象需要消耗過多的資源,如:訪問I0和數(shù)據(jù)庫(kù)等資源或者有很多個(gè)地方都用到了這個(gè)實(shí)例。
四.注意的地方
雙重判斷加鎖式DCL :這種寫法也并不是保證完全100%的可靠,由于 java 編譯器允許執(zhí)行無序,并且 jdk1.5之前的jvm ( java 內(nèi)存模型)中的 Cache,寄存器到主內(nèi)存的回寫順序規(guī)定,第二個(gè)和第三個(gè)執(zhí)行是無法保證按順序執(zhí)行的,也就是說有可能1-2-3也有可能是1-3-2; 這時(shí)假如有 A 和 B 兩條線程, A線程執(zhí)行到3的步驟,但是未執(zhí)行2,這時(shí)候 B 線程來了搶了權(quán)限,直接取走 instance 這時(shí)候就有可能報(bào)錯(cuò)纽哥。
簡(jiǎn)單總結(jié)就是說jdk1.5之前會(huì)造成兩個(gè)問題:
1钠乏、線程間共享變量不可見性;
2、無序性(執(zhí)行順序無法保證);
當(dāng)然這個(gè)bug已經(jīng)修復(fù)了,SUN官方調(diào)整了JVM,具體了Volatile關(guān)鍵字,因此在jdk1.5之前只需要寫成這樣既可, private Volatitle static Singleton instance; 這樣就可以保證每次都是從主內(nèi)存中取,當(dāng)然這樣寫或多或少的回影響性能,但是為了安全起見,這點(diǎn)性能犧牲還是值得春塌。