设计模式_单例模式
创建型设计模式
单例模式
简介: 令某一个类在程序中只存在一个实例
实例化方法
饿汉式:基本方式
在载入JVM虚拟机时直接实例化
我们知道,类加载的方式是按需加载,且加载一次。因此,在下述单例类被加载时,就会实例化一个对象并交给自己的引用,供系统使用;而且,由于这个类在整个生命周期中只会被加载一次,因此只会创建一个实例,即能够充分保证单例。
优点:这种写法比较简单,就是在类装载的时候就完成实例化。避免了线程同步问题。
缺点:在类装载的时候就完成实例化,没有达到Lazy Loading的效果。如果从始至终从未使用过这个实例,则会造成内存的浪费。
class Singleton { private final static Singleton INSTANCE = new Singleton(); private Singleton() {} public static Singleton getInstance() { return INSTANCE; } } public class Demo1 { public static void main(String[] args) { for (int i = 1; i <= 999; ++i) { new Thread(new Runnable() { @Override public void run() { Singleton instance = Singleton.getInstance(); System.out.println(instance); // 打印结果都一样 } }).start(); } } }
懒汉式:基本方式
做到懒加载(懒汉式需要考虑线程安全问题)
优点:当需要时对对象进行实例化,节约了内存
缺点:存在线程同步问题,需要加锁,然而此种方法对getInstance()
进行加锁,导致即使instance
已经不为null,仅仅只单纯获取对象时依然受到锁的限制,效率过低。
class Singleton { private static volatile Singleton instance; // volatile保证线程同步 private Singleton() {} public static synchronized Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } }
懒汉式:双重校验锁
保留了基本方式的优点,也解决了基本方式的缺点
class Singleton { private static volatile Singleton instance; private Singleton() {} public static Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) { instance = new Singleton(); } } } return instance; } }
懒汉式:静态内部类
静态内部类单例模式由内部类创建,由于JVM在加载外部类时,不会加载静态内部类,只要当静态内部类被访问时才会进行加载,并初始化静态变量,静态变量被static修饰保证只会被实例化一次,切严格保证实例化顺序。
class Singleton { private Singleton() {} private static class SingletonHolder { private final static Singleton INSTANCE = new Singleton(); } public static Singleton getInstance() { return SingletonHolder.INSTANCE; } }
双重校验锁与静态内部类都是非常推荐的懒汉式单例模式
饿汉式:枚举
enum Singleton { /** * Singleton实例 */ INSTANCE } public class Demo { public static void main(String[] args) { for (int i = 1; i <= 999; ++i) { new Thread(new Runnable() { @Override public void run() { Singleton instance = Singleton.INSTANCE; System.out.println(instance.hashCode()); } }).start(); } } }
总结
一般情况下,不建议使用懒汉式基本方式,建议使用饿汉式基本方式。只有在要明确实现 lazy loading 效果时,才会使用静态内部类方式。如果涉及到反序列化创建对象时,可以尝试使用枚举方式。如果有其他特殊的需求,可以考虑使用双重校验锁方式。
破坏单例模式
序列化与反序列化
序列化:对象➡字节串;是将对象的状态信息转换为可以存储或传输的形式的过程。在序列化期间,对象将其当前状态写入到临时或持久性存储区。以后,可以通过从存储区中读取或反序列化对象的状态,重新创建该对象。
目的: 1、以某种存储形式使自定义对象持久化;2、将对象从一个地方传递到另一个地方;
反序列化:字节串➡对象;将存储的对象信息重新转换为对象;
目的:1、将持久化后的对象重新实例化;2、接收其他地方传递来的对象
class Singleton implements Serializable { private final static Singleton INSTANCE = new Singleton(); private Singleton() {} public static Singleton getInstance() { return INSTANCE; } } public class Demo1 { public static void main(String[] args) throws Exception { writeSerializable(); Singleton singleton = readSerializable(); Singleton singleton1 = readSerializable(); System.out.println(singleton == singleton1); } public static void writeSerializable() throws Exception { Singleton instance = Singleton.getInstance(); ObjectOutputStream objectOutputStream = new ObjectOutputStream( new FileOutputStream("C:\\Users\\80946\\Desktop\\temp.txt")); objectOutputStream.writeObject(instance); objectOutputStream.close(); } public static Singleton readSerializable() throws Exception { ObjectInputStream inputStream = new ObjectInputStream( new FileInputStream("C:\\Users\\80946\\Desktop\\temp.txt")); Singleton singleton = (Singleton) inputStream.readObject(); inputStream.close(); return singleton; } }
输出结果:
false
由此可见,Singleton
在本程序中出现了两个实例,违背的单例模式。序列化与反序列化破坏了单例模式。
解决方案
在Singleton
类中添加 readResolve()
方法,在反序列化时被反射调用,如果定义了这个方法,就返回这个方法的值,如果没有定义,则返回新new
除的对象
此解决方案与inputStream.readObject()
的内部实现有关。
class Singleton implements Serializable { private final static Singleton INSTANCE = new Singleton(); private Singleton() {} public Object readResolve() { return INSTANCE; } public static Singleton getInstance() { return INSTANCE; } } public class Demo1 { public static void main(String[] args) { writeSerializable(); Singleton singleton = readSerializable(); Singleton singleton1 = readSerializable(); System.out.println(singleton == singleton1); } public static void writeSerializable() { Singleton instance = Singleton.getInstance(); try (ObjectOutputStream objectOutputStream = new ObjectOutputStream( new FileOutputStream("C:\\Users\\80946\\Desktop\\temp.txt"))) { objectOutputStream.writeObject(instance); } catch (Exception e) { e.printStackTrace(); } } public static Singleton readSerializable() { Singleton singleton = null; try (ObjectInputStream inputStream = new ObjectInputStream( new FileInputStream("C:\\Users\\80946\\Desktop\\temp.txt"))) { singleton = (Singleton) inputStream.readObject(); } catch (Exception e) { e.printStackTrace(); } return singleton; } }
输出结果:
true
反序列化得到的时同一对象,遵守了单例模式
反射
Java反射:在运行状态中,程序可以拿到一个类或者一个对象的所有属性和方法,并对其进行修改。
class Singleton { private final static Singleton INSTANCE = new Singleton(); private Singleton() {} public static Singleton getInstance() { return INSTANCE; } } public class Demo1 { public static void main(String[] args) throws Exception { Class<Singleton> singletonClass = Singleton.class; Constructor<Singleton> declaredConstructor = singletonClass.getDeclaredConstructor(); declaredConstructor.setAccessible(true); Singleton singleton = declaredConstructor.newInstance(); Singleton singleton1 = declaredConstructor.newInstance(); System.out.println(singleton == singleton1); } }
输出结果:
false
由此可见通过反射可以改变构造函数的可见性,导致可以通过构造函数直接
解决方案
饿汉式
class Singleton { private final static Singleton INSTANCE = new Singleton(); /** * 当单例对象已经被实现后,如果构造方法再次被调用,就会发生异常 */ private Singleton() { if (INSTANCE != null) { throw new RuntimeException("不能创建多个对象"); } } public static Singleton getInstance() { return INSTANCE; } }
懒汉式
懒汉式存在多线程问题,故处理方式和饿汉式不一样
// 静态内部类方式,其他的懒汉式也可以这样处理 class Singleton { private volatile static boolean flag = false; private Singleton() { synchronized (Singleton.class) { if (flag) { throw new RuntimeException(); } flag = true; } } private static class SingletonHolder { private final static Singleton INSTANCE = new Singleton(); } public static Singleton getInstance() { return SingletonHolder.INSTANCE; } } public class Demo1 { public static void main(String[] args) throws Exception { for (int i = 0; i <= 99; i++) { new Thread(new Runnable() { @Override public void run() { Class<Singleton> singletonClass = Singleton.class; Singleton singleton = null; Singleton singleton1 = null; try { Constructor<Singleton> declaredConstructor = singletonClass.getDeclaredConstructor(); declaredConstructor.setAccessible(true); singleton = declaredConstructor.newInstance(); } catch (Exception e) { e.printStackTrace(); } System.out.println(singleton); } }).start(); } Singleton instance = Singleton.getInstance(); System.out.println(instance); } }
输出结果:
仅有一个输出不为null,故
Singleton
只实例化了一次但经测试,此时类已被破坏,既即使不通过反射,通过正常方式也无法获取对象
原因分析
只能得到第一次反射创建的那个对象,但这个对象似乎无法存到
SingletonHolder.INSTANCE
中。。导致使用正常方式获取单例对象时,就会执行Singleton INSTANCE = new Singleton()
; 结果就又异常了,其他的懒汉式也有这种问题。。。。(我不会解决)
饿汉式没有这种问题
碰巧的解决方法
// 仅限懒汉式:静态内部类法使用 // 其他方法使用会栈溢出 class Singleton { private volatile static boolean flag = false; private Singleton() { synchronized (Singleton.class) { Singleton instance = Singleton.getInstance(); if (flag) { throw new RuntimeException(); } flag = true; } } private static class SingletonHolder { private final static Singleton INSTANCE = new Singleton(); } public static Singleton getInstance() { return SingletonHolder.INSTANCE; } } public class Demo1 { public static void main(String[] args) throws Exception { for (int i = 0; i <= 99; i++) { new Thread(new Runnable() { @Override public void run() { Class<Singleton> singletonClass = Singleton.class; Singleton singleton = null; Singleton singleton1 = null; try { Constructor<Singleton> declaredConstructor = singletonClass.getDeclaredConstructor(); declaredConstructor.setAccessible(true); singleton = declaredConstructor.newInstance(); } catch (Exception e) { e.printStackTrace(); } System.out.println(singleton); } }).start(); } Singleton instance = Singleton.getInstance(); System.out.println(instance); } }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 25岁的心里话
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用