每天一个设计模式之单例模式

什么是“单例模式”

单例模式:确保某个类只有一个实例,并且只能自行实例化并且向系统提供这个示例。

单例模式有几个特点:

  • 单例类只能有一个实例
  • 单例类必须自己创建自己的实例
  • 单例类必须给其他类提供这个实例

单例模式在很多场景都可以使用到,比如线程池、缓存、日志对象、打印机或者显卡驱动的对象等等,这些场景下,如果有多个实例的话,可能会导致程序的行为异常、资源使用过量等问题的出现。因此,学习单例模式是很有必要的。

如何实现单例模式

在单例模式中,主要有两种方式实现单例模式:

  • 饿汉式
  • 懒汉式

1. 懒汉式

懒汉:顾名思义,很懒!所以这个实例是在第一次调用的时候才会进行实例化。

//懒汉式单例类.在第一次调用的时候实例化自己   
public class Singleton {  
    private Singleton() {}  
    private static Singleton single=null;  
    //静态工厂方法   
    public static Singleton getInstance() {  
         if (single == null) {    
             single = new Singleton();  
         }    
        return single;  
    }  
}  

从代码中可以看出,实现懒汉式单例类有两个步骤:

  1. 将构造方法私有化,并将对象设置为静态私有
  2. 提供一个静态方法供外部程序调用获取实例。

实现了以上两个步骤之后,当外部程序第一个访问这个单例类的时候,Singleton将会进行实例化并返回。

那么,考虑一个问题:多线程下会发生什么问题?

我们可以把自己当做JVM来运行一下这个程序:

线程一进入getInstance 方法,判断 single == null之后,线程二抢夺了CPU的资源,线程一被挂起,线程二也进入 getInstance 方法,此时因为在线程一中还没有对 Singleton 进行实例化,因此线程二判断为 null ,实例化对象后成功返回。

这是执行权交回线程一,在停掉的那个地方继续往下执行,实例化 Singleton 对象,再返回。

这样,导致返回了两个不同的对象,破坏了单例模式的原则:单例类只能有一个实例

那么,怎么解决呢?

我们有两种方法解决这个问题:

  1. 在getInstance方法上加同步

    public static synchronized Singleton getInstance() {  
            if (single == null) {    
                single = new Singleton();  
            }    
           return single;  
    } 
    
  2. 双重检查加锁

    private volatile static Singleton single;  
    public static Singleton getInstance() {  
           if (singleton == null) {    
               synchronized (Singleton.class) {    
                  if (singleton == null) {    
                     singleton = new Singleton();   
                  }    
               }    
           }    
           return singleton;   
    } 

两种方法的区别

  1. 第一种方式在方法上加了 synchronized ,虽然保证了线程安全,但是每次调用的时候都要进行同步,降低了性能,因为99%的情况下都是不需要同步的。
  2. 第二种方法使用了双重检查加锁的方式,只有在第一次实例化的时候才需要同步。这种方法既保证了线程安全,又保证性能没有大幅度下降。

2. 饿汉式

饿汉式,这个方法就比较简单了。看名字我们也可以看出来,饿汉,那么就代表着在未访问之前就将单例类实例化好了。

下面是它的代码实现:

//饿汉式单例类.在类初始化时,已经自行实例化   
public class Singleton1 {  
    private Singleton1() {}  
    private static final Singleton1 single = new Singleton1();  

    public static Singleton1 getInstance() {  
        return single;  
    }  
}  

饿汉式在类创建的同时,就已经实例化了一个静态对象,因此是天生线程安全的。

懒汉式和饿汉式的区别

  1. 线程安全

    饿汉式天生线程安全,可以直接使用在多线程环境下。

    懒汉式本身是非线程安全的,要实现线程安全请参考上面代码。

  2. 性能

    饿汉式在类初始化的时候就会进行实例化单例类。 因此, 在初始化初期会占用较大资源。 但是相应带来的好处就是在第一次调用的时候速度变快。

    懒汉式的初始化策略就是 “ 延迟加载 ” , 因此在第一次调用的时候会占用较大资源。而且在保证线程安全的解决方法中也存在不同的性能问题(区别请往上回顾一下)。

单例模式的优缺点

优点:

  1. 因为只有一个实例,减少了系统性能开支。
  2. 避免对内存的多重占用。
  3. 可以在系统中设置全局访问点,优化和共享资源访问。

缺点:

  1. 拓展困难。(没有接口)
  2. 对测试不利。
  3. 与单一原则冲突。一个类负责单一功能 。而单例模式 将单例类的实例化和其他业务逻辑放在同一个类中。

感谢阅读本博客。

欢迎关注我的博客:https://li-weijian.github.io/

欢迎关注我的CSDN:https://blog.csdn.net/qq352642663

需要联系请加QQ:352642663

欢迎联系我共同交流

猜你喜欢

转载自blog.csdn.net/qq352642663/article/details/80633433