单例模式:秒懂五种实现方式

在我们日常的开发工作中,有时候会遇到这样的情况:某个类只需要创建一个实例,这个实例的生命周期贯穿整个程序,共享资源。这时候就需要用到我们今天的主角——单例模式。单例模式不仅可以提高代码的可读性和可维护性,还可以在一定程度上提高系统的性能。那么,接下来让我们一起详细了解一下单例模式吧!

简介

单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。单例模式指的是一个类只能实例化一次,并提供全局访问点来访问该实例。在Java编程中,单例模式非常有用,因为它可以确保类只有一个实例,并且该实例可以被其他对象访问,而不需要再创建新的对象。

设计原理

  • 拥有静态的、私有的(自身类的)成员变量
  • 将构造方法私有化,以避免在类外部创建对象
  • 提供静态的public方法用于返回全局唯一的实例

优点

  • 节省系统资源。由于单例模式创建的对象只有一个,因此可以避免创建多个实例对象所带来的资源消耗,提高了系统的性能和效率。
  • 实现了全局唯一性,避免了多个实例对象之间相互影响的问题。

缺点

  • 违反了单一职责原则,因为单例模式把两个职责合并在了一起,即实现了单例模式,同时还要负责自身的业务逻辑。
  • 单例模式的单例对象的生命周期往往是整个应用程序的生命周期,如果单例对象需要释放资源,需要格外小心处理,避免影响整个应用程序。

应用场景

  • 系统中只需要存在一个实例的对象,例如全局配置信息、数据库连接池、日志管理器等。
  • 对象需要被频繁创建和销毁的场景,例如线程池、缓存管理器等。
  • 需要控制实例数量的场景,例如计数器、信号量等。

实现方式

1. 懒汉式

延迟加载(懒加载),线程不安全

如要保证线程安全,可在方法上加锁synchronized,但是影响调用效率

public class SingletonDemo1 {
    
    
    private static SingletonDemo1 instance;
    private SingletonDemo2(){
    
    
    }    
    public  static  SingletonDemo1 getInstance(){
    
    
        if(instance==null){
    
    
            instance=new SingletonDemo1();
        }
        return instance;
    }
}

如果要验证是否为单例,只需要在main方法中使用equals方法进行比较即可

public static void main(String[] args) {
    
    
    SingletonDemo1 s1=SingletonDemo1.getInstance();
    SingletonDemo1 s2=SingletonDemo1.getInstance();
    System.out.println(s1.equals(s2));
}

2. 饿汉式

不能延迟加载,天生线程安全,但资源利用率低。

其所谓的饿汉就是说,在类加载的时候就把实例创建好了,不管用不用,都会一直存在于内存,如果不用就有点浪费资源。

public class SingletonDemo2{
    
     
     private static SingletonDemo2 instance = new SingletonDemo2(); 
     private SingletonDemo2(){
    
    } 
     public static SingletonDemo2 getInstance(){
    
      
          return instance;  
      } 
}

3. 静态内部类实现

延迟加载,线程安全。这种方式可以避免synchronized关键字带来的性能开销,也是一种推荐的使用方式。

延迟加载:静态内部类只有在第一次被使用时才会被加载和初始化,而不是在外部类加载的时候初始化。只有在第一次调用 getInstance() 方法时,才会加载 Inner 类并创建单例对象,所以也是一种懒加载的方式。

类加载的机制保证了线程安全性:虚拟机在加载类的时候会保证线程安全,即多个线程同时调用 getInstance() 方法时,只有一个线程能够获取到类加载锁并加载 Inner 类,其他线程需要等待。在 Inner 类被加载和初始化时,会创建 SingletonDemo3的唯一实例,从而保证了线程安全性。

public class SingletonDemo3 {
    
    
    private static class Inner {
    
    
         private static final SingletonDemo3 instance = new SingletonDemo3();  
     }
     private SingletonDemo3(){
    
    
     }
     public static SingletonDemo3 getInstance(){
    
      
         return Inner.instance;  
     }

 }

4. 双重检查锁(重点)

线程安全,延迟加载。这种方式采用双重检查锁机制,保证线程安全情况下能保持高性能。

“双重检查锁”:即两次非空检查,一个代码块锁

重点说明

1,为什么要进行双重检查

第一个非空检查是为了保证效率,如果对象已经实例化,那么直接返回就行了,没必要再进入同步代码块。

第二个非空检查放在了同步代码块里面,也是必须存在的,避免多个线程同时创建对象的情况发生。如在多线程环境下,两个线程可能同时通过第一层非空检查,然后排队进入同步代码块,其中一个线程先获得锁,在创建对象后释放锁,另一个线程获得锁后,如果不检验,会再次创建一个对象。

2,为什么要使用volatile关键字?

volatile关键字此处的作用:在创建对象时禁止指令重排序,避免多线程环境下发生错误访问。

原理:

instance = new Singleton()创建实例的操作,并不是一个原子操作,它由三条指令组成,非顺序执行,这三条指令在jvm中会进行指令重排序。

  1. 分配对象内存
  2. 调用构造器方法,执行初始化
  3. 将对象引用赋值给变量。

当一个线程进入到同步代码块中,正在进行实例化操作,此时分为三步执行,如果先执行的是1、3步,此时另一个线程乱入,在第一层非空检查时会得到对象已存在这个结果,但此时对象实际并未被实例化,如果访问对象就会引发错误。所以需要使用volatile关键字禁止指令重排。

代码实现

public class Singleton {
    
    
    private volatile static Singleton singleton; 

    private Singleton (){
    
    

    }
    public static Singleton getSingleton() {
    
      
        if (singleton == null) {
    
      
            synchronized (Singleton.class) {
    
    
                if (singleton == null) {
    
    
                    singleton = new Singleton();  
                }
            }
        }
        return singleton;
    }
}

5. 枚举

不能延迟加载,线程安全

这种方式是《Effective Java》作者提倡的方式。最简洁的一种实现方式,提供了序列化机制,保证线程安全,绝对防止多次实例化,即使是在面对复杂的序列化或者反射攻击的时候,只是在工作中比较少见。

public enum EnumSingleton {
    
    
    INSTANCE;
    public void doSomething() {
    
    
       System.out.println("hello world");
    }
}

public class Test {
    
    
    public static void main(String[] args) {
    
    
        EnumSingleton.INSTANCE.doSomething();
    }
}

总结

在本文中,我们详细介绍了Java单例模式的设计原理、几种典型的实现方式以及优缺点分析。单例模式是一种非常实用且常用的设计模式,它可以帮助我们提高代码的可读性和可维护性,节省系统资源,提高性能。

在实际的开发过程中,我们应该根据具体的场景选择合适的单例模式实现方式。同时,我们要时刻关注单例模式的优缺点,确保在使用的过程中避免潜在的问题。

希望这篇文章能够帮助你更好地理解和掌握Java单例模式,为你的编程之旅带来启示。如果你觉得本文对你有所帮助,欢迎分享给更多的小伙伴们!记得关注我的博客,我们下次再见!


在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_36756227/article/details/130450147