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

单例模式

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

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

饿汉式

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

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);

    }
}

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

相关推荐
sniper_fandc3 分钟前
JVM(Java虚拟机)详解
java·开发语言·jvm
小鸡脚来咯3 分钟前
SpringBoot 常用注解通俗解释
java·spring boot·后端
AI的魔盒9 分钟前
基于Java与MAVLink协议的多无人机(Cube飞控)集群控制与调度方案问题
java·开发语言·无人机
北执南念1 小时前
项目代码生成工具
java
中国lanwp1 小时前
springboot logback 默认加载配置文件顺序
java·spring boot·logback
苹果酱05671 小时前
【Azure Redis 缓存】在Azure Redis中,如何限制只允许Azure App Service访问?
java·vue.js·spring boot·mysql·课程设计
Java致死2 小时前
单例设计模式
java·单例模式·设计模式
胡子发芽2 小时前
请详细解释Java中的线程池(ThreadPoolExecutor)的工作原理,并说明如何自定义线程池的拒绝策略
java
沫夕残雪2 小时前
Tomcat的安装与配置
java·tomcat
胡子发芽2 小时前
请解释Java中的NIO(New I/O)与传统I/O的区别,并说明NIO中的关键组件及其作用
java