第一種:采用靜態(tài)內(nèi)部類的寫法
public class Singleton {
private static class SingletonHandler {
private static final Singleton INSTANCE = new Singleton();
}
//默認(rèn)構(gòu)造器弄成私有全景,禁止外部調(diào)用new
private Singleton() {
}
public static final Singleton getInstance(){
return SingletonHandler.INSTANCE;
}
}
第二種:餓漢模式顯示單例模式
public class Singleton {
private static Singleton instance = new Singleton();
//默認(rèn)構(gòu)造器弄成私有,禁止外部調(diào)用new
private Singleton() {
}
public static Singleton getInstance(){
return instance;
}
}
第三種:餓漢變種實(shí)現(xiàn)單例模式
public class Singleton {
private static Singleton instance = null;
static {
instance = new Singleton();
}
//默認(rèn)構(gòu)造器弄成私有牵囤,禁止外部調(diào)用new
private Singleton() {
}
public static Singleton getInstance(){
return instance;
}
}
以上三種方式都是通過定義靜態(tài)的成員變量爸黄,以保證單例對象可以在類初始化的過程中被實(shí)例化。
以上三種有個(gè)共同特點(diǎn):
- 構(gòu)造器私有化
- 靜態(tài)屬性私有化
- 獲取實(shí)例的方法共有
這其實(shí)是利用了ClassLoader的線程安全機(jī)制揭鳞。ClassLoader的loadClass方法在加載類的時(shí)候使用了synchronized關(guān)鍵字炕贵。
所以, 除非被重寫野崇,這個(gè)方法默認(rèn)在整個(gè)裝載過程中都是線程安全的称开。所以在類加載過程中對象的創(chuàng)建也是線程安全的。
這里就引發(fā)了另一個(gè)問題類加載機(jī)制。需要另一篇文章做支撐鳖轰。
上面這三種情況不是所有的情況下是線程安全的清酥,如果采用反射機(jī)制是可以調(diào)用私有構(gòu)造器的
public class Test {
public static void main(String[] args) throws Exception {
Singleton singleton = Singleton.getInstance();
Singleton instance = Singleton.getInstance();
System.out.println(singleton == instance);
Constructor<Singleton> singletonConstructor = Singleton.class.getDeclaredConstructor();
singletonConstructor.setAccessible(true);
Singleton ref = singletonConstructor.newInstance();
System.out.println(singleton == ref);
}
}
通過反射的方式,創(chuàng)建出來的Singleton實(shí)例就不是單例的了脆霎。
還有一個(gè)情況就是序列化之后也不是單例
public class Singleton implements Serializable {
private static Singleton instance = null;
static {
instance = new Singleton();
}
//默認(rèn)構(gòu)造器弄成私有总处,禁止外部調(diào)用new
private Singleton() {
}
public static Singleton getInstance(){
return instance;
}
}
public class Test {
public static void main(String[] args) throws Exception {
Singleton instance = Singleton.getInstance();
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("test.obj"));
outputStream.writeObject(instance);
outputStream.flush();
outputStream.close();
FileInputStream inputStream = new FileInputStream("test.obj");
ObjectInputStream inputStream1 = new ObjectInputStream(inputStream);
Singleton instance1 = (Singleton)inputStream1.readObject();
inputStream1.close();
inputStream.close();
System.out.println(instance == instance1);
}
}
任何一個(gè)readObject方法,不管是顯式的還是默認(rèn)的睛蛛,它都會返回一個(gè)新建的實(shí)例鹦马,這個(gè)新建的實(shí)例不同于該類初始化時(shí)創(chuàng)建的實(shí)例。
那有沒有方法可以防止反射攻擊和序列化破壞單例模式呢忆肾?
public class Singleton implements Serializable{
private static final long serialVersionUID = -4264591697494981165L;
// 靜態(tài)內(nèi)部類
private static class SingletonHandler {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton(){
// 防止反射創(chuàng)建多個(gè)對象
if(SingletonHandler.INSTANCE!=null){
throw new RuntimeException("只能實(shí)例化一次");
}
}
public static final Singleton getInstance(){
return SingletonHandler.INSTANCE;
}
// 防止序列化創(chuàng)建多個(gè)對象,這個(gè)方法是關(guān)鍵
private Object readResolve(){
return SingletonHandler.INSTANCE;
}
}
通過這種方式就可以防止反射攻擊和序列化問題
第四種寫法:枚舉類單例
public enum EnumSingleton {
INSTANCE;
public EnumSingleton getInstance(){
return INSTANCE;
}
}
這個(gè)寫法好簡單荸频,枚舉類型其實(shí)是繼承了Enum抽象類的,而抽象類中是沒有無參構(gòu)造器客冈,所以不管你反射的時(shí)候調(diào)用無參構(gòu)造器旭从,還是調(diào)用父類的有參構(gòu)造器都會拋出異常,這樣就避免了反射攻擊场仲。也解決了序列化問題和悦。
參考:https://www.cnblogs.com/chiclee/p/9097772.html
枚舉類是JDK1.5才出現(xiàn)的,那之前的程序員面對反射攻擊和序列化問題是怎么解決的呢渠缕?其實(shí)就是像Enum源碼那樣解決的鸽素,只是現(xiàn)在可以用enum可以使我們代碼量變的極其簡潔了。
Joshua Bloch說的“單元素的枚舉類型已經(jīng)成為實(shí)現(xiàn)Singleton的最佳方法”
這里序列化Serializable 亦鳞,實(shí)現(xiàn)了這個(gè)接口之后馍忽,所以的方法和屬性都自動(dòng)序列化了,有時(shí)候我們不需要對所有的屬性都進(jìn)行序列化燕差,所以就引出了transient關(guān)鍵字遭笋。打個(gè)比方,如果一個(gè)用戶有一些敏感信息(如密碼徒探,銀行卡號等)瓦呼,為了安全起見,不希望在網(wǎng)絡(luò)操作(主要涉及到序列化操作测暗,本地序列化緩存也適用)中被傳輸吵血,這些信息對應(yīng)的變量就可以加上transient關(guān)鍵字。換句話說偷溺,這個(gè)字段的生命周期僅存于調(diào)用者的內(nèi)存中而不會寫到磁盤里持久化蹋辅。
1.一旦變量被transient修飾,變量將不再是對象持久化的一部分挫掏,該變量內(nèi)容在序列化后無法獲得訪問。
2.transient關(guān)鍵字只能修飾變量,而不能修飾方法和類褒傅。注意弃锐,本地變量是不能被transient關(guān)鍵字修飾的。變量如果是用戶自定義類變量殿托,則該類需要實(shí)現(xiàn)Serializable接口霹菊。
3.被transient關(guān)鍵字修飾的變量不再能被序列化,一個(gè)靜態(tài)變量不管是否被transient修飾支竹,均不能被序列化旋廷。
參考:https://www.cnblogs.com/lanxuezaipiao/p/3369962.html
所以第四種單例模式是線程安全的。原因就是枚舉其實(shí)底層是依賴Enum類實(shí)現(xiàn)的礼搁,這個(gè)類的成員變量都是static類型的饶碘,并且在靜態(tài)代碼塊中實(shí)例化的,和餓漢有點(diǎn)像馒吴, 所以他天然是線程安全的扎运。
第五種雙重檢查的方式(俗稱Double Check方式)實(shí)現(xiàn)單例模式
第六種使用同步代碼塊(效率很低)
public class Singleton {
private static Singleton instance=null;
private Singleton() {
}
public synchronized static Singleton getInstance(){
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
第七種使用CAS(非阻塞方式也叫樂觀鎖)實(shí)現(xiàn)單例模式
public class Singleton{
private static final AtomicReference<Singleton> INSTANCE= new AtomicReference<Singleton>();
private Singleton(){}
public static final Singleton getInstance(){
for(;;){
Singleton current = INSTANCE.get();
if(current != null){
return current;
}
current = new Singleton();
if(INSTANCE.compareAndSet(null,current)){
return current;
}
}
}
}
用CAS的好處在于不需要使用傳統(tǒng)的鎖機(jī)制來保證線程安全,CAS是一種基于忙等待的算法饮戳,依賴底層硬件的實(shí)現(xiàn)豪治,相對于鎖它沒有線程切換和阻塞的額外消耗,可以支持較大的并行度扯罐。
CAS的一個(gè)重要缺點(diǎn)在于如果忙等待一直執(zhí)行不成功(一直在死循環(huán)中)负拟,會對CPU造成較大的執(zhí)行開銷。
另外篮赢,代碼中,如果N個(gè)線程同時(shí)執(zhí)行到 singleton = new Singleton();的時(shí)候琉挖,會有大量對象被創(chuàng)建启泣,可能導(dǎo)致內(nèi)存溢出。
那這里就引出了另外一個(gè)問題就是CAS原理
什么是CAS示辈? CAS:Compare and Swap寥茫,即比較再交換。
jdk5增加了并發(fā)包java.util.concurrent.*,其下面的類使用CAS算法實(shí)現(xiàn)了區(qū)別于synchronouse同步鎖的一種樂觀鎖矾麻。JDK 5之前Java語言是靠synchronized關(guān)鍵字保證同步的纱耻,這是一種獨(dú)占鎖,也是是悲觀鎖险耀。
參考:http://www.reibang.com/p/ab2c8fce878b
第八種ThreadLocal(以空間換時(shí)間方式)實(shí)現(xiàn)單例模式
ThreadLocal會為每一個(gè)線程提供一個(gè)獨(dú)立的變量副本弄喘,從而隔離了多個(gè)線程對數(shù)據(jù)的訪問沖突。對于多線程資源共享的問題甩牺,同步機(jī)制(synchronized)采用了“以時(shí)間換空間”的方式蘑志,而ThreadLocal采用了“以空間換時(shí)間”的方式。
同步機(jī)制僅提供一份變量,讓不同的線程排隊(duì)訪問急但,而ThreadLocal為每一個(gè)線程都提供了一份變量澎媒,因此可以同時(shí)訪問而互不影響。
public class Singleton{
private static final ThreadLocal<Singleton> singleton = new ThreadLocal<Singleton>(){
@Override
protected Singleton initialValue() {
return new Singleton();
}
};
private Singleton(){}
public static Singleton getInstance(){
return singleton.get();
}
}
ThreadLocal 擴(kuò)展知識:參考http://www.reibang.com/p/3c5d7f09dfbd