文章目录
前言
设计出单例模式的背景主要是为了解决某些场景下需要确保一个类只有一个实例,并提供一个全局访问点的问题。
一、设计单例模式的几个主要原因和背景
1.资源控制:某些类可能管理着重要的资源,如数据库连接、文件句柄、线程池等。如果允许多个实例存在,可能会导致资源浪费、冲突或不一致的状态。通过单例模式,可以确保这些资源被统一管理和控制。
2.全局状态管理:有时,我们需要确保系统中的某个对象的状态在全局范围内保持一致。例如,配置信息、系统参数等。如果允许多个实例存在,可能会导致状态不一致或难以管理。通过单例模式,可以确保这些对象的状态在整个系统中都是唯一的。
3.性能考虑:创建对象的开销可能很大,特别是当对象涉及复杂的初始化过程或占用大量内存时。如果频繁地创建和销毁这些对象,可能会导致性能问题。通过单例模式,可以避免不必要的对象创建和销毁,从而提高性能。
4.设计简洁性:在某些情况下,我们可能希望限制某个类的实例化,以确保其使用方式的简洁性。通过单例模式,可以确保该类只有一个实例,从而简化代码结构和逻辑。
5.并发安全:在多线程环境中,如果允许多个线程同时创建和访问同一个类的多个实例,可能会导致数据不一致或其他并发问题。通过单例模式,可以确保只有一个实例被多个线程共享访问,从而简化并发控制。
总之,单例模式的设计背景是为了解决资源控制、全局状态管理、性能考虑、设计简洁性和并发安全等问题。它提供了一种简单而有效的方式来确保一个类只有一个实例,并提供全局访问点。
二、代码实现
1.饿汉式
饿汉式单例模式在类加载时就完成了实例化,避免了线程同步问题。
代码如下(示例):
public class SingletonEager {
// 在类加载时就完成了实例化,是线程安全的
private static final SingletonEager INSTANCE = new SingletonEager();
// 私有的构造方法
private SingletonEager() {}
// 提供全局访问点
public static SingletonEager getInstance() {
return INSTANCE;
}
}
2.懒汉式
懒汉式单例模式在第一次调用getInstance()方法时才进行实例化,因此存在线程安全问题。为了解决这个问题,通常需要在getInstance()方法上添加同步机制。但同步操作会影响性能,因此有多种优化方式。
2.1 基础的懒汉式(非线程安全)
public class SingletonLazyUnsafe {
private static SingletonLazyUnsafe instance;
private SingletonLazyUnsafe() {}
public static SingletonLazyUnsafe getInstance() {
if (instance == null) {
instance = new SingletonLazyUnsafe();
}
return instance;
}
}
2.1 双重检查锁定懒汉式(线程安全)
public class SingletonLazySafe {
// volatile 关键字保证 instance 在多线程环境下的可见性
private volatile static SingletonLazySafe instance;
private SingletonLazySafe() {}
public static SingletonLazySafe getInstance() {
if (instance == null) { // 第一次检查
synchronized (SingletonLazySafe.class) {
if (instance == null) { // 第二次检查
instance = new SingletonLazySafe();
}
}
}
return instance;
}
}
为什么要进行两次判断?只判断一次可以吗?
1.第一次检查(非同步):
如果instance不为null,则直接返回已存在的实例,无需同步。这一步的目的是为了减少同步的开销,因为同步块内的代码是线程安全的,但同步操作本身是有开销的。因此,通过第一次非同步检查,可以快速地返回实例(如果它已经被创建),而无需进入同步块。
2.同步块:
当多个线程几乎同时执行到第一次检查,并且都发现instance为null时,它们都会尝试进入同步块。但由于synchronized的存在,只有一个线程能够进入同步块,其他线程将等待。
3.第二次检查(同步内):
在同步块内,线程会再次检查instance是否为null。这是必要的,因为可能有其他线程在当前线程进入同步块之前就已经创建了实例。这个第二次检查确保了我们不会创建一个不必要的实例。
4.如果不进行第二次检查:
假设我们去掉第二次检查,并直接在同步块内创建实例。那么,所有进入同步块的线程都会尝试创建实例,即使实例已经被其他线程创建。这将导致不必要的实例创建和可能的内存浪费。
2.2 静态内部类方式实现懒汉式单例模式(线程安全)
静态内部类实现的单例模式也是懒加载的,但它利用了JVM的类加载机制保证了线程安全性,不需要额外的同步措施。
public class SingletonLazyStaticInner {
private SingletonLazyStaticInner() {}
// 静态内部类
private static class SingletonHolder {
private static final SingletonLazyStaticInner INSTANCE = new SingletonLazyStaticInner();
}
// 提供全局访问点
public static SingletonLazyStaticInner getInstance() {
return SingletonHolder.INSTANCE;
}
}
为什么说静态内部类的方式实现的单例模式是懒汉式?
回答这个问题首先要知道,什么是懒汉式单例模式和什么是饿汉式单例模式;这里的“懒”指的是它不会在类加载时就创建实例,而是在实际需要使用时才进行实例化。
在这个例子中,当Singleton类被加载到JVM时,并不会创建Singleton的实例。只有当getInstance()方法被调用时,SingletonHolder类才会被加载,而JVM的类加载机制保证了加载过程是线程安全的,因此此时创建的INSTANCE对象是线程安全的单例对象。
因此,静态内部类实现的单例模式虽然利用了类加载机制来保证线程安全,但其本质仍然是懒汉式单例模式,因为它实现了在第一次需要时才创建单例对象的行为。
将上述代码块加上完整注释:
public class SingletonLazyStaticInner {
// 私有静态内部类,含有静态属性instance
private static class SingletonHolder {
// 在这里初始化instance,此时不会被加载
private static final SingletonLazyStaticInner INSTANCE = new SingletonLazyStaticInner ();
}
// 私有构造方法,防止被外部实例化
private SingletonLazyStaticInner () {}
// 静态工厂方法,返回单例对象
public static SingletonLazyStaticInner getInstance() {
// 第一次调用此方法时导致SingletonHolder类被加载
// JVM通过类加载机制初始化INSTANCE,保证线程安全
return SingletonHolder.INSTANCE;
}
}
三、总结
在以上几种懒汉式单例模式中,静态内部类实现的单例模式被认为是最优雅的方式,因为它既保证了线程安全,又避免了同步带来的性能开销。