【Java 设计模式 · 创建型】单例模式(Singleton Pattern)

创建型模式:关注对象的创建过程,它描述如何将对象的创建、使用分离,让用户无需关心对象的创建细节,从而降低系统的耦合度,让设计方案易于修改、扩展。

Windows 中的任务管理器,就是典型的单例模式,无论如何,只允许一个任务管理器存在:

任务管理器

一、概述

单例模式(Singleton Pattern):确保一个类只有一个实例,并提供一个全局访问点来访问这个实例。

三个要点:

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

单例模式

二、创建方式

① 饿汉式

饿汉式(Eager Singleton)是实现起来最简单的单例类。由于在定义静态变量的时候实例化单例类,因此在类加载时单例对象就已创建。
饿汉式 单例模式

当类加载时,静态变量 INSTANCE 被初始化,唯一的实例被创建。

public class EagerSingleton {
	//final修饰的静态引用,直接创建对象,一般不存在线程安全问题
    private static final EagerSingleton INSTANCE = new EagerSingleton();
	//私有的构造方法
    private EagerSingleton(){ }
	//静态的获取方法
    public static EagerSingleton getInstance() {
        return INSTANCE;
    }
}

② 懒汉式

懒汉式单例类(Lazy Singleton)的构造函数也是私有的,与饿汉式不同,懒汉式单例类在第一次被使用时将自己实例化。
懒汉式 单例模式
从图中可以看出,懒汉式单例在类加载时并不实例化,而是在第一次调用 getInstance() 方法时实例化,这种技术被称为 延迟加载 (Lazy Load) 技术,即需要的时候再加载实例.

为了避免多个线程同时调用 getInstance() 方法,可使用 双重检查锁定 (Double-Check Locking),这种方法相比于直接使用 synchronized 关键字修饰的好处在于:

避免 一瞬间两线程同时调用getInstance() 方法时,均能通过 "instance == null"的判断,串行创建两对象的情况。这种情况产生多个单例对象,违背了单例模式的设计思想。

public class LazySingleton {
	//静态实例(volatile保证了对象的有序性、可见性)
    private volatile static LazySingleton instance = null;
	//私有的构造方法
    private LazySingleton() { }
	//静态的获取方法,双重检查锁定,保证安全问题
    public static LazySingleton getInstance() {
        //第一重判断
        if (instance == null) {
            //锁定代码块
            synchronized (LazySingleton.class) {
                //第二重判断
                if (instance == null){
                    instance = new LazySingleton();
                }
            }
        }
        return instance;
    }
}

如若使用双重检查锁定,需要使用volatile修饰静态变量成员,且须在 JDK 1.5+版本运行。由于volatile关键字会屏蔽 JVM 所做的一些代码优化,可能导致效率降低。

③ 静态内部类式

饿汉式单例类不能实现延迟加载,不管将来用不用始终占据内存;懒汉式单例类线程安全控制繁琐,性能受影响。

为克服以上问题,可通过Initialization on Demand Holder (IoDH)技术实现单例问题,这种方法须增加一个静态(static)内部类,再通过 getInstance() 方法返回给 外部使用,实现代码:

public class Singleton {
	//私有的构造方法
    private Singleton(){ }
    //静态内部类
    private static class HolderClass{
        private final static Singleton INSTANCE = new Singleton();
    }
    //静态的获取方法
    public static Singleton getInstance(){
        return HolderClass.INSTANCE;
    }
}

第一次调用getInstance() 时将加载内部类 HolderClass,在内部类定义了static 类型的变量 instance,由 JVM 来保证其线程安全性,确保该成员变量只能初始化一次。由于 getInstance() 方法没有任何线程锁定,因此对性能不会造成任何影响。

④ 枚举式(饿汉式)

单元素的 枚举类型 已经成为实现Singleton的最佳方法
                     —— 出自 《Effective Java》

public enum EnumSingleton {
	//直接写即可,由于可直接获取对象,getInstance() 方法无直接意义
    INSTANCE
}

三、特点

以上代码可能还存在反射攻击或者反序列化攻击

享有特权的客户端可以借助AccessibleObject.setAccessible方法,通过反射机制调用私有构造器。如果需要抵御这种攻击,可以修改构造器,让它在被要求创建第二个实例的时候抛出异常。
                     —— 出自 《Effective Java》

☯ 优点

  • 提供了唯一实例的受控访问。
  • 在系统内存中只存在一个对象,可节约系统资源。
  • 允许可变的数目的实例。基于单例模式可扩展获得指定数量的实例,这样的类称为多例类。
    (解决了由于单例对象共享过多有损性能的问题)

☯ 缺点

  • 单例模式没有抽象层,扩展较为困难。
  • 单例类职责过重,一定程度上违背了单一职责原则。单例类即提供了业务方法,由提供了工厂方法,功能耦合。
  • 有一定概率会被GC回收,可能存在状态丢失问题。

四、扩展:多例模式

多例模式(Multiton Pattern)是单例模式的一种扩展,有限的多例模式须配合容器,并为每个对象分配唯一 标识序号,多例模式的一种实现代码如下:

public class Multipleton {
    //实例最大数量
    private final static int MAX_INSTANCE = 10;
    // 存放N个实例对象的容器
    private static ArrayList<Multipleton> list = new ArrayList<Multipleton>(MAX_INSTANCE);
    // 每个对象的序号 标识
    private int no;
    // 私有构造方法 防止外界应用程序实例化
    private Multipleton(int no) {
        this.no = no;
    }
    // 实例化N个对象实例
    static {
        // 添加Multipleton对象实例
        for (int i = 0; i < MAX_INSTANCE; i++) {
            list.add(new Multipleton(i));
        }
    }
    /**
     * 随机获得 实例对象
     */
    public static Multipleton getRandomInstance() {
        // 获得随机数字
        int num = (int) (Math.random() * MAX_INSTANCE);
        // 获得list中的对象实例
        return list.get(num);
    }
    public int getNo() {
        return no;
    }
    public void setNo(int no) {
        this.no = no;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值