单例设计模式
- 单例设计模式(Singleton Pattern)
- talk is cheap, show you my code
- 饿汉式
- 懒汉式
- 双重检查锁定
- 静态内部类
- 枚举
- 总结
单例设计模式(Singleton Pattern)
单例设计模式(Singleton Pattern)是面向对象编程中的一种创建型设计模式,它确保一个类只有一个实例,并提供一个全局访问点来访问这个唯一的实例。这种模式在需要控制资源的访问或配置时非常有用,例如数据库连接池、线程池、缓存等场景。
太抽象了
来我们换个说法,当一些资源比较重的时候,我们不希望创建那么多的实例。举个现实的例子,我们有一大堆地方法院;但是最高法院只有一个。为什么? 因为当所有的事情有分歧的时候,一层层上报,总是需要有一个部门有最终决策权。
talk is cheap, show you my code
饿汉式
饿汉式是在类加载时就创建好实例,这种方式简单直接,但缺点是即使实例从未被使用过,也会占用内存资源。
public class Singleton {private static Singleton singleton = new Singleton();private Singleton() {}public static Singleton getInstance() {return singleton;}
}
我们通过将Singleton设置为private,使得外部没有办法调用构造方法新建实例;又利用类加载机制,我们将Singleton设置为静态变量,这样的话,类加载的时候,就可以新建一个实例;最后开放一个静态方法getInstance()返回实例。
懒汉式
懒汉式是在第一次调用 getInstance() 方法时才创建实例,这样可以节省资源,但是如果多线程环境下未加锁可能会导致多个实例被创建。
public class Singleton1 {private static Singleton1 singleton = null;private Singleton1() {}public static Singleton1 getInstance() {if (singleton == null) {singleton = new Singleton1();}return singleton;}
}
我们通过将Singleton设置为private,使得外部没有办法调用构造方法新建实例;然后调用getInstance()的时候触发新建实例的动作,返回实例,但是这个代码在多线程下可能会创建出多个实例。
双重检查锁定
上面的懒汉式单例写入有多线程的安全问题,为了保证线程安全同时避免不必要的同步开销,可以采用双重检查锁定的方式。
public class Singleton1 {private volatile static Singleton1 singleton = null;private Singleton1() {}public static Singleton1 getInstance() {if (singleton == null) {synchronized (Singleton1.class) {if (singleton == null) {singleton = new Singleton1();}}}return singleton;}
}
synchronized这个关键字用来加锁,但是仅仅通过synchronized也不能保证只产生一个实例,为什么呢?
因为第一个 if (singleton == null)后面通过synchronized拿锁,然后有一个线程会拿到锁,另一些在等待锁,如果不写第二个if判断的话,导致第一个拿到锁的执行了创建实例的操作,释放锁。而等待锁的线程就可以拿到锁,导致又创建了新的实例,所以我们要写两个if判断。所以这种创建单例的写法又叫做双重检查锁定。
静态内部类
public class Singleton {private Singleton() {}private static class SingletonHolder {private static final Singleton INSTANCE = new Singleton();}public static Singleton getInstance() {return SingletonHolder.INSTANCE;}
}
利用 Java 的类加载机制,在第一次调用 getInstance() 方法时才会加载静态内部类,从而实现延迟初始化和线程安全。换言之,这种的线程安全是通过类加载机制保证的,我们就不需要再写线程安全控制了。
另外补充private static final Singleton INSTANCE = new Singleton();这个看起来像是饿汉式,但是实际上只有调用getInstance的时候才会创建对象,因为SingletonHolder是在它自己被调用的时候才会触发类加载。所以从创建时机上看,这种是懒汉式一样的。
枚举
枚举天生就是线程安全的,并且可以防止反序列化攻击,因此它是一种非常简洁且推荐的方式来实现单例模式。
public enum Singleton {INSTANCE;// 单例对象的成员变量private String data;// 私有构造函数private Singleton() {this.data = "Initial Data";}// 获取数据的方法public String getData() {return data;}// 设置数据的方法public void setData(String data) {this.data = data;}// 其他方法public void doSomething() {System.out.println("Doing something with data: " + data);}// 主方法用于测试public static void main(String[] args) {// 获取单例实例Singleton singleton = Singleton.INSTANCE;// 使用单例实例的方法System.out.println(singleton.getData()); // 输出: Initial Datasingleton.setData("New Data");System.out.println(singleton.getData()); // 输出: New Datasingleton.doSomething(); // 输出: Doing something with data: New Data}
}
总结
单例模式特点:
- 单例模式的优点
- 唯一性:确保了类在整个应用程序中只有一个实例。
- 控制资源:对于需要严格控制资源使用的场合非常适合。
- 延迟加载:可以通过某些实现方式(如懒汉式、静态内部类)实现延迟初始化,节省资源。
- 线程安全:通过适当的实现方式可以保证在多线程环境下的安全性。
- 单例模式的缺点
- 难以测试:单例模式引入了全局状态,这可能使得单元测试变得困难。
- 隐藏依赖关系:因为单例模式通常通过静态方法提供实例,所以依赖关系往往被隐藏起来,不利于依赖注入和代码维护。
- 不适用于分布式系统:在一个分布式的环境中,单例模式无法保证跨进程或跨机器的唯一性。
单例模式应用场景
- 配置管理:如读取配置文件或环境变量。
- 日志记录器:整个应用共享同一个日志记录器实例。
- 工厂类:如工厂模式中的工厂类,确保所有客户端都使用同一个工厂来创建对象。
- 数据库连接池:控制对数据库连接的访问,确保高效利用有限的连接资源。
单例模式是一个简单而强大的设计模式,广泛应用于各种需要确保唯一实例的场景。Spring 中的Bean默认就是单例模式的。