001設計模式:單例模式
單例模式的定義:
單例模式是指確保一個類在任何情況下都絕對只有一個例項,並提供一個全域性訪問點。隱藏其所有的構造方法。
建立模式的常見寫法
1:餓漢模式
2:懶漢模式
3:註冊模式
4:ThreadLocal單例
餓漢模式
餓漢模式單例是單例首次載入時就建立例項
/***
* 1)私有建構函式
*
* 2)靜態私有成員--在類載入時已初始化
*
* 3)公開訪問點getInstance-----不需要同步,因為在類載入時已經初始化完畢,
* 也不需要判斷null,直接返回
*
* 缺點:不管用沒用到類 都會給你初始化 浪費型別空間
*/
public class HungrySingleton implements Serializable {
/**
* 餓漢模式 直接初始化
*/
private static final HungrySingleton hungrySingleton = new HungrySingleton();
/**
* 私有化 構造方法 別人呼叫不到
*/
private HungrySingleton() {}
public static HungrySingleton getInstance(){
return hungrySingleton;
}
}
缺點:不管用沒用到例項,都會初始化,浪費型別空間。
破壞單例的方式有序列化單例。程式碼如下:
/**
* 序列化去建立單例
*
* @param args
*/
public static void main(String[] args) {
HungrySingleton s1 = null;
HungrySingleton s2 = HungrySingleton.getInstance();
FileOutputStream fos = null;
try {
fos = new FileOutputStream("HungrySingleton.obj");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(s2);
oos.flush();
oos.close();
FileInputStream fis = new FileInputStream("HungrySingleton.obj");
ObjectInputStream ois = new ObjectInputStream(fis);
s1 = (HungrySingleton)ois.readObject();
ois.close();
System.out.println("s1:"+s1);
System.out.println("s2"+s2);
System.out.print("s1和s2的比較為");
System.out.println(s1 == s2);
} catch (Exception e) {
e.printStackTrace();
}
}
執行結果為false,這是因為序列化的時候再次初始化了單例。
這時候需要在實體類中加上
原始碼分析:
ois.readObject();
-> 431行 readObject(false)
->readOrdinaryObject(unshared)
->2053行 obj = desc.isInstantiable() ? desc.newInstance() : null;
desc.isInstantiable() -> 1023 return (cons != null); //判斷是否有構造方法如果有返回 true
這裡又再次初始化了一次實體類
為了防止這種時間發生 需要在實體類中新增方法
private Object readResolve(){
return hungrySingleton;
}
原始碼分析:
ois.readObject();
-> 431行 readObject(false)
->1573行 readOrdinaryObject(unshared)
->2074行
obj != null &&handles.lookupException(passHandle) == null &&
desc.hasReadResolveMethod()
前兩個判斷條件為true 第三個判斷條件 hasReadResolveMethod() 字面意思就是看有沒有 readResolve 這個方法 如果有 繼續往下走
->2091行 handles.setObject(passHandle, obj = rep);
obj :為反序列話時候再次初始化的值
rep:為執行readResolve方法返回的值
原始碼分析可以看出實際上建立了兩次,只不過初始化的那個讓GC處理了
懶漢式單例
被外部類呼叫時才建立例項
/**
* 懶載入:
* 1) 被外部類呼叫的時候才會建立例項
* 2)私有建構函式
*/
public class LazySingleton implements Serializable{
private static LazySingleton lazySingleton;
private LazySingleton() {}
/**
* 為了執行緒安全和效能 把synchronized 放到方法裡邊
*
* @return
*/
public static LazySingleton getInstance() {
if (lazySingleton == null) {
// 如果為空建立
synchronized (LazySingleton.class) {
/**
* 這個時候在去判空一次
* 防止當lazySingleton 為空的時候 兩個執行緒 同時進入這裡
*/
if (lazySingleton == null) {
lazySingleton = new LazySingleton();
}
}
}
return lazySingleton;
}
}
注意:這裡用到雙重檢查鎖
還有一種懶載入方法:靜態內部類,靜態內部類是最高階的單例。
靜態內部類特性:只有在外部呼叫的時候,才會載入。
public class LazyInnerClassSingleton {
private static final LazyInnerClassSingleton lazyInnerClassSingleton
= new LazyInnerClassSingleton();
private LazyInnerClassSingleton() {
if (lazyHolder.LAZY != null) {
throw new RuntimeException("不允許建立");
}
}
/**
* @return
*/
public static LazyInnerClassSingleton getInstance() {
return lazyHolder.LAZY;
}
/**
* 靜態內部類
* 靜態內部類 是最高階的 單例模式
* 巧妙的利用了內部類的特性
* 只有在外部類呼叫的時候 才會去建立內部類
*/
private static class lazyHolder {
private static final LazyInnerClassSingleton LAZY = new LazyInnerClassSingleton();
}
}
上邊程式碼雖然私有化了構造方法,但是在反射的時候,jvm不管是不是私有化,他會執行他的初始方法。所以在初始化方法加上報錯,防止被多次建立。
反射測試程式碼如下:
/**
* 靜態內部類有 可能 被反射攻擊
* 通過反射去獲取他的 構造方法
*/
public static void main(String[] args) {
EnumSingleton instance = EnumSingleton.INSTANCE;
LazyInnerClassSingleton lazyInnerClassSingleton = LazyInnerClassSingleton.getInstance();
// 獲取他的構造方法
Class<?> lazyClass = LazyInnerClassSingleton.class;
try {
Constructor c = lazyClass.getDeclaredConstructor(null);
c.setAccessible(true);
LazyInnerClassSingleton o = (LazyInnerClassSingleton) c.newInstance();
// 比較 o和 一開始建立的 不是同一個變數
System.out.print("o和lazyInnerClassSingleton比較結果為: ");
System.out.println(o == lazyInnerClassSingleton);
} catch (Exception e) {
e.printStackTrace();
}
}
}
註冊式單例
將每一個例項都快取到統一的容器中,使用唯一表示獲取例項。
最常見的如,spring ioc容器
public class ContainerSngleton {
private ContainerSngleton() {
}
private static Map<String, Object> ioc =
new ConcurrentHashMap<String, Object>();
public static Object getBean(String beanName) {
if (!ioc.containsKey(beanName)) {
synchronized (ContainerSngleton.class) {
if (!ioc.containsKey(beanName)) {
return ioc.get(beanName);
}
Object obj = null;
try {
obj = Class.forName(beanName).newInstance();
ioc.put(beanName, obj);
} catch (Exception e) {
e.printStackTrace();
}
}
}
return ioc.get(beanName);
}
}
註冊容器裡還有列舉是單例:如
/**
* 註冊式單例
*
* 列舉式單例 這種是執行緒安全的
*
* 因為在 通過工具檢視指令時候會發現
*/
public enum EnumSingleton {
INSTANCE;
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
private Object data;
public static EnumSingleton getInstance(){
return INSTANCE;
}
}
用jad 反編譯 class 檔案可以看到 如下:
(jad下載連結https://varaneckas.com/jad/jad158g.win.zip)
static
{
INSTANCE = new EnumSingleton("INSTANCE", 0);
$VALUES = (new EnumSingleton[] {
INSTANCE
});
}
對沒錯,餓漢式。jvm編譯了一個static 靜態方法 把裡邊的例項 初始化到一個陣列裡邊。
反射原始碼分析列舉初始化:
newInstance()方法 416行
if ((clazz.getModifiers() & Modifier.ENUM) != 0)
throw new IllegalArgumentException(“Cannot reflectively create enum objects”);
這裡如果是列舉類 呼叫反射初始化方法,會報錯。
從jdk層面就為列舉不被序列化和反射破壞來保駕護航
彩蛋:
用ThreadLocal去初始化單例
public class ThreadLocalSingleton {
private ThreadLocalSingleton() {}
private static final ThreadLocal<ThreadLocalSingleton> threadLocalInstance = new ThreadLocal<ThreadLocalSingleton>() {
@Override
protected ThreadLocalSingleton initialValue() {
return new ThreadLocalSingleton();
}
};
public static ThreadLocalSingleton getInstance(){
return threadLocalInstance.get();
}
}
然後用多執行緒去呼叫getInstance()方法,你會發現在同一個執行緒裡邊獲取到的單例是同一個物件,不同執行緒獲取到的單例是不同物件。
原始碼分析:
threadLocalInstance.get()
-> Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
通過原始碼分析他是執行緒間的安全,在一個執行緒中去獲取他是執行緒安全的,在不同執行緒執行緒間他的,物件是不相等的,也可以叫做偽執行緒安全,通過原始碼 可以發現他是以當前執行緒作為key
單例模式的優點:
在記憶體中只有一個例項,減少了記憶體的開銷.
可以避免堆資源的多重佔用。
設定全域性訪問點,嚴格控制訪問
--------------------------------------------------------------關注⬆並回復 gp001 獲取原始碼-------------------------------------------------------------
相關文章
- 設計模式(單例模式)設計模式單例
- [設計模式] 單例模式設計模式單例
- 設計模式-單例模式設計模式單例
- 設計模式 —— 單例模式設計模式單例
- 設計模式 單例模式設計模式單例
- 設計模式——單例模式設計模式單例
- 設計模式-單例模式、多例模式設計模式單例
- 設計模式之單例設計模式設計模式單例
- 設計模式(一)_單例模式設計模式單例
- 常用設計模式-單例模式設計模式單例
- 設計模式之單例模式設計模式單例
- Java設計模式【單例模式】Java設計模式單例
- Java設計模式 | 單例模式Java設計模式單例
- # Python設計模式 單例模式Python設計模式單例
- 設計模式一(單例模式)設計模式單例
- 設計模式之☞單例模式設計模式單例
- Java設計模式——單例模式Java設計模式單例
- Java設計模式–單例模式Java設計模式單例
- js設計模式--單例模式JS設計模式單例
- Java設計模式-單例模式Java設計模式單例
- 設計模式(二)——單例模式設計模式單例
- 設計模式之---單例模式設計模式單例
- Java設計模式--單例模式Java設計模式單例
- Python設計模式——單例模式Python設計模式單例
- 設計模式—singleton(單例模式)設計模式單例
- python設計模式-單例模式Python設計模式單例
- 設計模式總結 —— 單例設計模式設計模式單例
- JavaScript設計模式初探--單例設計模式JavaScript設計模式單例
- 單例設計模式單例設計模式
- Javascript設計模式之單例模式JavaScript設計模式單例
- PHP 設計模式之——單例模式PHP設計模式單例
- Javascript設計模式(三)單例模式JavaScript設計模式單例
- golang設計模式之單例模式Golang設計模式單例
- jS設計模式二:單例模式JS設計模式單例
- 前端設計模式(2)--單例模式前端設計模式單例
- JS設計模式(二)--- 單例模式JS設計模式單例
- js設計模式之單例模式JS設計模式單例
- Javascript 設計模式之單例模式JavaScript設計模式單例