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

单例模式

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

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

饿汉式

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

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

    }
}

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

相关推荐
lemon3106242 分钟前
Linux Java环境配置
java·开发语言
Brookty12 分钟前
Java线程创建与运行全解析
java·开发语言·后端·学习·java-ee·线程
野生程序员y29 分钟前
spring容器的bean是单例还是多例的?线程安全吗?
java·后端·spring
天天向上的鹿茸1 小时前
web前端用MVP模式搭建项目
前端·javascript·设计模式
星辰大海的精灵1 小时前
Java 线程池的工作原理及实践
java·后端·架构
我命由我123451 小时前
Spring Boot - Spring Boot 集成 MyBatis 分页实现 手写 SQL 分页
java·spring boot·后端·sql·spring·java-ee·mybatis
天天摸鱼的java工程师1 小时前
每天导入100万数据导致数据库死锁?
java·后端·面试
吗喽对你问好1 小时前
java 知识点表格
java·开发语言
Xxtaoaooo1 小时前
手撕Spring底层系列之:IOC、AOP
java·后端·spring·spring框架·底层源码剖析
m0_535064601 小时前
C++类模版与友元
java·c++·算法