Skip to content

单例模式

一、介绍

单例模式(Singleton Pattern)是设计模式中最简单,同时也是最常见的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

单例模式是一种创建型设计模式,它确保一个类只有一个实例,并提供了一个全局访问点来访问该实例。

二、特点

  • 单一实例: 单例模式确保一个类只有一个实例对象。这个实例通常在首次访问时被创建,并在整个应用程序的生命周期内持续存在。
  • 全局访问点: 单例模式提供一个全局的访问点,允许其他对象在程序中访问单例实例。这使得单例对象成为一个全局资源。
  • 私有构造方法: 单例类通常会将其构造方法声明为私有,以防止外部直接实例化。这样,只有单例类内部能够控制实例的创建。
  • 懒加载或饿汉式: 单例可以采用懒加载(Lazy Initialization)方式,在需要时才创建实例,或者采用饿汉式,在类加载时就创建实例。懒加载的方式能够节省资源,而饿汉式则保证了在多线程环境中的线程安全性。
  • 线程安全性: 在多线程环境下,确保单例模式的线程安全性是关键。常见的实现方式包括使用双重检查锁定、静态内部类、枚举等机制来保证线程安全。
  • 全局状态: 单例模式可能引入全局状态,这可能会对应用程序的可维护性和测试性产生影响。因此,在使用单例模式时,需要注意全局状态的管理。
  • 可扩展性: 单例模式有助于将资源管理和控制封装在一个地方。这样,如果以后需要修改实例化逻辑,只需要修改单例类而不是整个代码库。

总体而言,单例模式适用于需要一个全局访问点,且在整个应用程序生命周期内只需要一个实例的情况。在设计中,需要权衡资源使用、线程安全性和全局状态等因素,选择适合特定需求的单例实现方式。

三、使用场景

  1. 资源共享: 当应用程序中的多个模块需要共享某个资源(例如配置信息、数据库连接、线程池等)时,可以使用单例模式确保全局只有一个实例,避免资源的浪费。
  2. 日志记录: 单例模式可以用于创建日志记录对象,以确保在整个应用程序中只有一个记录日志的实例,方便集中管理和控制日志输出。
  3. 工具类: 单例模式在创建工具类时非常有用,例如日志管理、配置管理等。通过单例模式,可以将这些工具类的实例化过程封装在一个类中,提供全局的访问点。
  4. spring容器:spring容器管理的bean都是单例的

四、实现方式

4.1 懒汉式

  • 优点:在使用的时候才进行加载
  • 缺点:如果不加锁会出现线程安全问题,加锁第一次加载的时候如果请求量比较大会进行阻塞

主要讲解常用的一些实现,如登记式、枚举等可自行了解

4.1.1 线程不安全

这种方式是最基本的实现方式,这种实现最大的问题就是不支持多线程,严格意义上它并不算单例模式。

package com.lcy.study.design.singleton;

/**
 * 单例模式懒汉加载-非线程安全
 * @Author lcy
 * @Date 2021/7/19 14:11
 */
public class SingletonLazyNoSafe {

    /**
     * 私有静态变量
     */
    private static SingletonLazyNoSafe singletonLazy;

    private SingletonLazyNoSafe(){}

    /**
     * 公有方法,提供全局访问点
     * @return com.lcy.study.design.singleton.SingletonLazy
     * @author lcy
     * @date 2021/7/19 14:18
     **/
    public static SingletonLazyNoSafe newInstance(){
        if (singletonLazy == null) {
            singletonLazy = new SingletonLazyNoSafe();
        }
        return singletonLazy;
    }

}

4.1.2 线程安全-全局锁

线程安全通过synchronized锁保证,但是锁的颗粒度大

package com.lcy.study.design.singleton;

/**
 * 单例模式懒汉加载-线程安全synchronized锁
 * @Author lcy
 * @Date 2021/7/19 14:11
 */
public class SingletonLazySync {

    /**
     * 私有静态变量
     */
    private static SingletonLazySync singletonLazy;

    private SingletonLazySync(){}

    /**
     * 通过加锁保证单例。但是锁颗粒度大
     * @return com.lcy.study.design.singleton.SingletonLazy
     * @author lcy
     * @date 2021/7/19 14:18
     **/
    public synchronized static SingletonLazySync newInstance(){
        if (singletonLazy == null) {
            singletonLazy = new SingletonLazySync();
        }
        return singletonLazy;
    }

}

4.1.3 线程安全-double check(推荐)

全局锁由于锁的颗粒度较大,每次在获取单例对象的时候会获取一次锁,影响性能,这里减少锁的颗粒度,提高效率。

注意:现在高版本的Java(JDK9) 已经在JDK 内部实现中解决了这个问题,把对象的new 操作和初始化操作设计为原子操作。jdk9及以后得版本是不需要加volatile修饰变量。

package com.lcy.study.design.singleton;

/**
 * 单例模式线程安全-double check
 * @Author lcy
 * @Date 2021/7/19 14:11
 */
public class SingletonLazy {

    /**
     * 私有静态变量,用于存储唯一实例,加上volatile禁止jvm指令重排
     */
    private volatile static SingletonLazy singletonLazy;

    private SingletonLazy(){}

    /**
     * 公有方法,提供全局访问点 通过加锁保证单例,减少锁颗粒度
     * <p>
     * 使用DCL(double-checked-locking) 双重检查
     * <p>
     * 使用volatile防止jvm指令重排,原因: new 对象正常的顺序为 1、分配空间给对象 2、分配内存地址给对象 3、将对象赋值给引用instance
     * <p>
     * @return com.lcy.study.design.singleton.SingletonLazy
     * @author lcy
     * @date 2021/7/19 14:18
     **/
    public static SingletonLazy newInstance(){
        if (singletonLazy == null) {
            //多线程的情况下可能会出现多个线程进入到这里等待锁,里层需要再判断null
            synchronized (SingletonLazy.class) {
                if (singletonLazy == null) {
                    singletonLazy = new SingletonLazy();
                }
            }

        }
        return singletonLazy;
    }

}

4.2 饿汉加载

饿汉式实现相对简单,但是比较浪费资源,取决于具体使用的场景,比如spring容器启动的时候加载的bean默认就是饿汉式

  • 优点:实现简单,没有线程安全问题
  • 缺点:对象在没有被使用的时候始终大量占用内存
package com.lcy.study.design.singleton;

/**
 * 单例模式饿汉加载
 * @Author lcy
 * @Date 2021/7/19 14:11
 */
public class SingletonHungry {

    /**
     * 单例对象
     */
    private static final SingletonHungry singletonLazy = new SingletonHungry();

    private SingletonHungry(){}

    /**
     * 获取单例对象
     * @return com.lcy.study.design.singleton.SingletonLazy
     * @author lcy
     * @date 2021/7/19 14:18
     **/
    public static SingletonHungry newInstance(){
        return singletonLazy;
    }

}