一、为什么使用单例模式?
单例模式是一种创建型设计模式,目的是确保一个类在应用程序中只有一个实例,并提供全局访问点。以下是使用单例模式的几个好处
- 资源节省:对于需要频繁创建和销毁的对象,比如数据库连接或日志记录器,使用单例模式可以节约系统资源。
- 全局访问:提供一个全局的访问点,便于访问共享资源。
- 控制实例数量:确保一个类只有一个实例,避免对资源的多重占用。
二、单例模式是什么?
单例模式限制类的实例化次数为一次,并通过提供一个静态方法来获取该实例。下面是几种常见的实现方式:
- 懒汉式:实例在第一次使用时创建,不适用于多线程环境。
- 饿汉式:类加载时即创建实例,线程安全。
- 双重检查锁定:结合懒汉和饿汉的优点,适用于多线程。
- 静态内部类:利用JVM的类加载机制实现线程安全和延迟加载。
三、怎么用单例模式?
一、懒汉式(线程不安全)
这种方式只适合单线程使用,多线程下有会实例化多个对象
public class Single {
private static Single instance;
private Single(){
System.out.println("实例化Single对象");
}
public static Single getInstance(){
if (instance == null) instance = new Single();
return instance;
}
}
public class test {
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
Single.getInstance();
}
}
}
测试结果:
/*
实例化Single对象
Process finished with exit code 0
*/
二、懒汉式(线程安全)
只需添加一个synchronized 关键字即可,也就是常见的加锁操作。
public class Single {
private static Single instance;
private Single(){
System.out.println("实例化Single对象");
}
public synchronized static Single getInstance(){
if (instance == null) instance = new Single();
return instance;
}
}
public class test {
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(()->{
Single.getInstance();
}).start();
}
}
}
测试结果:
/*
实例化Single对象
Process finished with exit code 0
*/
在多线程环境中使用 synchronized 可以确保实例只被创建一次,但它会导致系统资源的浪费。假设我们进行10次调用,每次都会经过加锁过程,实际上我们只需在第一次实例化时加锁。后续的调用仅需要进行逻辑判断,而不必再实例化对象。因此,我们可以引入一种优化方法,减少锁的使用频率,也就是在类加载时直接实例化对象。
三、饿汉式
在饿汉式单例模式中,类的实例是在类加载时就被创建的,这样确保了线程安全和唯一性,但也可能导致资源浪费,因为无论是否需要这个实例,都会在启动时被创建。
public class Single {
// 在类加载时就创建实例 无论使用不使用 都会创建 会造成资源浪费
private static final Single instance =new Single();
private Single(){
System.out.println("实例化Single对象");
}
public static Single getInstance(){
return instance;
}
}
四、双重检查锁定:结合懒汉和饿汉的优点,适用于多线程。
双重检查锁定旨在减少不必要的同步,提高性能。它结合了懒汉式和饿汉式的优点:
- 懒汉式加载:只有在需要时才创建实例。
- 线程安全:通过同步块和双重检查,确保多线程环境下实例的唯一性。
public class Singleton {
private volatile static Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
//第一次检查:避免不必要的同步,提高性能。如果实例已存在,直接返回。
if (instance == null) {
synchronized (Singleton.class) {
//第二次检查:在同步块内再次检查,确保实例未被其他线程创建。只在第一次时创建实例。
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
五、静态内部类。
静态内部类利用JVM类加载机制,在类加载时并不会立即创建实例,而是在调用 getInstance() 方法时才创建,实现了延迟加载和线程安全。
静态内部类的特性,JVM在加载外部类时不会加载内部类,这样可以实现懒加载并且是线程安全的:
public class Singleton {
private Singleton() {}
private static class Holder {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return Holder.INSTANCE;
}
}
适用场景:
单例模式适用于需要频繁创建和销毁的对象、创建对象耗时较多但又经常用到的对象、工具类对象等。
优缺点:
- 优点:内存中只有一个实例,减少内存开销,避免多重占用。
- 缺点:不利于扩展,单例类职责过重,在一定程度上违反了单一职责原则。
注意事项: - 在多线程环境下,要确保线程安全。
- 如果使用反射机制或者序列化机制,需要额外处理以确保单例。