java单例模式的七种写法_Java设计模式之单例模式的七种写法
什么是單例模式?
單例模式是一種常見的設(shè)計(jì)模式,單例模式的寫法有很多種,這里主要介紹三種: 懶漢式單例模式、餓漢式單例模式、登記式單例 。
單例模式有以下特點(diǎn):
1、單例類只能有一個(gè)實(shí)例。
2、單例類必須自己創(chuàng)建自己唯一的實(shí)例。
3、單例類必須給所有其它對(duì)象提供這一實(shí)例。
單例模式確保某各類只有一個(gè)實(shí)例,而且自行實(shí)例化并向整個(gè)系統(tǒng)提供這個(gè)實(shí)例。在計(jì)算機(jī)系統(tǒng)中,線程池、緩存、日志對(duì)象、對(duì)話框、打印機(jī)、顯卡的驅(qū)動(dòng)程序?qū)ο蟪1辉O(shè)計(jì)成單例。這些應(yīng)用都或多或少具有資源管理器的功能,每臺(tái)計(jì)算機(jī)可以有若干個(gè)打印機(jī),但只能有一個(gè)Printer spooler,以避免兩個(gè)打印作業(yè)同時(shí)輸出到打印機(jī)中,每臺(tái)計(jì)算機(jī)可以有若干通信端口,系統(tǒng)應(yīng)當(dāng)集中管理這些通信端口,以避免一個(gè)通信端口同時(shí)被兩個(gè)請(qǐng)求同時(shí)調(diào)用。總之,選擇單例模式就是為了避免不一致狀態(tài).
在將單例之前,要做一次基礎(chǔ)知識(shí)的科普行動(dòng),大家都知道Java類加載器加載內(nèi)容的順序:
1、從上往下(Java的變量需要聲明才能使用)
2、先靜態(tài)后動(dòng)態(tài)(對(duì)象實(shí)例化) (靜態(tài)塊和static關(guān)鍵字修飾在實(shí)例化以前分配內(nèi)存空間)
3、先屬性后方法(成員變量不能定義在方法中,只能定義在class下)
懶漢式單例(4種寫法)
懶漢式顧名思義:需要用到的時(shí)候才會(huì)初始化
餓漢式:不管用不用先實(shí)例化
注冊(cè)登記式:相當(dāng)于有一個(gè)容器裝載實(shí)例,在實(shí)例產(chǎn)生之前會(huì)先檢查一下容器看有沒有,如果有就直接取出來使用,如果沒有就先new一個(gè)放進(jìn)去,然后在后面的人使用,Spring IOC就是一種典型的注冊(cè)登記單例
第一種寫法:
/**
* Created by xingyuchao on 2018/1/20.
* 懶漢式單例類,在第一次使用的時(shí)候?qū)嵗约?/p>
*/
public class Singleton {
//1.第一步先將構(gòu)造方法私有化
private Singleton(){}
//2.然后聲明一個(gè)靜態(tài)變量保存單例的引用
private static Singleton single = null;
//3.通過提供一個(gè)靜態(tài)方法來獲得單例的引用
public static Singleton getInstance(){
if(single == null){
single = new Singleton();
}
return single;
}
}
Singleton1通過將構(gòu)造方法限定為private避免了類在外部被實(shí)例化,在同一個(gè)虛擬機(jī)范圍內(nèi),Signleton1的唯一實(shí)例只能通過getInstance()方法訪問。
事實(shí)上,通過Java反射機(jī)制是能否實(shí)現(xiàn)實(shí)例化構(gòu)造方法為private的類的,那基本上會(huì)使所有的Java單例實(shí)現(xiàn)失效,此問題在此處不做討論
但是以上懶漢式單例的實(shí)現(xiàn)沒有考慮線程安全問題,它是非線程安全的,并發(fā)環(huán)境下可能出現(xiàn)多個(gè)Singleton1實(shí)例,要實(shí)現(xiàn)線程安全,有以下三種方式,都是對(duì)getInstance這個(gè)方法的改造,保證了懶漢式單例的線程安全.
第二種寫法:在getInstance()方法上加同步
/**
* Created by xingyuchao on 2018/1/29.
* 懶漢式單例類,保證線程安全
*/
public class Singleton2 {
//1.第一步先將構(gòu)造方法私有化
private Singleton2(){}
//2.然后聲明一個(gè)靜態(tài)變量保存單例的引用
private static Singleton2 single = null;
//3.通過提供一個(gè)靜態(tài)方法來獲得單例的引用
// 為了保證線程環(huán)境下正確訪問,給方法上加上同步鎖synchronized
public static synchronized Singleton2 getInstance(){
if(single == null){
single = new Singleton2();
}
return single;
}
}
第三種寫法:雙重檢測(cè)機(jī)制
/**
* Created by xingyuchao on 2018/1/29.
* 懶漢式單例類,保證線程安全 雙重檢測(cè)機(jī)制
*/
public class Singleton3 {
//1.第一步先將構(gòu)造方法私有化
private Singleton3(){}
//2.然后聲明一個(gè)靜態(tài)變量保存單例的引用
private static Singleton3 single = null;
//3.通過提供一個(gè)靜態(tài)方法來獲得單例的引用
// 為了保證線程環(huán)境下的另一種實(shí)現(xiàn)方式,雙重鎖檢查
public static synchronized Singleton3 getInstance(){
if(single == null){
single = new Singleton3();
}
return single;
}
}
第四種:靜態(tài)內(nèi)部類
/**
* Created by xingyuchao on 2018/1/29.
* 懶漢式單例類,通過靜態(tài)內(nèi)部類實(shí)現(xiàn)
*/
public class Singleton4 {
//1. 先聲明一個(gè)靜態(tài)內(nèi)部類
//內(nèi)部類的初始化,需要依賴主類
//也就是說,當(dāng)JVM加載Singleton4類的時(shí)候LazyHolder類也會(huì)被加載
//只是目前還沒有被實(shí)例化,需要等主類先實(shí)例化后,內(nèi)部類才開始實(shí)例化
private static class LazyHolder{
//final是為了防止內(nèi)部類將這個(gè)屬性值覆蓋掉
private static final Singleton4 INSTANCE = new Singleton4();
}
//2. 將默認(rèn)構(gòu)造方法私有化
private Singleton4(){}
//3.同樣提供靜態(tài)方法獲取實(shí)例
//當(dāng)getInstance方法第一次被調(diào)用的時(shí)候,它第一次讀取LazyHolder.INSTANCE,內(nèi)部類LazyHolder類得到初始化;
// 而這個(gè)類在裝載并被初始化的時(shí)候,會(huì)初始化它的靜態(tài)域,從而創(chuàng)建Singleton4的實(shí)例,由于是靜態(tài)的域,因此只會(huì)在
// 虛擬機(jī)裝載類的時(shí)候初始化一次,并由虛擬機(jī)來保證它的線程安全性。這個(gè)模式的優(yōu)勢(shì)在于,getInstance方法并沒有被同步,
// 并且只是執(zhí)行一個(gè)域的訪問,因此延遲初始化并沒有增加任何訪問成本。
//此處加final是為了防止子類重寫父類方法
public static final Singleton4 getInstance(){
return LazyHolder.INSTANCE;
}
}
餓漢式單例(1種寫法)
/**
* Created by xingyuchao on 2018/1/29.
* 餓漢式單例類,在類初始化時(shí),已經(jīng)自行初始化,不會(huì)產(chǎn)生線程安全問題
*/
public class Singleton5 {
//1.同樣也是將默認(rèn)構(gòu)造方法私有化
private Singleton5(){}
//2.聲明靜態(tài)變量,在類初始化之前就初始化變量,將對(duì)象引用保存
//相反的如果這個(gè)單例對(duì)象一直沒使用,那么內(nèi)存空間也就被浪費(fèi)掉了
private static final Singleton5 singleton = new Singleton5();
//3.開放靜態(tài)方法,獲取實(shí)例
public static Singleton5 getSingleton(){
return singleton;
}
}
枚舉式單例(1種寫法)
public class DBConnection {}
/**
* Created by xingyuchao on 2018/1/29.
* 枚舉式單例
*/
public enum Singleton6 {
DATASOURCE;
private DBConnection connection = null;
private Singleton6() {
connection = new DBConnection();
}
public DBConnection getConnection() {
return connection;
}
}
/**
* Created by xingyuchao on 2018/1/29.
*/
public class Main {
public static void main(String[] args) {
DBConnection dbConnection1 = Singleton6.DATASOURCE.getConnection();
DBConnection dbConnection2 = Singleton6.DATASOURCE.getConnection();
System.out.println(dbConnection1 == dbConnection2); //true true結(jié)果表明兩次獲取返回了相同的實(shí)例。
}
}
這種方式不僅能避免多線程同步問題,而且能防止反射創(chuàng)建新的對(duì)象,可謂是很堅(jiān)強(qiáng)的壁壘不過這種方式用的極少
為什么枚舉會(huì)滿足線程安全、序列化等標(biāo)準(zhǔn)。參考:http://blog..net/gavin_dyson/article/details/70832185
登記注冊(cè)式單例
/**
* Created by xingyuchao on 2018/1/29.
* 登記式單例:類似spring里面的方法,將類名注冊(cè),下次直接從里面獲取
*
* 登記式單例實(shí)際上維護(hù)了一組單例類的實(shí)例,將這些實(shí)例存放在一個(gè)Map(登記簿)中,對(duì)于已經(jīng)登記過的實(shí)例,則從Map直接獲取,對(duì)于沒有登記的,則先登記,然后返回
*
* 內(nèi)部實(shí)現(xiàn)還是用的餓漢式單例,因?yàn)槠渲械膕tatic方法塊,它的單例在被裝載的時(shí)候就被實(shí)例化了
*/
public class Singleton7 {
private static Map map = new HashMap<>();
static{
Singleton7 singleton7 = new Singleton7();
map.put(singleton7.getClass().getName(),singleton7);
}
//保護(hù)的默認(rèn)構(gòu)造
protected Singleton7(){}
//靜態(tài)工程方法,返回此類的唯一實(shí)例
public static Singleton7 getInstance(String name) throws Exception {
if(name == null){
name = Singleton7.class.getName();
}
if(map.get(name) == null){
map.put(name,(Singleton7)Class.forName(name).newInstance());
}
return map.get(name);
}
}
測(cè)試:
public class Test {
public static void main(String[] args) throws Exception{
//啟動(dòng)100個(gè)線程同時(shí)去搶占cpu ,有可能產(chǎn)生并發(fā),觀察并發(fā)情況下是否為同一個(gè)對(duì)象實(shí)例
int count = 100;
//發(fā)令槍
CountDownLatch latch = new CountDownLatch(count);
for (int i = 0; i < count; i++){
//Lambda簡化后
new Thread(()->{
System.out.println(System.currentTimeMillis() + ":" + Singleton4.getInstance());
}).start();
latch.countDown();
}
latch.await(); //開始法令,搶占cpu
}
}
結(jié)果:
分布式環(huán)境下的單例
有兩個(gè)問題需要注意:
1. 如果單例類由不同的類裝載器裝載,那邊可能存在多個(gè)單例類的實(shí)例。假定不是遠(yuǎn)端存取,例如有一些servlet容器對(duì)每個(gè)servlet使用完全不同的類裝載器,這樣的話如果有兩個(gè)servlet訪問一個(gè)單例類,它們就會(huì)有各自的實(shí)例
解決:指定classloader
private static Class getClass(String classname) throws ClassNotFoundException{
ClassLoader classloader = Thread.currentThread().getContextClassLoader();
if(classloader == null){
classloader = Singleton.class.getClassLoader();
}
return (classloader.loadClass(classname));
}
2. 如果Singleton實(shí)現(xiàn)了java.io.Serializable接口,那么這個(gè)類的實(shí)例就可能被序列化和復(fù)原。不管怎么樣,如果你序列化一個(gè)單例類的對(duì)象,接下來復(fù)原多個(gè)那個(gè)對(duì)象,那么就會(huì)有多個(gè)類的實(shí)例
public class Singleton implements Serializable {
public static Singleton singleton = new Singleton();
protected Singleton(){}
private Object readResolve(){
return singleton;
}
}
創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎(jiǎng)勵(lì)來咯,堅(jiān)持創(chuàng)作打卡瓜分現(xiàn)金大獎(jiǎng)總結(jié)
以上是生活随笔為你收集整理的java单例模式的七种写法_Java设计模式之单例模式的七种写法的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java资源争夺_所有满足类似需求,争夺
- 下一篇: Proteus仿真单片机:PIC18单片