天天看点

设计模式——单例模式详解

本篇文章主要介绍设计模式——单例模式详解。

设计模式——单例模式详解

单例

介绍

  单例模式(Singleton Pattern)是Java中最基础最简单的设计模式之一,这种模式属于

创建型模式

,提供的就是一种创建对象的方式。这种模式中的单一类创建自己的对象,确保只有一个对象被创建,并且为所有对象提供一个访问它的全局访问点。

  单例模式用来解决频繁创建与销毁实例对象问题,当我们想要控制实例创建个数或者复用实例时,就可以使用单例模式,这样有助于节省系统资源。

特点

  1. 单例类,顾名思义,只有一个实例。
  2. 单例类必须是自己创建自己的唯一实例。
  3. 单例类必须给所有对象提供这个唯一实例。
  4. 构造函数私有。

应用场景

适用范围

  1. 频繁的访问数据库或文件的对象。
  2. 频繁的需要实例化,然后销毁的对象。
  3. 创建对象耗时长且耗资源,但是又需要常用的对象。

使用举例

  1. Windows的任务管理器,每次只能打开一个。
  2. 应用程序的日志应用。
  3. 网上在线人数统计。
  4. 配置文件的访问类。
  5. 数据库的连接池。
  6. 多线程的线程池。
  7. 操作系统的文件系统。

设计思路

  1. 一个类每次都返回唯一的对象(每次返回都是同一个对象实例)。
  2. 提供一个获取该实例的方法。(基本上都是静态方法)

加载分类

  1. 提前加载

    :在应用开始时就创建单例实例。
  2. 延迟加载

    :在

    getInstance()方法

    首次被调用时才调用单例创建,即需要使用时,才加载创建单例对象实例。

单例分类

1 饿汉式单例

  1. 类加载时就初始化好实例对象,容易产生垃圾对象,浪费内存。
  2. 无锁,执行效率高。
  3. 天生线程安全,因为类加载时就已初始化好实例。

代码

/**
 * 饿汉式单例
 * @author Andya
 * @date 2021/3/9
 */
public class EHanSingleInstance {

    //饿汉式:初始化时就建好实例
    private static final EHanSingleInstance instance = new EHanSingleInstance();

    private EHanSingleInstance() {
        doSomething();
    }

    public static EHanSingleInstance getInstance() {
        return instance;
    }

    public void doSomething() {

    }
}
           

2 懒汉式单例

  1. 饿汉式属于延迟加载初始化。
  2. 若不加锁,则为非线程安全的。
  3. 若加锁,则为线程安全的,但效率很低,基本上每次都要同步。

无锁懒汉式代码

/**
 * 无锁懒汉式单例,非线程安全
 * @author Andya
 * @date 2021/3/9
 */
public class LanHanNoLockSingleInstance {

    //懒汉式:初始化时不创建实例,等需要时再创建
    private static LanHanNoLockSingleInstance instance;

    private LanHanNoLockSingleInstance() {
        doSomething();
    }

    public static LanHanNoLockSingleInstance getInstance() {
        //先判断是否为空,若为空创建一个新的实例
        if (instance == null) {
            instance = new LanHanNoLockSingleInstance();
        }

        return instance;
    }

    public void doSomething() {

    }
}
           

加锁懒汉式代码

/**
 * 加锁懒汉式单例,线程安全
 * @author Andya
 * @date 2021/3/9
 */
public class LanHanWithLockSingleInstance {

    //懒汉式:初始化时不创建实例,等需要时再创建
    private static LanHanWithLockSingleInstance instance;

    private LanHanWithLockSingleInstance() {
        doSomething();
    }

    public static synchronized LanHanWithLockSingleInstance getInstance() {
        //先判断是否为空,若为空创建一个新的实例
        if (instance == null) {
            instance = new LanHanWithLockSingleInstance();
        }

        return instance;
    }

    public void doSomething() {

    }
}
           

3 双重检锁式单例

  在多线程应用中使用这种模式可以保证线程安全。因为如果实例为空,有可能存在两个线程同时调用

getInstance()

方法的情况,这样的话,第一个线程会首先使用新构造器实例化一个单例对象,但此时它还没有完成单例对象的实例化操作,同时第二个线程也检查到单例实例为空,也会开始实例化单例对象,这就造成了2次实例化对象。所以多线程应用中,需要

来检查实例是否线程安全。

  1. 静态方法锁
public static synchronized Singleton getInstance()
           
  1. 代码块锁
synchronized(SingleInstance.class){
	if (instance == null) {
		instance = new SingleInstance();
	}
}
           

  虽然加锁可以保证线程安全,但是会带来延迟,因为加锁后,代码块在同一时刻只能被一个线程执行,但是同步锁只有在实例没被创建的时候才会起作用。如果单例实例已经被创建,其实不需要走该步骤。因此,我们可以在代码块锁外面再加一层实例空判断。

instance == null

被检查

2

次。

if (instance == null) {
	synchronized(SingleInstance.class){
		if (instance == null) {
			instance = new SingleInstance();
		}
	}
}
           

/**
 * 双检锁单例
 * @author Andya
 * @date 2021/3/9
 */
public class DoubleCheckSingleInstance {

    //使用volatile保证多线程的可见性
    private static volatile DoubleCheckSingleInstance instance;

    private DoubleCheckSingleInstance() {
        doSomething();
    }

    public static DoubleCheckSingleInstance getInstance() {
        //第一次检查是否创建过该单例
        if (instance == null) {
            //加锁,保证线程安全
            synchronized (DoubleCheckSingleInstance.class) {
                if (instance == null) {
                    instance = new DoubleCheckSingleInstance();
                }
            }
        }
        return instance;
    }

    public void doSomething() {

    }
}
           

4 静态内部类单例

  1. 延迟加载初始化实例instance。
  2. 线程安全。
  3. 该方式只适用于静态域的情况。
  4. 该方式单例不会立即初始化,只有显式调用getInstance()方法时,才会显式转载静态内部类,才会实例化instance。

/**
 * 静态内部类单例
 * @author Andya
 * @date 2021/3/9
 */
public class StaticInternalSingleInstance {

    //静态内部类
    private static class StaticInternalSingleInstanceHolder{

        private static final StaticInternalSingleInstance INSTANCE
                = new StaticInternalSingleInstance();

    }

    private StaticInternalSingleInstance() {
        doSomething();
    }

    public static final StaticInternalSingleInstance getInstance() {
        return StaticInternalSingleInstanceHolder.INSTANCE;
    }

    public void doSomething() {

    }

}
           

5 枚举类单例

  1. 不属于延迟加载初始化实例instance。
  2. 多线程安全。
  3. 支持序列化机制,从而防止多次实例化。

/**
 * 枚举型单例
 * @author Andya
 * @date 2021/3/9
 */
public enum EnumSingleInstance {

    INSTANCE;

    public EnumSingleInstance getInstance(){
        return INSTANCE;
    }
}
           

总结

  一般在开发应用中,建议使用第1种饿汉式单例,而不推荐使用第2种懒汉式单例,除非明确需要延迟加载时,才会使用第4种静态内部类单例,若涉及到反序列化创建对象时,推荐使用第5种枚举式单例。其他也可以考虑使用第3种双重检锁式单例。

烧不死的鸟就是凤凰

继续阅读