一、單例模式介紹
所謂類的單例設(shè)計模式缩焦,就是采取一定的方法保證在整個的軟件系統(tǒng)中读虏,對于某個類只能存在一個對象實例责静,并且該類只提供一個取得其對象實例的方法(靜態(tài)方法)。
比如Hibernate的SessionFactory盖桥,它充當(dāng)數(shù)據(jù)存儲源的代理灾螃,并負(fù)責(zé)創(chuàng)建Session對象。SessionFactory并不是輕量級的揩徊,一般情況下腰鬼,一個項目通常只需要一個SessionFactory就夠,這時就會使用到單例模式塑荒。
二熄赡、單例模式的七種方式
單例模式有以下七種方式
- 餓漢式(靜態(tài)常量)
- 餓漢式(靜態(tài)代碼塊)
- 懶漢式(線程不安全)
- 懶漢式(線程安全,同步方法)
- 懶漢式之雙重檢查(線程安全袜炕,同步代碼塊)
- 靜態(tài)內(nèi)部類
- 枚舉
1本谜、餓漢式(靜態(tài)常量)
步驟如下:
- 構(gòu)造器私有化(防止new)
- 在類的內(nèi)部創(chuàng)建對象
- 向外暴露一個靜態(tài)的公共方法(getInstance)
package com.cxc.singleton.type1;
public class SingletonTest01 {
public static void main(String[] args) {
Singleton singleton = Singleton.getInstance();
Singleton singleton1 = Singleton.getInstance();
System.out.println(singleton == singleton1);
}
}
/**
* 餓漢式(靜態(tài)變量)
*/
class Singleton{
//構(gòu)造器私有化
private Singleton(){}
//在類內(nèi)部創(chuàng)建對象實例
private final static Singleton instance = new Singleton();
//提供一個公有的靜態(tài)方法,返回實例對象
public static Singleton getInstance(){
return instance;
}
}
這種方式的優(yōu)點是寫法簡單偎窘,在類裝載的時候就完成了實例化,避免了線程同步問題溜在。
而缺點有以下:
- 在類裝載的時候就完成實例化陌知,沒有達(dá)到懶加載(Lazy Loading)的效果。如果從始至終從未使用過這個實力掖肋,則會造成內(nèi)存的浪費仆葡。
- 這種方式基于classloader機(jī)制避免了多線程的同步問題,不過志笼,instance在類裝載時就實例化沿盅,在單例模式中大多數(shù)都是調(diào)用
getInstance
方法,但是導(dǎo)致類裝載的原因有很多種纫溃,因此不能確定有其他的方式(或者其他的靜態(tài)方法)導(dǎo)致類裝載腰涧,這時候切換instance
就沒有達(dá)到懶加載的效果。 - 這種單例模式可用紊浩,但是可能造成內(nèi)存浪費窖铡。
2、餓漢式(靜態(tài)代碼塊)
與第一種方式一樣坊谁,僅僅只是將實例化對象放入一個靜態(tài)代碼塊而已费彼。
package com.cxc.singleton.type2;
public class SingletonTest02 {
public static void main(String[] args) {
Singleton singleton = Singleton.getInstance();
Singleton singleton1 = Singleton.getInstance();
System.out.println(singleton == singleton1);
}
}
/**
* 餓漢式(靜態(tài)代碼塊)
*/
class Singleton{
//構(gòu)造器私有化
private Singleton(){}
//對象實例
private static Singleton instance ;
//在靜態(tài)代碼塊中實例化對象
static{
instance = new Singleton();
}
//提供一個公有的靜態(tài)方法,返回實例對象
public static Singleton getInstance(){
return instance;
}
}
3口芍、懶漢式(線程不安全)
先不實例化對象箍铲,等到需要用到的時候再去實例化:
package com.cxc.singleton.type3;
public class SingletonTest03 {
public static void main(String[] args) {
Singleton singleton = Singleton.getInstance();
System.out.println(singleton);
}
}
/**
* 懶漢式(線程不安全)
*/
class Singleton{
private static Singleton instance;
private Singleton(){}
//提供一個靜態(tài)的公有方法,當(dāng)使用到該方法時鬓椭,才去實例化
public static Singleton getInstance(){
if (instance == null){
instance = new Singleton();
}
return instance;
}
}
優(yōu)缺點:
- 起到了懶加載的效果颠猴,但是只能在單線程下使用聋庵。
- 如果在多線程下,一個線程進(jìn)入了
if(singleton == null)
判斷語句塊芙粱,還未來得及往下執(zhí)行祭玉,另一個線程也通過了這個判斷語句,這時就會產(chǎn)生多個實例春畔。所以在多線程環(huán)境下不可使用這種方式脱货。
4、懶漢式(線程安全律姨,同步方法)
在方法上加入synchronized
同步關(guān)鍵字:
package com.cxc.singleton.type4;
public class SingletonTest04 {
public static void main(String[] args) {
Singleton singleton = Singleton.getInstance();
System.out.println(singleton);
}
}
/**
* 懶漢式(線程安全,同步方法)
*/
class Singleton{
private static Singleton instance;
private Singleton(){}
//提供一個靜態(tài)的公有方法振峻,加入了同步處理的代碼,解決線程安全問題
public static synchronized Singleton getInstance(){
if (instance == null){
instance = new Singleton();
}
return instance;
}
}
優(yōu)缺點:
- 解決了線程不安全問題择份。
- 效率太低了扣孟,每個線程在想獲得類的實例的時候,執(zhí)行
getInstance()
方法都要進(jìn)行同步荣赶,而其實這個方法只執(zhí)行一次實例化代碼就夠了凤价,后面的想獲得該類實例,直接return
就行了拔创,方法進(jìn)行同步效率太低利诺。
5、懶漢式之雙重檢查(線程安全剩燥,同步代碼塊)
在方法里面先判空后慢逾,再使用synchronized
關(guān)鍵字的同步代碼塊(鎖的是類),如果再一次判空(這里必須雙重判空灭红,不然會有線程安全問題):
package com.cxc.singleton.type5;
public class SingletonTest05 {
public static void main(String[] args) {
Singleton singleton = Singleton.getInstance();
System.out.println(singleton);
}
}
/**
* 懶漢式(線程安全,同步代碼塊)
*/
class Singleton{
private static volatile Singleton instance;
private Singleton(){}
//提供一個靜態(tài)的公有方法
public static Singleton getInstance(){
if (instance == null){
synchronized(Singleton.class){
//雙重判定
if (instance == null){
instance = new Singleton();
}
}
}
return instance;
}
}
對象加上了volatile
關(guān)鍵字是為了保證變量的可見性侣滩,防止指令重排序。
6变擒、靜態(tài)內(nèi)部類
這種方式利用到了靜態(tài)內(nèi)部類的特性:
靜態(tài)內(nèi)部類在外部類裝載時并不是立即被實例化君珠,而是需要使用到靜態(tài)內(nèi)部類時才會被加載。
package com.cxc.singleton.type6;
public class SingletonTest06 {
public static void main(String[] args) {
Singleton singleton = Singleton.getInstance();
System.out.println(singleton);
}
}
/**
* 靜態(tài)內(nèi)部類
*/
class Singleton{
private static volatile Singleton instance;
private Singleton(){}
//寫一個靜態(tài)內(nèi)部類赁项,該類中有一個靜態(tài)屬性
private static class SingletonInstance{
private static final Singleton INSTANCE = new Singleton();
}
//提供一個靜態(tài)的公有方法
public static Singleton getInstance(){
return SingletonInstance.INSTANCE;
}
}
優(yōu)缺點:
- 這種方式采用了類裝載的機(jī)制來保證初始化實例時只有一個線程葛躏。
- 靜態(tài)內(nèi)部類這種方式在Singleton類被裝載時并不是立即實例化,而是在需要實例化時悠菜,調(diào)用
getInstance
方法用到了內(nèi)部類舰攒,才會裝載SingletonInstance類,從而完成Singleton的實例化悔醋。 - 類的靜態(tài)屬性只會在第一次加載類的時候初始化摩窃,所以在這里,JVM幫助我們保證了線程的安全性,在類進(jìn)行初始化時猾愿,別的線程是無法進(jìn)入的鹦聪。
- 避免了線程不安全,利用靜態(tài)內(nèi)部類特點實現(xiàn)延遲加載蒂秘,效率高泽本。極力推薦。
7姻僧、枚舉
package com.cxc.singleton.type7;
public class SingletonTest07 {
public static void main(String[] args) {
Singleton singleton = Singleton.INSTANCE;
singleton.sayOk();
}
}
/**
* 使用枚舉可以實現(xiàn)單例模式
*/
enum Singleton{
INSTANCE;
public void sayOk(){
System.out.println("ok~");
}
}
三规丽、單例模式在JDK應(yīng)用的源碼分析
在JDK的java.lang.Runtime
類中就用到了單例模式,Runtime
的部分源碼如下:
public class Runtime {
private static Runtime currentRuntime = new Runtime();
/**
* Returns the runtime object associated with the current Java application.
* Most of the methods of class <code>Runtime</code> are instance
* methods and must be invoked with respect to the current runtime object.
*
* @return the <code>Runtime</code> object associated with the current
* Java application.
*/
public static Runtime getRuntime() {
return currentRuntime;
}
/** Don't let anyone else instantiate this class */
private Runtime() {}
......
}
Runtime
類定義了currentRuntime
對象撇贺,然后直接實例化赌莺,對其構(gòu)造方法私有化。這是屬于餓漢式單例模式松嘶。
四艘狭、單例模式注意事項和細(xì)節(jié)說明
- 單例模式保證了系統(tǒng)內(nèi)存中該類只存在一個對象,節(jié)省了系統(tǒng)資源翠订,對于一些需要頻繁創(chuàng)建銷毀的對象巢音,使用單例模式可以提高系統(tǒng)性能。
- 當(dāng)想實例化一個單例類的時候蕴轨,必須要記住使用相應(yīng)的獲取對象的方法港谊,而不是使用
new
。 - 單例模式使用的場景:需要頻繁的進(jìn)行創(chuàng)建和銷毀的對象橙弱、創(chuàng)建對象時耗時過多或耗費資源過多(即重量級對象),但又經(jīng)常用到的對象燥狰、工具類對象棘脐、頻繁訪問數(shù)據(jù)庫或文件的對象(比如數(shù)據(jù)源、session工廠等)龙致。