1. 定义
优点
缺点
特性:
(1)私有构造函数
(2)线程安全
(3)延迟加载
(4)序列化和反序列化
(5)反射攻击
2. 懒汉模式
多线程创建:
主函数直接调用
开启线程调试:类型设置为Thread
开始调试:
thread0
thread1
直接往下走,生成两个对象。
3. 双重检验锁
public class LazyDoubleCheckSingleton {
private volatile static LazyDoubleCheckSingleton lazySingleton = null;
private LazyDoubleCheckSingleton(){
}
public static LazyDoubleCheckSingleton getInstance(){
if(lazySingleton==null){//1.第一次检查,此处是为了减少性能开销,不用每次进入synchronized中
synchronized (LazyDoubleCheckSingleton.class) {
if(lazySingleton==null){//2.第二次检测
/*问题:原来的排序过程是:1、2、3,但是可能jvm指令重排序成为1、3、2,
* 此时可能两个线程都在执行2,执行3的时候这两个线程都new了LazyDoubleCheckSingleton对象
*
* 解决办法:volatile
* */
//1.分配内存给这个对象
//3.设置lazyDoubleCheckSingleton 指向刚分配的内存地址
//2.new一个LazyDoubleCheckSingleton对象
lazySingleton = new LazyDoubleCheckSingleton();
}
}
}
return lazySingleton;
}
}
4. 静态内部类
public class StaticInnerClassSingleton {
private static class InnerClass{
private static StaticInnerClassSingleton staticInnerClassSingleton = new StaticInnerClassSingleton();
}
public static StaticInnerClassSingleton getInstance(){
return InnerClass.staticInnerClassSingleton;
}
private StaticInnerClassSingleton(){
}
}
静态内部类在外部类被调用时不会立即被初始化,只有当其中的静态内部参数被调用时,才会被初始化。这样保证懒加载。
通过静态内部类的初始化staticInnerClassSingleton,jvm保证类的初始化只能有一个线程可以同时初始化同一个类,也就是获取初始化InnerClass静态类的锁只会被一个线程获取到,所以在InnerClass被初始化时,其中的静态资源会优先被初始化。
相比双重检验锁:毕竟volatile禁止了指令重排,所以双重检验锁的效率肯定略低一点。
5.懒汉模式
public class HungrySingleton implements Serializable {
private static HungrySingleton ourInstance = new HungrySingleton();
public static HungrySingleton getInstance() {
return ourInstance;
}
private HungrySingleton() {
}
}
特点:在类加载的时候完成对单例类的初始化。
6.序列化对单例的破坏
流程
(1)ObjectInputStream中readObject读取一个object
(2)ObjectInputStream中readObject0读取
(3)readObject0方法中发现是一个object调用readOrdinaryObject
case TC_OBJECT:
return checkResolve(readOrdinaryObject(unshared));
(4)readOrdinaryObject中有,表示只要是实现了序列化就new一个对象,否则返回null
obj = desc.isInstantiable() ? desc.newInstance() : null;
(5)调用了cons.newInstance()
@CallerSensitive
public T newInstance(Object ... initargs)
throws InstantiationException, IllegalAccessException,
IllegalArgumentException, InvocationTargetException
{
if (!override) {
if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
Class<?> caller = Reflection.getCallerClass();
checkAccess(caller, clazz, null, modifiers);
}
}
if ((clazz.getModifiers() & Modifier.ENUM) != 0)
throw new IllegalArgumentException("Cannot reflectively create enum objects");
ConstructorAccessor ca = constructorAccessor; // read volatile
if (ca == null) {
ca = acquireConstructorAccessor();
}
@SuppressWarnings("unchecked")
T inst = (T) ca.newInstance(initargs);
return inst;
}
(6)Reflection.getCallerClass()调用反射实现了new一个新的对象。
故前后不是同一个对象。
重写:
(1)在readOrdinaryObject中反射完成一个对象的创建以后会调用desc.invokeReadResolve(obj)
(2)desc.invokeReadResolve(obj)中return readResolveMethod.invoke(obj, (Object[]) null);
(3) readResolveMethod.invoke调用原方法的readResolve方法,于是可以通过重写readResolve实现返回原来的单例对象。
7. 反射攻击单例
反射修改私有构造器的权限来new一个新的单例对象。
防止反射攻击
懒汉式在多线程条件下无法防止反射
无法防止的原因:先利用构造器new了一个单例对象,然后getInstance()方法new了一个对象。
8. 枚举完成单例
枚举类型
反序列化无法攻击枚举
在ObjectInputStream中readEnum方法去读取数据时,会从常量池中去寻找这些数据。所以枚举中的data无论何时被获取都是相等。
反射攻击枚举:报错,获取构造器失败
查看源码发现,枚举没有空构造器,必须要带上参数
传入参数后重试
查看源码:发现构造器无法获取枚举对象
继续使用jad EnumInstance.class生成EnumInstance.jad,里面有如下代码:
public final class EnumInstance extends Enum // final类
{
public static EnumInstance[] values()
{
return (EnumInstance[])$VALUES.clone();
}
public static EnumInstance valueOf(String name)
{
return (EnumInstance)Enum.valueOf(com/qianliu/creational/songleton/EnumInstance, name);
}
private EnumInstance(String s, int i)//私有构造器
{
super(s, i);
}
public static EnumInstance getInstance()
{
return INSTANCE;
}
public String getData()
{
return data;
}
public void setData(String data)
{
this.data = data;
}
public static final EnumInstance INSTANCE;
String data;
private static final EnumInstance $VALUES[];
static
{
INSTANCE = new EnumInstance("INSTANCE", 0);//在静态模块初始化单例对象
$VALUES = (new EnumInstance[] {
INSTANCE
});
}
}
Enum类似于饿汉模式声明对象,类加载时完成初始化,再加上反射和io相关的类保证枚举类型的单例。
使用枚举类内部的方法:枚举类内部的方法必须显式声明。
9. 容器实现单例
如果我们使用的过程中单例对象特别多,可以用这种方式实现单例
10. ThreadLocal实现特殊的单例
ThreadLocal可以保证每个线程内部的对象是单例的,不同线程直接按互不干扰
源码:https://github.com/LUK-qianliu/design_pattern_in_all