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