飢漢模式 package com.cz.single; /** * @author 卓亦葦 * @version 1.0 * 2023/3/11 21:31 */ public class Hungry { private byte[] data1 = new byte[1024]; private ...
飢漢模式
package com.cz.single;
/**
* @author 卓亦葦
* @version 1.0
* 2023/3/11 21:31
*/
public class Hungry {
private byte[] data1 = new byte[1024];
private byte[] data2 = new byte[1024];
private byte[] data3 = new byte[1024];
private byte[] data4 = new byte[1024];
private Hungry(){
}
private final static Hungry hungry = new Hungry();
public static Hungry getInstance(){
return hungry;
}
public static void main(String[] args) {
. Hungry.getInstance();
}
}
會浪費記憶體,執行代碼,其4個對象已經被創建,浪費空間。
懶漢式單例
package com.cz.single;
/**
* @author 卓亦葦
* @version 1.0
* 2023/3/11 21:35
*/
public class LazyMan {
private LazyMan(){
System.out.println(Thread.currentThread().getName()+" OK");
}
private volatile static LazyMan lazyMan;
public static LazyMan getLazyMan(){
if ((lazyMan==null)){
synchronized (LazyMan.class){
if (lazyMan==null){
lazyMan = new LazyMan();
}
}
}
return lazyMan;
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(()->{
LazyMan.getLazyMan();
}).start();
}
}
}
懶漢式為使用該對象才創建新對象,但是初始代碼有問題,單線程初始沒有問題,多線程會造成,非單例。
解決辦法,首先加鎖,先判斷對象是否為空,如果為空則將class對象進行上鎖,然後需再判斷,鎖是否為空,如果為空再創建新對象。
同步代碼塊簡單來說就是將一段代碼用一把鎖給鎖起來, 只有獲得了這把鎖的線程才訪問, 並且同一時刻, 只有一個線程能持有這把鎖, 這樣就保證了同一時刻只有一個線程能執行被鎖住的代碼。第二層,是因為使用同步代碼塊才加上的,有的可能過了第一個if,沒到同步代碼塊
為雙層檢測的懶漢式單例,也稱DCL懶漢式
第二個問題
lazyMan = new LazyMan();
代碼為非原子性操作
創建新對象的底層操作分為3步
1.分配記憶體空間
2、執行構造方法,初始化對象
3、把這個對象指向這個空間
但如果不是原子操作,那132的狀況式可能發現的,如果在A還沒完成構造是,線程B進來,則不會執行if語句,發生錯誤
讓lazyMan加上volatile參數
反射會破壞單例模式
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
// for (int i = 0; i < 10; i++) {
// new Thread(()->{
// LazyMan.getLazyMan();
// }).start();
// }
LazyMan lazyMan1 = LazyMan.getLazyMan();
//獲得空參構造器
Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
//反射的setAccessible(true)參數,無視私有構造器
declaredConstructor.setAccessible(true);
//通過反射建造對象
LazyMan lazyMan2 = declaredConstructor.newInstance();
System.out.println(lazyMan1);
System.out.println(lazyMan2);
}
解決辦法,在無參構造器添加異常
private LazyMan(){
synchronized (LazyMan.class){
if (lazyMan!=null){
throw new RuntimeException("不要試圖反射破壞");
}
}
System.out.println(Thread.currentThread().getName()+" OK");
}
但依然存在問題, if (lazyMan!=null)為非原子性操作,依然存在兩個反射對象導致出現非單例的狀況
//可以採用紅綠燈解決,定義一個密鑰
private static boolean key = false;
private LazyMan(){
synchronized (LazyMan.class){
if (key==false){
key=true;
}else {
throw new RuntimeException("不要試圖反射破壞");
}
}
System.out.println(Thread.currentThread().getName()+" OK");
}
但是如果仍然用反射破環,假設獲取到了密鑰的情況
//通過反射修改靜態參數
Field key1 = LazyMan.class.getDeclaredField("key");
key1.setAccessible(true);
//獲得空參構造器
Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
//反射的setAccessible(true)參數,無視私有構造器
declaredConstructor.setAccessible(true);
//通過反射建造對象
LazyMan lazyMan1 = declaredConstructor.newInstance();
//修改對象的密鑰參數
key1.set(lazyMan1,false);
LazyMan lazyMan2 = declaredConstructor.newInstance();
System.out.println(lazyMan1);
System.out.println(lazyMan2);
單例仍然會被破壞
真實有效的方式枚舉
package com.cz.single;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
/**
* @author 卓亦葦
* @version 1.0
* 2023/3/14 16:26
*/
public enum EnumSingle {
INSTANCE;
public EnumSingle getInstance(){
return INSTANCE;
}
}
class Test{
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
EnumSingle instance1 = EnumSingle.INSTANCE;
//EnumSingle instance2 = EnumSingle.INSTANCE;
Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class);
declaredConstructor.setAccessible(true);
EnumSingle instance2 = declaredConstructor.newInstance();
System.out.println(instance1);
System.out.println(instance2);
}
}
至此才得以真正解決