赐萧瑀
- 实现方案
- 饿汉模式
- 懒汉式(非线程安全)
- 懒汉模式(线程安全)
- 双重检查锁定
- 静态内部类
- 攻击方式
- 序列化攻击
- 反射攻击
- 枚举(最佳实践)
- 枚举是一种类
唐 李世民
疾风知劲草,板荡识诚臣。
勇夫安识义,智者必怀仁。
实现单例模式的主要方式有:饿汉模式、懒汉模式(非线程安全)、懒汉模式(线程安全)、双重检查锁定、静态内部类和枚举方式。攻击方式有克隆攻击、序列化攻击和反射攻击。
实现方案
序号 | 实现方式 | 描述 | 优点 | 缺点 |
---|---|---|---|---|
1 | 饿汉式 | 在类加载时就创建实例 | 实现简单,线程安全 | 如果实例未被使用,会造成资源浪费 |
2 | 懒汉式(非线程安全) | 在第一次调用时创建实例 | 延迟加载,节省资源 | 非线程安全,多线程环境下可能创建多个实例 |
3 | 懒汉式(线程安全) | 在第一次调用时创建实例,并使用同步方法确保线程安全 | 延迟加载,线程安全 | 每次调用 getInstance 都需要同步,性能较差 |
4 | 双重检查锁定 | 在第一次调用时创建实例,并使用双重检查锁定机制确保线程安全 | 延迟加载,线程安全,且只在第一次创建实例时同步,性能较好 | 实现较复杂,需要注意 volatile 关键字的使用 |
5 | 静态内部类 | 利用静态内部类的特性,在第一次调用时创建实例 | 延迟加载,线程安全,实现简单 | 无法传递参数给单例实例 |
6 | 枚举 | 使用枚举类型实现单例 | 实现简单,线程安全,且能防止反射和序列化破坏单例 | 不能延迟加载,且不够灵活 |
饿汉模式
package com.cld.designpattern.creation.singleton.hungry;
import java.io.Serializable;
/**
* 饿汉式
* 是否 Lazy 初始化:否
* <p>
* 是否多线程安全:是
* <p>
* 实现难度:易
* <p>
* 描述:这种方式比较常用,但容易产生垃圾对象。
* 优点:没有加锁,执行效率会提高。
* 缺点:类加载时就初始化,浪费内存。
* 它基于 classloader 机制避免了多线程的同步问题,不过,instance 在类装载时就实例化,虽然导致类装载的原因有很多种,在单例模式中大多数都是调用 getInstance 方法, 但是也不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化 instance 显然没有达到 lazy loading 的效果。
*
* @author 休克柏
*/
public class HungrySingleton implements Serializable,Cloneable {
private static final HungrySingleton INSTANCE = new HungrySingleton();
private HungrySingleton() {
if (INSTANCE != null) {
throw new RuntimeException("单例构造器禁止反射调用!");
}
}
public static HungrySingleton getInstance() {
return INSTANCE;
}
@Override
public HungrySingleton clone() {
//避免克隆攻击
return getInstance();
}
}
懒汉式(非线程安全)
import java.io.ObjectStreamException;
import java.io.Serializable;
/**
* (线程不安全)懒汉式
* 是否 Lazy 初始化:是
*
* 是否多线程安全:否
*
* 实现难度:易
*
* 描述:这种方式是最基本的实现方式,这种实现最大的问题就是不支持多线程。因为没有加
* 锁synchronized,所以严格意义上它并不算单例模式。
* 这种方式 lazy loading 很明显,不要求线程安全,在多线程不能正常工作。
*
* @author 休克柏
*/
public class Lazy1Singleton implements Serializable {
private static Lazy1Singleton instance;
private Lazy1Singleton() {
}
/**
* 线程不安全
*
* @return Lazy1Singleton
*/
public static Lazy1Singleton getInstance() {
if (instance == null) {
instance = new Lazy1Singleton();
}
return instance;
}
/**
* 反序列化的时候,会调用该方法,从而避免反序列化对单例的破坏
* @return Lazy1Singleton
* @throws ObjectStreamException
*/
private Object readResolve() throws ObjectStreamException {
return getInstance();
}
}
懒汉模式(线程安全)
package org.cqcs.knowledge.designpattern.creation.singleton.lazy;
import java.io.Serializable;
/**
* (线程安全)懒汉式
* 是否 Lazy 初始化:是
* <p>
* 是否多线程安全:是
* <p>
* 实现难度:易
* <p>
* 描述:这种方式具备很好的 lazy loading,能够在多线程中很好的工作,但是,效率很低,99% 情况下不需要同步。
* 优点:第一次调用才初始化,避免内存浪费。
* 缺点:必须加锁 synchronized 才能保证单例,但加锁会影响效率。
* getInstance() 的性能对应用程序不是很关键(该方法使用不太频繁)。
*
* @author 休克柏
*/
public class Lazy2Singleton implements Serializable {
private static Lazy2Singleton instance;
private Lazy2Singleton() {
}
/**
* 线程安全,但是synchronized锁比较重
*
* @return Lazy2Singleton
*/
public static synchronized Lazy2Singleton getInstance() {
if (instance == null) {
instance = new Lazy2Singleton();
}
return instance;
}
}
双重检查锁定
/**
* JDK 版本:JDK1.5 起
*
* 是否 Lazy 初始化:是
*
* 是否多线程安全:是
*
* 实现难度:较复杂
*
* 描述:这种方式采用双锁机制,安全且在多线程情况下能保持高性能。
* getInstance() 的性能对应用程序很关键。
*
* Lazy2Singleton相对于Lazy1Singleton的效率问题,其实是为了解决1%几率的问题,
* 而使用了一个100%出现的防护盾。
* 那有一个优化的思路,就是把100%出现的防护盾,也改为1%的几率出现,使之只出现在可能会导致多个实例出现的地方。
*
* @author 休克柏
*/
public class DclSingleton implements Serializable {
/**
* volatile 的作用是对dclSingleton的写操作有一个内存屏障,这样,在它的赋值完成之前,就不用会调用读操作。
*
* volatile关键字通过提供“内存屏障”的方式来防止指令被重排序,为了实现volatile的内存语义,
* 编译器在生成字节码时,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序。
*
* 在java内存模型中,volatile 关键字作用可以是保证可见性或者禁止指令重排。
* 这里是因为 dclSingleton = new DclSingleton() ,它并非是一个原子操作,事实上:
* 在 JVM 中上述语句至少做了以下这 3 件事:
*
* 第一步是给 dclSingleton 分配内存空间;
*
* 第二步开始调用 DclSingleton 的构造函数等,来初始化 dclSingleton;
*
* 第三步,将 dclSingleton 对象指向分配的内存空间(执行完这步 dclSingleton 就不是 null 了)。
*
* 这里需要留意一下 1-2-3 的顺序,因为存在指令重排序的优化,也就是说第 2 步和第 3 步的顺序是
* 不能保证的,最终的执行顺序,可能是 1-2-3,也有可能是 1-3-2。
* 如果是 1-3-2,那么在第 3 步执行完以后,dclSingleton 就不是 null 了,可是这时第 2 步并没
* 有执行,singleton 对象未完成初始化,它的属性的值可能不是我们所预期的值。
* 假设此时线程 2 进入 getInstance 方法,由于 dclSingleton 已经不是 null 了,
*
* 所以会通过第一重检查并直接返回,但其实这时的 singleton 并没有完成初始化,所以使用这个实例的时候会报错。
*/
private volatile static DclSingleton dclSingleton;
private DclSingleton() {}
public static DclSingleton getDlcSingleton() {
if (dclSingleton == null) {
synchronized (DclSingleton.class) {
if (dclSingleton == null) {
//1. 给 dclSingleton 分配内存
//2. 调用 dclSingleton 的构造函数来初始化成员变量,形成实例
//3. 将dclSingleton对象指向分配的内存空间(执行完这步 singleton才是非 null了)
//上述3步是dclSingleton = new DclSingleton()的指令执行顺序,
dclSingleton = new DclSingleton();
}
}
}
return dclSingleton;
}
/**
* 反序列化的时候,会调用该方法,从而避免反序列化对单例的破坏
* @return Lazy1Singleton
* @throws ObjectStreamException
*/
private Object readResolve() throws ObjectStreamException {
return getDlcSingleton();
}
}
静态内部类
package org.cqcs.knowledge.designpattern.creation.singleton.staticinnerclass;
/**
*静态内部类的实现方式利用了 类加载机制 和 静态内部类的特性 来保证单例的线程安全和延迟加载。
*
* 延迟加载:
* 静态内部类不会在外部类加载时立即加载,而是在第一次调用 getInstance() 方法时才会加载内部类并创建实例。这种方式实现了延迟加载,避免了资源浪费。
*
* 线程安全:
* JVM 在加载类时是线程安全的,静态内部类在加载时会由 JVM 保证线程安全,因此不需要额外的同步机制。
*
* 静态内部类的特性:
* 静态内部类是独立于外部类的,只有在被引用时才会加载。
* 静态内部类的静态成员变量(单例实例)在类加载时初始化,且只会初始化一次。
* @author 休克柏
*/
public class StaticInnerClass {
private StaticInnerClass() {}
private static class SingletonHolder {
private static final StaticInnerClass INSTANCE = new StaticInnerClass();
}
public static StaticInnerClass getInstance() {
return SingletonHolder.INSTANCE;
}
}
攻击方式
序列化攻击
反射攻击
枚举(最佳实践)
There are three kinds of reference types: class types, array types, and interface types.
Their values are references to dynamically created class instances, arrays,
or class instances or arrays that implement interfaces, respectively.
一共有三种引用类型:class types, array types, and interface types. 其值指向动态创建的类实例,数组或者分别实现了接口的类实例或者数组。
从中我们可以得知enum不是一种专门的引用数据类型,它是类。
枚举是一种类
Day.java
public enum Day {
MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY;
}
- 编译Day.java生成Day.class
javac Day.java
- 反编译Day.class
javap -c Day.class
Compiled from "Day.java"
public final class demo.Day extends java.lang.Enum<demo.Day> {
public static final demo.Day MONDAY;
public static final demo.Day TUESDAY;
public static final demo.Day WEDNESDAY;
public static final demo.Day THURSDAY;
public static final demo.Day FRIDAY;
public static final demo.Day SATURDAY;
public static final demo.Day SUNDAY;
public static demo.Day[] values();
Code:
0: getstatic #1 // Field $VALUES:[Ldemo/Day;
3: invokevirtual #2 // Method "[Ldemo/Day;".clone:()Ljava/lang/Object;
6: checkcast #3 // class "[Ldemo/Day;"
9: areturn
……
……
……
从输出我们可以发现定义语句public final class demo.Day extends java.lang.Enum<demo.Day>知道我们的Day.java就是一个类,该类继承了java.lang.Enum.
/**
* This is the common base class of all Java language enumeration types.
*
* More information about enums, including descriptions of the
* implicitly declared methods synthesized by the compiler, can be
* found in section 8.9 of
* <cite>The Java™ Language Specification</cite>.
*
* <p> Note that when using an enumeration type as the type of a set
* or as the type of the keys in a map, specialized and efficient
* {@linkplain java.util.EnumSet set} and {@linkplain
* java.util.EnumMap map} implementations are available.
*
* @param <E> The enum type subclass
* @author Josh Bloch
* @author Neal Gafter
* @see Class#getEnumConstants()
* @see java.util.EnumSet
* @see java.util.EnumMap
* @since 1.5
*/
@SuppressWarnings("serial") // No serialVersionUID needed due to
// special-casing of enum types.
public abstract class Enum<E extends Enum<E>>
implements Comparable<E>, Serializable {