设计模式一: 单例模式(Singleton)

简介: 简介单例模式是属于创建型模式的一种(另外两种分别是结构型模式,行为型模式).是设计模式中最为简单的一种.英文单词Singleton的数学含义是"有且仅有一个元素的集合".从实现层面看, 由类自身管理自己的唯一对象,这个类提供了访问该对象的方式,可以直接访问,不需要实例化(使用new).

简介

单例模式是属于创建型模式的一种(另外两种分别是结构型模式,行为型模式).是设计模式中最为简单的一种.

英文单词Singleton的数学含义是"有且仅有一个元素的集合".

从实现层面看, 由类自身管理自己的唯一对象,这个类提供了访问该对象的方式,可以直接访问,不需要实例化(使用new).

动机

设计模式中的Singleton的目的是使类的对象在应用程序中保持唯一,这在某些应用场合非常重要,比如文件系统的资源管理器,又比如应用的日志,应用程序配置等. 保持唯一实例有利于节约系统资源, 同时提升了应用程序性能,避免对资源的多重占用.

实现

一. 简单实现(线程不安全)-懒汉模式

public class Singleton{
    private static Singleton onlyOneInstance;

    private Singleton(){
    }

    public static Singleton getOnlyOneInstance(){
        if(onlyOneInstance == null){
            onlyOneInstance = new Singleton();
        }
        return onlyOneInstance;
    }
}

如果有多个线程同时运行到if(onlyOneInstance == null),并且此时实例为null,那多个线程会执行onlyOneInstance = new Singleton();语句,导致实例化多次.

二. 简单实现(线程安全)-懒汉模式

public static synchronized Singleton getOnlyOneInstance(){
    if(onlyOneInstance == null){
        onlyOneInstance = new Singleton();
    }
    return onlyOneInstance;
}

在方法getOnlyOneInstance()上增加同步修饰符synchronized,这样可以保证同一时间只有一个线程访问方法,从而确保不会发生多次实例化.

问题是多个线程访问该方法时会发生阻塞,导致其他线程会在该方法上等待,因此性能上有损耗.

三. 最简单实现(线程安全)-饿汉模式

private static Singleton onlyOneInstance = new Singleton();

这个实现是线程安全的, 但同时也没有延迟加载带来的节约资源的好处.

四. 双重校验锁(线程安全)

public class Singleton{

    private volatile static Singleton onlyOneInstance;

    private Singleton(){
    }

    public static Singleton getOnlyOneInstance(){
        if(onlyOneInstance == null){
            synchronized(Singleton.class){
                if(onlyOneInstance == null){
                    onlyOneInstance = new Singleton();
                }
            }
        }
        return onlyOneInstance;
    }
}

与第二点的代码比较,本方法将同步锁放在了方法内部,这样可以减少部分线程阻塞,因为在第一个判断if(onlyOneInstance == null)时如果实例不为null,方法就返回了.

与第二点的代码比较,多个线程依然可能会同时进入到if(onlyOneInstance == null),同样的,此时onlyOneInstance有可能是null,因此需要在synchronized(Singleton.class)内部再次判断if(onlyOneInstance == null)

onlyOneInstance 使用 volatile 修饰的必须的.解释这点的原因需要深入到 JVM 的指令重排机制.onlyOneInstance = new Singleton();的执行需要分三步:

1. 分配内存
2. 初始化对象
3. 将 onlyOneInstance 指向分配的内存地址

由于 JVM 的指令重排特性,上述三步步骤可能会重排为 1>3>2 ,在多线程环境下访问onlyOneInstance时, 有可能会得到一个未被初始化的实例.而valatile关键字可以阻止 JVM 的指令重排,从而保证多线程环境下正常运行.

五. 静态内部类实现

public class Singleton{

    private Singleton(){}

    private static class SingletonHolder{
        private static final Singleton INSTANCE = new Singleton();
    }

    public static Singleton getUniqueInstance(){
        return SingletonHolder.INSTANCE;
    }
}

这种实现利用了 JVM 保持线程安全性, 同时也具备了延迟加载的好处.

六. 枚举实现

public enum Singleton{
    INSTANCE
}

单例的最佳实践, 一则实现简单, 二则面对复杂的序列化或反射攻击的时候能够防止实例化多次.

反射攻击:可以使用反射原理, 通过setAccessible()方法提升构造函数的访问级别为public,然后通过new实例化对象.

对于序列化和反序列化,因为每一个枚举类型和枚举变量在JVM中都是唯一的,即Java在序列化和反序列化枚举时做了特殊的规定,枚举的writeObject、readObject、readObjectNoData、writeReplace和readResolve等方法是被编译器禁用的,因此也不存在实现序列化接口后调用readObject会破坏单例的问题。

枚举为什么是最佳实践?

首先, 枚举是class实现的,也就是说它可以有成员变量及成员函数,这是我们可以使用它来实现单例的基础. 另外,枚举继承了Enum类,它不能作为子类继承其他类,但可以实现接口, 它也不能被其他类继承, 枚举编译后的类会添加final修饰符, 编译后的代码如下:

public final class EnumClass extends Enum{

}

其次,枚举有且仅有private构造器,这点满足单例模式的条件.而枚举值的初始化是在静态代码中进行.枚举实现的单例模式不具备懒加载的作用.

相关文章
|
29天前
|
设计模式 安全 测试技术
【C/C++ 设计模式 单例】单例模式的选择策略:何时使用,何时避免
【C/C++ 设计模式 单例】单例模式的选择策略:何时使用,何时避免
59 0
|
1月前
|
设计模式 缓存 安全
【设计模式】单例模式:确保类只有一个实例
【设计模式】单例模式:确保类只有一个实例
20 0
|
3月前
|
设计模式 安全 Java
设计模式-单例模式
设计模式-单例模式
36 0
|
1月前
|
设计模式 安全 Java
设计模式之单例模式
设计模式之单例模式
|
9天前
|
设计模式 存储 Java
Java设计模式:解释一下单例模式(Singleton Pattern)。
`Singleton Pattern`是Java中的创建型设计模式,确保类只有一个实例并提供全局访问点。它通过私有化构造函数,用静态方法返回唯一的实例。类内静态变量存储此实例,对外仅通过静态方法访问。
14 1
|
1月前
|
设计模式 存储 缓存
设计模式之单例模式(C++)
设计模式之单例模式(C++)
22 2
|
1月前
|
设计模式 安全 Java
Java设计模式之单例模式
在软件工程中,单例模式是一种常用的设计模式,其核心目标是确保一个类只有一个实例,并提供一个全局访问点来获取这个实例。Java作为一门广泛使用的编程语言,实现单例模式是面试和实际开发中的常见需求。
66 9
Java设计模式之单例模式
|
2月前
|
设计模式 存储 安全
【设计模式】创建型模式之单例模式(Golang实现)
【2月更文挑战第3天】一个类只允许创建一个对象或实例,而且自行实例化并向整个系统提供该实例,这个类就是一个单例类,它提供全局访问的方法。这种设计模式叫单例设计模式,简称单例模式。
34 1
|
3月前
|
设计模式 安全 Java
【设计模式】单例模式
【1月更文挑战第27天】【设计模式】单例模式
|
3月前
|
设计模式 安全 Java
Java设计模式—单例模式的实现方式和使用场景
那么为什么要有单例模式呢?这是因为有的对象的创建和销毁开销比较大,比如数据库的连接对象。所以我们就可以使用单例模式来对这些对象进行复用,从而避免频繁创建对象而造成大量的资源开销。
54 1