設計模式-創建型-單例模式
單例模式
單例模式(Singleton Pattern)是指確保一個類在任何情況下都絕對只有一個實例,并提供一個全局訪問點。
單例模式是創建型模式,Spring框架中的ApplicationContext、J2EE中的ServletContext和ServletContextConfig、數據庫的連接池都是單例模式
1、餓漢單例模式
餓漢單例模式在類加載的時候就立即初始化,并且創建單例對象。
絕對的線程安全,在線程還沒有出現以前就實例化了,不可能存在訪問安全問題。
-
優點:沒有任何加鎖操作、執行效率比較高,用戶體驗比懶漢單例模式更好
-
缺點:類加載的時候就初始化,不管用不用都占空間,浪費內存
1.1、餓漢單例案例
經典案例:
/**
* TODO 懶漢單例模式
* 餓漢單例模式在類加載的時候就立即初始化,并且創建單利對象
* 絕對線程安全
* @author ss_419
* @version 1.0
* @date 2023/9/1 09:10
*/
public class HungrySingleton {
// 先靜態,后動態
// 先屬性,后方法
// 先上后下
private static final HungrySingleton HUNGRY_SINGLETON = new HungrySingleton();
private HungrySingleton() {}
// 通過getInstance返回對象實例
public static HungrySingleton getInstance(){
return HUNGRY_SINGLETON;
}
}
改進寫法,使用靜態代碼塊機制:
/**
* TODO 靜態餓漢單例模式
* 利用靜態代碼塊的機制
* 餓漢單例模式適用于單例對象較少的情況
* @author ss_419
* @version 1.0
* @date 2023/9/1 09:16
*/
public class HungryStaticSingleton {
private static final HungryStaticSingleton INSTANCE ;
/**
* 通過靜態代碼塊對類進行實例化
*/
static {
// 靜態代碼塊,在初始化之前的連接階段中的準備階段就對靜態變量分配內存、設置初始值等操作
INSTANCE = new HungryStaticSingleton();
}
/**
* 不能通過構造器對類進行實例化操作
*/
private HungryStaticSingleton() {
}
// 提供一個全局訪問點,以供實例化對象
public static HungryStaticSingleton getInstance() {
return INSTANCE;
}
}
2、懶漢單例模式
特點:被外部類調用的時候內部類才會加載
2.1、懶漢單例案例
懶漢單例模式在外部需要使用的時候才進行實例化:
/**
* TODO 懶漢單例模式
* 懶漢單例模式:在外部需要使用的時候才進行實例化
*
* @author ss_419
* @version 1.0
* @date 2023/9/1 09:20
*/
public class LazySimpleSingleton {
private LazySimpleSingleton() {
}
// 靜態塊,公共內存區域
private static LazySimpleSingleton lazy = null;
// 提供全局訪問點,實例化單例對象
public static LazySimpleSingleton getInstance() {
if (lazy == null){
// 對象沒有創建的時候才new,否則就返回之前所存在的對象
return new LazySimpleSingleton();
}
return lazy;
}
}
創建一個線程類ExectorThread:
public class ExectorThread implements Runnable{
@Override
public void run() {
// 通過懶漢單例中提供的全局訪問點創建一個單例對象
LazySimpleSingleton singleton = LazySimpleSingleton.getInstance();
// 查看線程中存在的對象
System.out.println(Thread.currentThread().getName() + ": " + singleton);
}
}
測試代碼:
@Test
public void test1(){
Thread t1 = new Thread(new ExectorThread());
Thread t2 = new Thread(new ExectorThread());
// 這里測試,出現兩個類不同的情況,單例存在線程安全 問題
t1.start();
t2.start();
System.out.println("End");
}
通過測試,上面的代碼存在線程安全問題,怎么樣才能使線程安全呢?
第一個方法:通過對獲得實例方法進行加鎖操作,保證當前線程獲得實例時,其他線程都處于阻塞狀態
package org.pp.my.design_pattern.create.singleton2.lazy;
/**
* TODO 懶漢單例模式
* 懶漢單例模式:在外部需要使用的時候才進行實例化
*
* @author ss_419
* @version 1.0
* @date 2023/9/1 09:20
*/
public class LazySimpleSingleton {
private LazySimpleSingleton() {
}
// 靜態塊,公共內存區域
private static LazySimpleSingleton lazy = null;
/**
* 為了保證懶漢單例模式在多線程環境下線程安全,通過synchronized加鎖實現
* @return
*/
public synchronized static LazySimpleSingleton getInstance() {
if (lazy == null){
// 對象沒有創建的時候才new,否則就返回之前所存在的對象
return new LazySimpleSingleton();
}
return lazy;
}
}
但是通過synchronized加鎖的方式,會使性能降低
使用雙重鎖既能兼顧線程安全又能提升程序性能:
/**
* TODO 懶漢模式雙重檢查鎖
*
* @author ss_419
* @version 1.0
* @date 2023/9/1 09:34
*/
public class LazyDoubleCheckSingleton {
private volatile static LazyDoubleCheckSingleton lazy = null;
private LazyDoubleCheckSingleton(){
}
public static LazyDoubleCheckSingleton getInstance() {
if (lazy == null){
synchronized (LazyDoubleCheckSingleton.class){
lazy = new LazyDoubleCheckSingleton();
// 1、分配內存給這個對象
// 2、初始化對象
// 3、設置lazy指向剛分配的內存地址
}
}
return lazy;
}
}
采用靜態內部類方式,避免加鎖操作
/**
* TODO 懶漢模式-靜態內部類
* 通過靜態內部類來避免使用synchronized加鎖的情況
* @author ss_419
* @version 1.0
* @date 2023/9/1 09:39
*/
public class LazyInnerClassSingleton {
// 使用LazyInnerClassSingleton,默認會先初始化內部類
// 如果沒使用,則內部類是不加載的
private LazyInnerClassSingleton(){
}
// 每一個關鍵字都不是多余的,static是為了使單例的空間共享,保證這個方法不會被重寫、重載
public static final LazyInnerClassSingleton getInstance(){
return LazyHolder.LAZY;
}
private static class LazyHolder{
private static final LazyInnerClassSingleton LAZY = new LazyInnerClassSingleton();
}
}
2.2、反射破壞單例
通過反射機制,獲取類的私有構造方法,強制訪問
/**
* 通過反射來破壞單例
*/
@Test
public void test2(){
try {
// 反射獲取單例類
Class<LazyInnerClassSingleton> clazz = LazyInnerClassSingleton.class;
// 通過反射獲取私有的構造方法
Constructor<LazyInnerClassSingleton> c = clazz.getDeclaredConstructor(null);
// 強制訪問
c.setAccessible(true);
// 暴力初始化
Object o1 = c.newInstance();
// 調用了兩次構造方法,相當于“new”了兩次,犯了原則性錯誤
Object o2 = c.newInstance();
System.out.println("o1 = " + o1);
System.out.println("o2 = " + o2);
System.out.println(o1 == o2);
}catch (Exception e){
}
}
為了避免上述情況發生,在構造器中做一些操作,一旦出現多次重復創建,則直接拋出異常
/**
* TODO 史上最強的懶漢單例模式
* 一旦出現多次重復創建,則直接拋出異常
*
* @author ss_419
* @version 1.0
* @date 2023/9/1 09:51
*/
public class LazyInnerClassNoNullSingleton {
// 使用LazyInnerClassGeneral的時候,默認會先初始化內部類
// 如果沒有使用,則內部類是不加載的
private LazyInnerClassNoNullSingleton(){
if (LazyHolder.LAZY != null){
// 不為null,說明對象已經實例化過了
throw new RuntimeException("不允許創建多個實例");
}
}
/**
* 每一個關鍵字都不是多余的
* static是為了單例的空間共享,保證這個方法不會被重寫、重載
* @return
*/
public static LazyInnerClassNoNullSingleton getInstance(){
// 在返回之前,一定會先加載內部類
return LazyHolder.LAZY;
}
/**
* 默認不加載
*/
private static class LazyHolder {
private static final LazyInnerClassNoNullSingleton LAZY = new LazyInnerClassNoNullSingleton();
}
}
posted on 2023-09-01 13:39 JavaCoderPan 閱讀(23) 評論(0) 收藏 舉報
浙公網安備 33010602011771號