
定義:一個(gè)類只有一個(gè)實(shí)例,并且該類可以自行創(chuàng)建這個(gè)實(shí)例的一種模式。
優(yōu)點(diǎn):
- 減少內(nèi)存資源
- 保證數(shù)據(jù)內(nèi)容一致性
缺點(diǎn):
- 單例模式一般沒(méi)有接口,擴(kuò)展困難,如果要擴(kuò)展需要修改原來(lái)的代碼,違反開(kāi)閉原則
- 并發(fā)測(cè)試,單例不利于代碼調(diào)試。
應(yīng)用場(chǎng)景:
- 需要頻繁創(chuàng)建的一些類,可以考慮使用單例
- 某類只要求生成一個(gè)對(duì)象的時(shí)候,如一個(gè)班中的班長(zhǎng)、每個(gè)人的身份證號(hào)等。
- 某些類創(chuàng)建實(shí)例時(shí)占用資源較多,或?qū)嵗臅r(shí)較長(zhǎng),且經(jīng)常使用。
- 某類需要頻繁實(shí)例化,而創(chuàng)建的對(duì)象又頻繁被銷毀的時(shí)候,如多線程的線程池、網(wǎng)絡(luò)連接池等。
- 頻繁訪問(wèn)數(shù)據(jù)庫(kù)或文件的對(duì)象。
- 對(duì)于一些控制硬件級(jí)別的操作,或者從系統(tǒng)上來(lái)講應(yīng)當(dāng)是單一控制邏輯的操作,如果有多個(gè)實(shí)例,則系統(tǒng)會(huì)完全亂套。
- 當(dāng)對(duì)象需要被共享的場(chǎng)合。由于單例模式只允許創(chuàng)建一個(gè)對(duì)象,共享該對(duì)象可以節(jié)省內(nèi)存,并加快對(duì)象訪問(wèn)速度。如 Web 中的配置對(duì)象、數(shù)據(jù)庫(kù)的連接池等。
如何手寫(xiě)一個(gè)單例?
三步走
構(gòu)造器私有化
自行創(chuàng)建,用靜態(tài)變量保存
提供獲取方法(靜態(tài)方法)
上代碼。。。。
/**
* 餓漢式
* 優(yōu)點(diǎn):效率高,線程安全
* 缺點(diǎn):占用內(nèi)存
*/
package com.yang.singleton;
public class Singleton01 {
//自行創(chuàng)建,用靜態(tài)變量保存
private static Singleton01 instance = new Singleton01();
//構(gòu)造器私有化
private Singleton01() {
}
//向外提供獲取方法
public static Singleton01 getInstance() {
return instance;
}
}
一分鐘解決戰(zhàn)斗。。。
測(cè)試一下
package com.yang.singleton;
class Test {
public static void main(String[] args) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + ": " + Singleton01.getInstance());
}, "線程1").start();
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + ": " + Singleton01.getInstance());
}, "線程2").start();
}
}
線程1: com.yang.singleton.Singleton01@6218fb2e
線程2: com.yang.singleton.Singleton01@6218fb2e
測(cè)試結(jié)果沒(méi)問(wèn)題,類加載的時(shí)候就已經(jīng)初始化創(chuàng)建實(shí)例 ,所以不存在訪問(wèn)線程安全問(wèn)題,但是有一個(gè)缺點(diǎn),如果這個(gè)類沒(méi)有被使用,就會(huì)一直占用內(nèi)存資源,造成內(nèi)存浪費(fèi)
優(yōu)化一下。。。
/**
* 懶漢式
* 優(yōu)點(diǎn):調(diào)用時(shí)才創(chuàng)建實(shí)例
* 缺點(diǎn):線程不安全
*/
package com.yang.singleton;
public class Singleton02 {
private static Singleton02 instance = null;
private Singleton02() {
}
//修改成需要使用時(shí),創(chuàng)建對(duì)象,節(jié)省內(nèi)存空間
public static Singleton02 getInstance() {
if (instance == null) {
instance = new Singleton02();
}
return instance;
}
}
測(cè)試一下。。。
package com.yang.singleton;
class Test {
public static void main(String[] args) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + ": " + Singleton02.getInstance());
}, "線程1").start();
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + ": " + Singleton02.getInstance());
}, "線程2").start();
}
}
線程1: com.yang.singleton.Singleton02@6dd2a02f
線程2: com.yang.singleton.Singleton02@4920b564
結(jié)果創(chuàng)建了兩個(gè)對(duì)象。
思考后認(rèn)識(shí)到,修改為調(diào)用方法的時(shí)候創(chuàng)建對(duì)象,但是這個(gè)時(shí)候如果兩個(gè)線程同時(shí)調(diào)用getInstance()方法,此時(shí)對(duì)于兩個(gè)線程來(lái)說(shuō)instance == null都為true,兩個(gè)線程都會(huì)創(chuàng)建一個(gè)新的對(duì)象,就會(huì)有線程安全問(wèn)題。
知道問(wèn)題所在,再稍作修改。。。
/**
* 懶漢式
* 優(yōu)點(diǎn):線程安全
* 缺點(diǎn):性能差
*/
package com.yang.singleton;
public class Singleton02 {
private static Singleton02 instance = null;
private Singleton02() {
}
public static synchronized Singleton02 getInstance() {
if (instance == null) {
instance = new Singleton02();
}
return instance;
}
}
測(cè)試結(jié)果
線程1: com.yang.singleton.Singleton02@6dd2a02f
線程2: com.yang.singleton.Singleton02@6dd2a02f
在getInstance()方法上加 synchronized關(guān)鍵字(為方法加鎖),好像可以了。
但是只有在第一次實(shí)例化的時(shí)候才有必要加鎖保證單例,感覺(jué)沒(méi)有必要每次調(diào)用getInstance()方法都讓它排隊(duì)等待,安全性保證了,但是影響了程序的性能。
將鎖的粒度調(diào)整一下
/**
* DCL(Double Check Lock)
* 優(yōu)點(diǎn):線程安全,調(diào)用時(shí)創(chuàng)建實(shí)例
* 缺點(diǎn):復(fù)雜,可讀性差
*/
package com.yang.singleton;
public class Singleton02 {
private volatile static Singleton02 instance = null;
private Singleton02() {
}
public static Singleton02 getInstance() {
//判斷是否需要阻塞
if (instance == null) {
//線程1,線程2。。。在這里等待。。
synchronized (Singleton02.class) {
//判斷是否別的線程已經(jīng)創(chuàng)建了實(shí)例
if (instance == null) {
instance = new Singleton02();
}
}
}
return instance;
}
}
線程1: com.yang.singleton.Singleton02@6dd2a02f
線程2: com.yang.singleton.Singleton02@6dd2a02f
我們真正想要加鎖的其實(shí)只是創(chuàng)建實(shí)例這一步,在多個(gè)線程進(jìn)入方法時(shí),先判斷instance == null,如果為true將接下來(lái)的動(dòng)作加鎖,否則直接返回,極大的提高了效率,第二個(gè)instance == null判斷是否其它線程已經(jīng)創(chuàng)建了實(shí)例,有則直接返回。
- 第一個(gè)
instance == null判斷是否需要阻塞 - 第二個(gè)
instance == null判斷是否別的線程已經(jīng)創(chuàng)建了實(shí)例
細(xì)心的人會(huì)發(fā)現(xiàn)不僅僅是加了兩層判空,在變量上還加了volatile關(guān)鍵字
volatile作用:
- 保證變量可見(jiàn)性
- 禁止指令重排序
簡(jiǎn)單解釋一下,想要詳細(xì)了解可自行百度。
可見(jiàn)性就如字面意思,當(dāng)一個(gè)線程改變了變量的值,其它持有該變量的線程能夠立即感知到,避免了臟讀的現(xiàn)象。
指令重排,Java代碼最終會(huì)被轉(zhuǎn)化為字節(jié)碼,JVM通過(guò)解釋字節(jié)碼將其翻譯成一條條對(duì)應(yīng)的機(jī)器指令。
創(chuàng)建對(duì)象的過(guò)程大致分為以下幾步:
- 申請(qǐng)分配內(nèi)存空間
- 初始化對(duì)象
- 設(shè)置對(duì)象(instance )指向剛剛分配的內(nèi)存地址
正常執(zhí)行順序應(yīng)該是1-->2-->3,但是當(dāng)2初始化對(duì)象的時(shí)間過(guò)長(zhǎng)時(shí),編譯器會(huì)交換2和3的順序,
導(dǎo)致執(zhí)行最終執(zhí)行的順序?yàn)?-->3-->2,當(dāng)執(zhí)行到3的時(shí)候另外的線程判斷instance == null不成立(此時(shí)instance 不為空,已經(jīng)指向內(nèi)存地址),直接返回沒(méi)有初始化的instance,造成NPE
加上volatile可以禁止2和3交換順序,按順序執(zhí)行,避免NPE
最終得到了一個(gè)線程安全,省內(nèi)存的單例
但是防這防那的,未免太過(guò)復(fù)雜。有鎖就會(huì)有性能問(wèn)題
有沒(méi)有不加鎖的方法?
重新整理一下思路。
以餓漢式為始,后面衍生出來(lái)的一系列問(wèn)題都是由于想要將創(chuàng)建實(shí)例的動(dòng)作滯后,達(dá)到節(jié)省內(nèi)存的目的,那么有沒(méi)有別的方法可以讓我們?cè)谡{(diào)用的時(shí)候創(chuàng)建。
可以利用靜態(tài)內(nèi)部類
調(diào)用的時(shí)候加載
上代碼。。。
/**
* 靜態(tài)內(nèi)部類
* 優(yōu)點(diǎn):線程安全,調(diào)用時(shí)創(chuàng)建實(shí)例
*/
package com.yang.singleton;
public class Singleton03 {
private Singleton03() {
}
public static Singleton03 getInstance() {
return InnerSingleton.INSTANCE;
}
private static class InnerSingleton {
private static final Singleton03 INSTANCE = new Singleton03();
}
}
線程2: com.yang.singleton.User@4920b564
線程1: com.yang.singleton.User@4920b564
至此,單例可以告一段落
---------------------------------------------------無(wú)敵分割線---------------------------------------------
下面補(bǔ)充幾種網(wǎng)上搜集來(lái)的單例寫(xiě)法
枚舉
/**
* 枚舉
* 優(yōu)點(diǎn):線程安全,防止反序列化
*/
package com.yang.singleton;
public class User {
//私有化構(gòu)造函數(shù)
private User() {
}
//定義一個(gè)靜態(tài)枚舉類
static enum SingletonEnum {
//創(chuàng)建一個(gè)枚舉對(duì)象,該對(duì)象天生為單例
INSTANCE;
private User user;
//私有化枚舉的構(gòu)造函數(shù)
private SingletonEnum() {
user = new User();
}
public User getInstnce() {
return user;
}
}
//對(duì)外暴露一個(gè)獲取User對(duì)象的靜態(tài)方法
public static User getInstance() {
return SingletonEnum.INSTANCE.getInstnce();
}
}
線程2: com.yang.singleton.User@2fec69f9
線程1: com.yang.singleton.User@2fec69f9
容器式單例
/**
* 容器式單例
* 實(shí)現(xiàn)思路:用一個(gè)Map保存對(duì)象,當(dāng)需要使用的時(shí)候去map里拿,如果有,則直接取出來(lái),沒(méi)有則利用返回創(chuàng)建一個(gè),放到map里,然后再返回出去。(相當(dāng)于一個(gè)簡(jiǎn)單的Spring管理容器)
*/
package com.yang.singleton;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class ContainerSingleton {
private ContainerSingleton() {
}
private static Map<String, Object> ioc = new ConcurrentHashMap<String, Object>();
public static Object getInstance(String className) {
Object instance = null;
if (!ioc.containsKey(className)) {
try {
instance = Class.forName(className).newInstance();
ioc.put(className, instance);
} catch (Exception e) {
e.printStackTrace();
}
return instance;
} else {
return ioc.get(className);
}
}
}
package com.yang.singleton;
class Test {
public static void main(String[] args) {
Object instance1 = ContainerSingleton.getInstance("com.yang.singleton.TestPojo");
Object instance2 = ContainerSingleton.getInstance("com.yang.singleton.TestPojo");
System.out.println("instance1: " + instance1);
System.out.println("instance2: " + instance2);
}
}
instance1: com.yang.singleton.TestPojo@61bbe9ba
instance2: com.yang.singleton.TestPojo@61bbe9ba
但是上述示例存在線程安全問(wèn)題
看看在多線程環(huán)境下測(cè)試結(jié)果
package com.yang.singleton;
class Test {
public static void main(String[] args) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + ": " + ContainerSingleton.getInstance("com.yang.singleton.TestPojo"));
}, "線程1").start();
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + ": " + ContainerSingleton.getInstance("com.yang.singleton.TestPojo"));
}, "線程2").start();
}
}
線程1: com.yang.singleton.TestPojo@6dd2a02f
線程2: com.yang.singleton.TestPojo@4920b564
簡(jiǎn)單修改一下
package com.yang.singleton;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class ContainerSingleton {
private ContainerSingleton() {
}
private volatile static Map<String, Object> ioc = new ConcurrentHashMap<String, Object>();
public static Object getInstance(String className) {
Object instance = null;
//如果容器里沒(méi)有,判斷是否需要加鎖
if (!ioc.containsKey(className)) {
synchronized (ContainerSingleton.class) {
if (!ioc.containsKey(className)) {
try {
instance = Class.forName(className).newInstance();
ioc.put(className, instance);
} catch (Exception e) {
e.printStackTrace();
}
return instance;
} else {
return ioc.get(className);
}
}
} else {
return ioc.get(className);
}
}
}
線程1: com.yang.singleton.TestPojo@43d83830
線程2: com.yang.singleton.TestPojo@43d83830
CAS單例
/**
* CAS單例
* 思路:用AtomicReference包裝保證原子性,然后利用無(wú)鎖機(jī)制,循環(huán)去判斷是否有現(xiàn)成的對(duì)象,有就返回,沒(méi)有就創(chuàng)建,但后替INSTANCE
*/
package com.yang.singleton;
import java.util.concurrent.atomic.AtomicReference;
public class CASSingleton {
private static final AtomicReference<CASSingleton> INSTANCE = new AtomicReference<CASSingleton>();
private CASSingleton() {
}
public static CASSingleton getInstance() {
for (; ; ) {
CASSingleton singleton = INSTANCE.get();
if (singleton != null) {
return singleton;
}
singleton = new CASSingleton();
if (INSTANCE.compareAndSet(null, singleton)) {
return singleton;
}
}
}
}