深度解析单例模式
饥汉模式
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);
}
}
至此才得以真正解决