【设计模式】 单例模式(单例模式哪几种实现,如何保证线程安全,反射破坏单例模式)

单例模式

作用:单例模式的核心是保证一个类只有一个实例,并且提供一个访问实例的全局访问点。

实现方式 优缺点
饿汉式 线程安全,调用效率高 ,但是不能延迟加载
懒汉式 线程安全,调用效率不高,能延迟加载
双重检测锁 在懒汉式的基础上解决并发问题
静态内部类 线程安全,资源利用率高,可以延时加载
枚举单例 线程安全,调用效率高,但是不能延迟加载

饿汉式

在类加载的时候立即实例化对象,实现的步骤是先私有化构造方法,对外提供唯一的静态入口方法

java 复制代码
public class SingletonInstance1 {
    
    private byte[] b1 = new byte[1024*1024];
    private byte[] b2 = new byte[1024*1024];
    private byte[] b3 = new byte[1024*1024];
    
	// 声明此类型的变量,并实例化,当该类被加载的时候就完成了实例化并保存在了内存中
	private final static SingletonInstance1 instance = new SingletonInstance1();

	// 私有化所有的构造方法,防止直接通过new关键字实例化
	private SingletonInstance1(){}
	// 对外提供一个获取实例的静态方法
	public static SingletonInstance1 getInstance(){
		return instance;
	}
}

在类加载时直接创建对象可能会造成空间的浪费

--

懒汉式

java 复制代码
public class SingletonInstance2 {
	// 声明此类型的变量,但没有实例化
	private static SingletonInstance2 instance = null;

	// 私有化所有的构造方法,防止直接通过new关键字实例化
	private SingletonInstance2(){}
	// 对外提供一个获取实例的静态方法
	public static SingletonInstance2 getInstance(){
		if(instance == null){
			// 当instance不为空的时候才实例化
			instance = new SingletonInstance2();
		}
		return instance;
	}
}

外部调用getInstance()方法时才会创建对象(判断对象是否存在),但是不能保证多线程并发的情况下的线程安全,所以就出现了双重检测锁模式

--

双重检测锁模式

java 复制代码
public class SingletonInstance3 {
	// 声明此类型的变量,但没有实例化,防止指令重排
	private volatile static SingletonInstance3 instance;

	// 私有化所有的构造方法,防止直接通过new关键字实例化
	private SingletonInstance3(){}
    
	// 对外提供一个获取实例的静态方法
	public static SingletonInstance3 getInstance(){
		if(instance == null){
            synchronized (SingletonInstance3.class){
                if(instance == null){
                    // 当instance不为空的时候才实例化
					instance = new SingletonInstance3();
                    /*
                    1.分配内存空间
                    2.执行构造法法,初始化对象
                    3.把这个对象指向这个空间
                    
                    如果不加volatile 会执行重排序 1 3 2
                    */
                }
            }
		}
		return instance;
	}
}

静态内部类

java 复制代码
public class SingletonInstance4 {
	// 静态内部类
	public static class SingletonClassInstance{
		// 声明外部类型的静态常量
		public static final SingletonInstance4 instance = new SingletonInstance4();
	}
	// 私有化构造方法
	private SingletonInstance4(){}

	// 对外提供的唯一获取实例的方法
	public static SingletonInstance4 getInstance(){
		return SingletonClassInstance.instance;
	}
}

枚举

java 复制代码
public enum EnumSingle {
    
    INSTANCE;
    
    public EnumSingle getInstance(){
        return INSTANCE;
    }
}

--

如何保证线程安全

推荐使用 静态内部类 或者 双重检测锁 配合volatile使用

--

反射破坏单例模式

代码如下

java 复制代码
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;


public class LazyMan {

    private static boolean jiamibiaozhi = false;  // 加密标志位

    // 私有化所有的构造方法,防止直接通过new关键字实例化
    private LazyMan(){
        synchronized (LazyMan.class){
            if(!jiamibiaozhi){  // 防止反射破坏单例
                jiamibiaozhi = true;
            }else {
                throw new RuntimeException("不能试图使用反射破坏异常");
            }
        }
        System.out.println(Thread.currentThread().getName() +"LazyMan");
    }


    // 声明此类型的变量,但没有实例化, volatile防止指令重排
    private volatile static LazyMan instance;
    
    // 对外提供一个获取实例的静态方法
    public static LazyMan getInstance(){
        if(instance == null){
            synchronized (LazyMan.class){
                if(instance == null){
                    // 当instance不为空的时候才实例化
                    instance = new LazyMan();
                    /*
                    1.分配内存空间
                    2.执行构造法法,初始化对象
                    3.把这个对象指向这个空间

                    如果不加volatile 会执行重排序 1 3 2
                    */
                }
            }
        }
        return instance;
    }

    // 反射破环单列
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {

        // LazyMan lazyMan = LazyMan.getInstance();
        Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null); // 获取空参构造器
        declaredConstructor.setAccessible(true); // 暴力反射,设置权限,无视私有构造器
        LazyMan lazyMan1 = declaredConstructor.newInstance(); // 通过空参构造器创建对象
        LazyMan lazyMan2 = declaredConstructor.newInstance();

        System.out.println(lazyMan1);
        System.out.println(lazyMan2);

    }
}

反射不能破坏枚举,见源码

相关推荐
卡尔特斯26 分钟前
Android Kotlin 项目代理配置【详细步骤(可选)】
android·java·kotlin
白鲸开源26 分钟前
Ubuntu 22 下 DolphinScheduler 3.x 伪集群部署实录
java·ubuntu·开源
ytadpole34 分钟前
Java 25 新特性 更简洁、更高效、更现代
java·后端
纪莫1 小时前
A公司一面:类加载的过程是怎么样的? 双亲委派的优点和缺点? 产生fullGC的情况有哪些? spring的动态代理有哪些?区别是什么? 如何排查CPU使用率过高?
java·java面试⑧股
JavaGuide2 小时前
JDK 25(长期支持版) 发布,新特性解读!
java·后端
用户3721574261352 小时前
Java 轻松批量替换 Word 文档文字内容
java
白鲸开源2 小时前
教你数分钟内创建并运行一个 DolphinScheduler Workflow!
java
晨米酱2 小时前
JavaScript 中"对象即函数"设计模式
前端·设计模式
Java中文社群2 小时前
有点意思!Java8后最有用新特性排行榜!
java·后端·面试
代码匠心3 小时前
从零开始学Flink:数据源
java·大数据·后端·flink