单例模式
单例模式(SingletonPattern
)是 Java
中最简单的设计模式之一。
单例模式一共存在 --> 懒汉式、饿汉式、懒汉+同步锁、双重校验锁、静态内部类、枚举这六种方式。
这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。
这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
要求
- 单例类只能有一个实例。
- 单例类必须自己创建自己的唯一实例。
- 单例类必须给所有其他对象提供这一实例。
为什么需要使用单例模式
- 只允许创建一个对象,因此节省内存,加快对象访问速度,因此对象需要被公用的场合适合使用,如多个模块使用同一个数据源连接对象等等。
- 解决一个全局使用的类频繁地创建与销毁问题。
- 其他场景自行脑部,单例即全局唯一对象,比如我们所熟悉的
Spring
的Bean
默认就是单例的,全局唯一。
单例原理
单例的原理非常简单,我们让他唯一的方法就是让他不可用被new
,那我们只需要私有化类的构造即可:
private ClassName() { }
但是私有化后,我们不能new
又如何创建对象呢?
我们首先要明白,private
他是私有的,也就是不让外部其他类访问,那我们自己还是可以访问的,所以在上文的要求中就说到了:单例类必须自己创建自己的唯一实例。
同时我们还需要抛出单例的获取方法。
单例模式之懒汉式
创建单例类
public class SlackerStyle { }
创建一个属性保存自身对象
public class SlackerStyle { private static SlackerStyle instance; }
私有化构造
public class SlackerStyle { private static SlackerStyle instance; /** * 私有化构造方法(防止外部new新的对象) */ private SlackerStyle() { } }
自身创建对象与获取对象方法
public class SlackerStyle { private static SlackerStyle instance; /** * 私有化构造方法(防止外部new新的对象) */ private SlackerStyle() { } /** * 提供一个静态的公有方法,当使用到该方法时,才去创建instance * 即懒汉式 * * @return instance(单例对象) */ public static SlackerStyle getInstance() { if (instance == null) { instance = new SlackerStyle(); } return instance; } }
当我们调用静态方法,它便会判断上面的静态属性
instance
中有无自身对象,无 --> 创建对象并赋值给instance
,有 --> 返回instance
。
优缺分析
优点:延迟加载,效率较高。
缺点:线程不安全,可能会造成多个实例。
解释:延迟加载 --> 懒汉式只有在需要时才会创建单例对象,可以节约资源并提高程序的启动速度。
单例模式之懒汉式+锁
在以上的类中,对getInstance()
方法添加synchronized
锁,即可弥补线程不安全缺陷。
/** * 注意,此段为补充,为了解决线程不安全的问题,可以在方法上加上synchronized关键字,但是这样会导致效率下降 * 提供一个静态的公有方法,加入同步处理的代码,解决线程安全问题 * 此方法为线程安全的懒汉式,即懒汉+同步锁,就不额外写一个类了 * * @return instance(单例对象) */ public static synchronized SlackerStyle getInstance2() { if (instance == null) { instance = new SlackerStyle(); } return instance; }
虽然弥补了线程不安全的缺陷,但是也失去了一部分效率,所以需要根据业务环境去选择适合的方法,鱼和熊掌不可兼得。
单例模式之饿汉式
还是如开始一样,创建好单例类,私有化构造方法。
public class HungryManStyle { /** * 私有化构造方法(防止外部new新的对象) */ private HungryManStyle() { } }
静态初始化对象
我们饿汉式是延迟加载的,即要用,然后第一次去调用时才会创建对象,而饿汉式恰恰相反,他在初始化类的时候就去创建。
静态初始化?
我们的static
关键词修饰的方法或属性,在类加载之初遍开辟内存创建好了相关的内容了。
包括每个类的:
static{ }
中也一样的。
所以我们直接使用static
修饰。
public class HungryManStyle { /** * 静态变量(单例对象),类加载时就初始化对象(不存在线程安全问题) */ private static final HungryManStyle INSTANCE = new HungryManStyle(); /** * 私有化构造方法(防止外部new新的对象) */ private HungryManStyle() { } /** * 提供一个静态的公有方法,直接返回INSTANCE * * @return instance(单例对象) */ public static HungryManStyle getInstance() { return INSTANCE; } }
而且我们在类的静态属性创建时就new
了一个自身对象了。
优缺分析
饿汉式的优点如下:
- 线程安全:由于在类加载时就创建单例对象,因此不存在多线程环境下的同步问题。
- 没有加锁的性能问题:饿汉式没有使用同步锁,因此不存在加锁带来的性能问题。
- 实现简单:饿汉式的实现比较简单,不需要考虑多线程环境下的同步问题。
饿汉式的缺点如下:
- 立即加载:由于在类加载时就创建单例对象,因此可能会影响程序的启动速度。
- 浪费资源:如果单例对象很大,并且程序中很少使用,那么饿汉式可能会浪费资源。
综上所述,饿汉式的优点是线程安全、没有加锁的性能问题和实现简单,缺点是可能影响程序的启动速度和浪费资源。
在选择单例模式的实现方式时,需要根据实际情况综合考虑各种因素,选择最适合的方式。