Java设计模式(五):单例模式(单件模式或单态模式)

模式动机

对于系统中的某些类来说,只有一个实例很重要,例如,一个系统中可以存在多个打印任务,但只能有一个正在工作的任务。保证一个类只有一个实例对象就是单例模式的动机。一个好的解决方法是,让类负责保存它唯一的实例,这个类可以保证没有其他实例被创建,并且可以提供一个访问该实例的方法。

模式定义

单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类称为单例类,它提供全局的方法。

单例模式的三大要点:

  • 某个类只能有一个实例
  • 这个类必须自行创建这个实例
  • 该类必须自行向整个系统提供这个实例

模式结构

私有成员变量:Singleton
静态public方法:getInstance()、singletonOperation()
私有构造方法:Singleton(),实例化Singleton类
在这里插入图片描述

时序图

在这里插入图片描述

代码实现

//线程不安全,懒汉单例模式
/**
 * 优点:懒加载启动快,资源占用小,使用时才实例化,无锁。
 * 缺点:非线程安全。
 **/
public class lazySingleton {
    
    
    private static lazySingleton singleton=null;

    private lazySingleton(){
    
    

    }

    public static lazySingleton getInstance(){
    
    
        if(singleton==null){
    
    
            singleton=new lazySingleton();
        }
        return singleton;
    }
}


//懒汉式单例,但是线程安全
/**
 *  优点:懒加载启动快,资源占用小,使用时才实例化,有锁。
 *
 *  缺点:synchronized 为独占排他锁,并发性能差。即使在创建成功以后,获取实例仍然是串行化操作。
 *
 **/
public class safeLazySingleton {
    
    
    private static safeLazySingleton singleton=null;

    private safeLazySingleton(){
    
    

    }

    //加上synchronized,多个线程调用同一个对象的同步方法(synchronized关键字修饰的方法)时会阻塞
    public static synchronized safeLazySingleton getInstance(){
    
    
        if(singleton==null){
    
    
            singleton=new safeLazySingleton();
        }
        return singleton;
    }
}


/**
 * 懒汉模式--双重加锁检查DCL(Double Check Lock)
 * 优点:懒加载,线程安全。
 *
 **/
public class DCLLazySingleton {
    
    

    private static DCLLazySingleton singleton=null;

    private DCLLazySingleton(){
    
    

    }

    //加上synchronized锁
    public static synchronized DCLLazySingleton getInstance(){
    
    
        if(singleton==null){
    
    
            //对DCLLazySingleton类加锁,多个线程实例化该类时会阻塞。
            synchronized(DCLLazySingleton.class){
    
    
                if(singleton==null) {
    
    
                    singleton = new DCLLazySingleton();
                }
            }

        }
        return singleton;
    }

}


/**
 *
 * 饿汉单例模式
 * 优点:天生是线程安全的,使用时没有延迟。
 * 缺点:启动时即创建实例,启动慢,有可能造成资源浪费。
 **/
public class HungerSingleton {
    
    
    private static HungerSingleton singleton=new HungerSingleton();

    private HungerSingleton(){
    
    

    }

    public static HungerSingleton getInstance(){
    
    
        return singleton;
    }
}


//将懒加载和线程安全完美结合的一种方式(无锁)。(推荐)
/*加载时无需创建初始化私有成员变量(懒加载),在实例化时调用内部静态类,实例化单例对象,天生线程安全
* */
public class HolderSingleton {
    
    

    //类的内部静态类,该内部类与外部类的实例没有绑定关系,只有被调用时才会被加载,实现了延迟加载
    private static class SingletonHolder{
    
    
        private static HolderSingleton singleton=new HolderSingleton();
    }

    private HolderSingleton(){
    
    

    }

    public static  HolderSingleton getInstance(){
    
    
        return SingletonHolder.singleton;
     }

}

模式分析

单例模式的目的是保证一个类仅有一个实例,并提供一个访问它的全局访问点。单例模式包含的角色只有一个,就是单例类——Singleton。单例类拥有一个私有构造函数,确保用户无法通过new关键字直接实例化它。除此之外,该模式包含一个静态私有成员变量静态公有的工厂方法。该工厂方法检验实例的存在性并实例化自己,然后存储在静态成员变量中,以确保只有一个实例被创建。

所以,单例模式的实现要注重如下三点:

  • 单例类的构造函数为私有;
  • 提供一个自身的静态私有成员变量;
  • 提供一个公有的静态工厂方法。

模式的优缺点

优点

  • 提供了对唯一实例的受控访问。
  • 由于在系统内存中只存在一个对象,因此可以节约系统资源,对于一些需要频繁创建和销毁的对象,单例模式无疑可以提高系统的性能。
  • 允许可变数目的实例。基于单例模式进行扩展,可以获得指定数目的对象实例

缺点

  • 由于单例模式中没有抽象层,因此单例类的扩展有很大的困难。
  • 单例类的职责过重,在一定程度上违背了“单一职责原则”。因为单例类既充当了工厂角色,提供了工厂方法,同时又充当了产品角色,包含一些业务方法,将产品的创建和产品的本身的功能融合到一起。
  • 现在很多面向对象语言(如Java、C#)的运行环境都提供了自动垃圾回收的技术,因此,如果实例化的对象长时间不被利用,系统会认为它是垃圾,会自动销毁并回收资源,下次利用时又将重新实例化,这将导致对象状态的丢失

模式实例

  • 数据库主键编号生成器。数据库只能有一个地方分配下一个主键编号。
  • 操作系统中的打印池,一个系统中只允许运行一个打印池对象

参考

https://design-patterns.readthedocs.io/zh_CN/latest/creational_patterns/singleton.html