Java设计模式(一)创建型-单例模式(史上最全单例模式饿汉懒汉双重检验锁静态内部枚举)与解决方法以及优缺点

1.单例模式(Singleton Pattern)

单例模式(Singleton Pattern)是 Java中最简单的设计模式之一。这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

1.1 饿汉式

特点:类加载时就初始化,线程安全

    // 构造方法私有化
	private Singleton() {
		
	}
 
	// 饿汉式创建单例对象
	private static Singleton singleton = new Singleton();
 
	public static Singleton getInstance() {
		return singleton;
	}

1.2 懒汉式

特点:第一次调用才初始化,避免内存浪费。

/*
	 * 懒汉式创建单例模式 由于懒汉式是非线程安全, 所以加上线程锁保证线程安全
	 */
	private static Singleton singleton;
 
	public static synchronized Singleton getInstance() {
		if (singleton == null) {
			singleton = new Singleton();
		}
		return singleton;
	}

1.3 双重检验锁(double check lock)(DCL)

特点:安全且在多线程情况下能保持高性能

private volatile static Singleton singleton;
    private Singleton (){}
    public static Singleton getInstance() {
        if (singleton == null) {
            synchronized (Singleton.class) {
                if (singleton == null) {
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }

1.4 静态内部类

特点:效果类似DCL,只适用于静态域

 private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }
    private Singleton (){}
    public static final Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }

1.5 枚举

特点:自动支持序列化机制,绝对防止多次实例化

public enum Singleton {
    INSTANCE;
}

1.6 破坏单例的几种方式与解决方法

1.6.1 反序列化

            Singleton singleton = Singleton.getInstance();
            ObjectOutputStream oos = new ObjectOutputStream(new     FileOutputStream("D:/test.txt"));
            oos.writeObject(singleton);
            oos.flush();
            oos.close();
 
            ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:/test.txt"));
            Singleton singleton1 = (Singleton)ois.readObject();
            ois.close();
            System.out.println(singleton);//com.ruoyi.base.mapper.Singleton@50134894
            System.out.println(singleton1);//com.ruoyi.base.mapper.Singleton@5ccd43c2

可以看到反序列化后,两个对象的地址不一样了,那么这就是违背了单例模式的原则了,解决方法只需要在单例类里加上一个readResolve()方法即可,原因就是在反序列化的过程中,会检测readResolve()方法是否存在,如果存在的话就会反射调用readResolve()这个方法。

private Object readResolve() {
        return singleton;
    }
//com.ruoyi.base.mapper.Singleton@50134894
//com.ruoyi.base.mapper.Singleton@50134894

1.6.2 反射

            Singleton singleton = Singleton.getInstance();
            Class<Singleton> singletonClass = Singleton.class;
            Constructor<Singleton> constructor = singletonClass.getDeclaredConstructor();
            constructor.setAccessible(true);
            Singleton singleton1 = constructor.newInstance();
            System.out.println(singleton);//com.ruoyi.base.mapper.Singleton@32a1bec0
            System.out.println(singleton1);//com.ruoyi.base.mapper.Singleton@22927a81

同样可以看到,两个对象的地址不一样,这同样是违背了单例模式的原则,解决办法为使用一个布尔类型的标记变量标记一下即可,代码如下:

private static boolean singletonFlag = false;
 
    private Singleton() {
        if (singleton != null || singletonFlag) {
            throw new RuntimeException("试图用反射破坏异常");
        }
        singletonFlag = true;
    }

但是这种方法假如使用了反编译,获得了这个标记变量,同样可以破坏单例,代码如下:

            Class<Singleton> singletonClass = Singleton.class;
            Constructor<Singleton> constructor = singletonClass.getDeclaredConstructor();
            constructor.setAccessible(true);
            Singleton singleton = constructor.newInstance();
            System.out.println(singleton); // com.ruoyi.base.mapper.Singleton@32a1bec0
 
            Field singletonFlag = singletonClass.getDeclaredField("singletonFlag");
            singletonFlag.setAccessible(true);
            singletonFlag.setBoolean(singleton, false);
            Singleton singleton1 = constructor.newInstance();
            System.out.println(singleton1); // com.ruoyi.base.mapper.Singleton@5e8c92f4

如果想使单例不被破坏,那么应该使用枚举的方式去实现单例模式,枚举是不可以被反射破坏单例的。

1.7 容器式单例

当程序中的单例对象非常多的时候,则可以使用容器对所有单例对象进行管理,如下:

public class ContainerSingleton {
    private ContainerSingleton() {}
    private static Map<String, Object> singletonMap = new ConcurrentHashMap<>();
    public static Object getInstance(Class clazz) throws Exception {
        String className = clazz.getName();
        // 当容器中不存在目标对象时则先生成对象再返回该对象
        if (!singletonMap.containsKey(className)) {
            Object instance = Class.forName(className).newInstance();
            singletonMap.put(className, instance);
            return instance;
        }
        // 否则就直接返回容器里的对象
        return singletonMap.get(className);
    }
    public static void main(String[] args) throws Exception {
        SafetyDangerLibrary instance1 = (SafetyDangerLibrary)ContainerSingleton.getInstance(SafetyDangerLibrary.class);
        SafetyDangerLibrary instance2 = (SafetyDangerLibrary)ContainerSingleton.getInstance(SafetyDangerLibrary.class);
        System.out.println(instance1 == instance2); // true
    }
}

1.8 ThreadLocal单例

不保证整个应用全局唯一,但保证线程内部全局唯一,以空间换时间,且线程安全。

public class ThreadLocalSingleton {
    private ThreadLocalSingleton(){}
    private static final ThreadLocal<ThreadLocalSingleton> threadLocalInstance = ThreadLocal.withInitial(() -> new ThreadLocalSingleton());
    public static ThreadLocalSingleton getInstance(){
        return threadLocalInstance.get();
    }
    public static void main(String[] args) {
        new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + "-----" + ThreadLocalSingleton.getInstance());
            System.out.println(Thread.currentThread().getName() + "-----" + ThreadLocalSingleton.getInstance());
        }).start();
        new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + "-----" + ThreadLocalSingleton.getInstance());
            System.out.println(Thread.currentThread().getName() + "-----" + ThreadLocalSingleton.getInstance());
        }).start();
//        Thread-0-----com.ruoyi.library.domain.vo.ThreadLocalSingleton@53ac93b3
//        Thread-1-----com.ruoyi.library.domain.vo.ThreadLocalSingleton@7fe11afc
//        Thread-0-----com.ruoyi.library.domain.vo.ThreadLocalSingleton@53ac93b3
//        Thread-1-----com.ruoyi.library.domain.vo.ThreadLocalSingleton@7fe11afc
    }
}

可以看到上面线程0和1他们的对象是不一样的,但是线程内部,他们的对象是一样的,这就是线程内部保证唯一。

1.9 总结

适用场景:
需要确保在任何情况下绝对只需要一个实例。如:ServletContext,ServletConfig,ApplicationContext,DBPool,ThreadPool等。

优点:

1、在内存中只有一个实例,减少了内存开销。
2、可以避免对资源的多重占用。
3、设置全局访问点,严格控制访问。

缺点:

1、没有接口,扩展困难。
2、如果要扩展单例对象,只有修改代码,没有其它途径。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

刘了个刘

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值