单例模式确保一个类只有一个实例,并提供全局访问点,实现方式包括饿汉式线程安全但浪费内存;懒汉式延迟加载但需加锁;双重检查锁减少同步开销;静态内部类结合延迟加载和线程安全;枚举最简洁且防反射攻击。应用场景如线程池、配置管理器、数据库连接池和日志记录器等。为防反射破坏,可在构造函数中判断实例是否存在并抛异常,而枚举天然防止反射攻击。与静态类相比,单例支持继承、多态和延迟加载,适用需要全局实例的场景。
单例模式,简单来说,就是确保一个类只有一个实例,并提供一个全局访问点。实现方式多种多样,各有千秋,选择哪种,得看具体应用场景。
解决方案
单例模式的核心在于控制实例的创建,防止外部随意new对象。常见的实现方式包括:
立即学习“Java免费学习笔记(深入)”;
饿汉式(Eager Initialization):
直接在类加载的时候就创建实例。线程安全,简单粗暴,但缺点是如果这个单例一直没被用到,就浪费了内存。
public class SingletonEager { private static final SingletonEager instance = new SingletonEager(); private SingletonEager() {} // 私有构造函数 public static SingletonEager getInstance() { return instance; } }
懒汉式(Lazy Initialization):
在第一次使用的时候才创建实例。优点是延迟加载,节省内存。但线程不安全,需要加锁。
public class SingletonLazy { private static SingletonLazy instance; private SingletonLazy() {} public static synchronized SingletonLazy getInstance() { if (instance == null) { instance = new SingletonLazy(); } return instance; } }
加 synchronized 关键字保证了线程安全,但每次获取实例都要同步,效率较低。
双重检查锁(Double-Checked Locking):
在懒汉式的基础上,通过双重检查和 volatile 关键字来提高效率。
public class SingletonDoubleCheck { private volatile static SingletonDoubleCheck instance; private SingletonDoubleCheck() {} public static SingletonDoubleCheck getInstance() { if (instance == null) { synchronized (SingletonDoubleCheck.class) { if (instance == null) { instance = new SingletonDoubleCheck(); } } } return instance; } }
volatile 关键字防止指令重排序,确保多线程环境下instance的正确初始化。双重检查减少了同步的开销,只有在第一次创建实例的时候才需要同步。不过,早期的JVM版本中,volatile 可能存在问题,导致DCL失效。
静态内部类(Static Inner Class):
利用类加载机制保证线程安全,同时实现延迟加载。
public class SingletonStaticInner { private SingletonStaticInner() {} private static class SingletonHolder { private static final SingletonStaticInner instance = new SingletonStaticInner(); } public static SingletonStaticInner getInstance() { return SingletonHolder.instance; } }
当外部类 SingletonStaticInner 被加载时,静态内部类 SingletonHolder 并不会被加载,只有当调用 getInstance() 方法时,才会加载 SingletonHolder,从而创建单例实例。这种方式既保证了线程安全,又实现了延迟加载,推荐使用。
枚举(Enum):
最简洁的单例实现方式,线程安全,防止反射攻击和序列化攻击。
public enum SingletonEnum { INSTANCE; public void doSomething() { // ... } }
枚举单例是Effective Java作者极力推荐的,它利用JVM保证线程安全和唯一性。
单例模式在多线程环境下如何保证线程安全?
保证线程安全的关键在于防止多个线程同时创建实例。饿汉式因为在类加载时就创建了实例,所以天生线程安全。懒汉式需要加锁,或者使用双重检查锁和 volatile 关键字。静态内部类和枚举则利用了类加载机制,由JVM保证线程安全。
单例模式有哪些应用场景?
单例模式的应用场景非常广泛。比如:
总之,任何只需要一个全局实例的场景,都可以考虑使用单例模式。
如何防止单例模式被反射破坏?
反射可以绕过私有构造函数,创建多个实例。为了防止反射攻击,可以在构造函数中进行判断,如果已经存在实例,则抛出异常。
public class SingletonDoubleCheck { private volatile static SingletonDoubleCheck instance; private SingletonDoubleCheck() { if (instance != null) { throw new IllegalStateException("Singleton instance already exists."); } } public static SingletonDoubleCheck getInstance() { if (instance == null) { synchronized (SingletonDoubleCheck.class) { if (instance == null) { instance = new SingletonDoubleCheck(); } } } return instance; } }
枚举单例则天然防止反射攻击,因为JVM会阻止通过反射创建枚举实例。
单例模式和静态类的区别?
虽然单例模式和静态类都可以实现全局访问,但它们有本质的区别。单例模式是一个类的实例,可以继承接口和抽象类,可以被多态使用。而静态类只是一个类的集合,不能被继承和多态使用。此外,单例模式可以延迟加载,而静态类在类加载时就被初始化。
在选择单例模式还是静态类时,要根据具体的需求来决定。如果需要继承、多态或者延迟加载,则应该选择单例模式。如果只是需要一个工具类,则可以选择静态类。
以上就是Java中单例模式的多种实现方式与优缺点比较的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号