这可能是最全的单例模式了

设计模式之——单例模式

    • 单例的几种实现
        • 1. 懒汉单例模式
        • 2. synchronized 修饰的懒汉单例模式
        • 3. 双重检查锁定的单例模式
        • 4. 静态内部类实现单例模式
        • 5. 饿汉实现单例模式
        • 6. 饿汉变种实现单例模式
        • 7. 枚举实现单例模式
    • static修饰下是怎么做到线程安全的?
    • 完全不使用synchronized实现单例模式
        • 1. CAS(AtomicReference)实现单例模式
        • 2. ThreadLocal实现单例模式
    • 如何破坏单例
    • 如何防止单例被破坏
    • 参考文章

单例模式有以下特点:
  1、单例类只能有一个实例。
  2、单例类必须自己创建自己的唯一实例。
  3、单例类必须给所有其他对象提供这一实例。

单例的几种实现

1. 懒汉单例模式

线程不安全的单例模式。

public class Singleton {  
    private Singleton() {}  
    private static Singleton single=null;  
    public static Singleton getInstance() {  
         if (single == null) {    
             single = new Singleton();  
         }    
        return single;  
    }  
}

Singleton通过将构造方法限定为private避免了其他类通过访问构造器进行实例化,在同一个虚拟机范围内,Singleton的唯一实例只能通过静态的getInstance()方法进行访问。但在并发的情况下是可能出现这种情况,就是a线程先进入getInstance()方法在创建实例化的时候,也就是还没创建成功,b线程也进入了getInstance()方法,这个时候a线程实例还没建成功,b线程判断single为空也开始创建实例,导致会出现创建出两个实例来。

2. synchronized 修饰的懒汉单例模式

但并发的时候也只能一个一个排队进行getInstance()方法访问。

public static synchronized Singleton getInstance() {  
    if (single == null) {    
        single = new Singleton();  
    }    
    return single;  
} 

3. 双重检查锁定的单例模式

在并发量高的情况下,不需要排队访问getInstance()方法,性能上会优于synchronized 修饰的懒汉单例模式。

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

4. 静态内部类实现单例模式

静态内部类实现单例模式,这种方式优于上的方式,它即实现了线程安全,又省去了null的判断

public class Singleton {  
    private static class SingletonHolder {  
    private static final Singleton INSTANCE = new Singleton();  
    }  
    private Singleton (){}  
    public static final Singleton getInstance() {  
    return SingletonHolder.INSTANCE;  
    }  
}

5. 饿汉实现单例模式

public class Singleton {  
    private static Singleton instance = new Singleton();  
    private Singleton (){}  
    public static Singleton getInstance() {  
    return instance;  
    }  
}

6. 饿汉变种实现单例模式

public class Singleton {  
    private Singleton instance = null;  
    static {  
    instance = new Singleton();  
    }  
    private Singleton (){}  
    public static Singleton getInstance() {  
    return this.instance;  
    }  
}

7. 枚举实现单例模式

public enum EnumSingleton {  
    INSTANCE;  
    public void whateverMethod() {  
    }  
}

枚举其实底层是依赖Enum类实现的,这个类的成员变量都是static类型的,并且在静态代码块中实例化的,和饿汉单例模式有点像。

static修饰下是怎么做到线程安全的?

因为以上几种虽然没有直接使用synchronized,但是也是间接用到了。类加载过程的线程安全性保证,以上的静态内部类、饿汉等模式均是通过定义静态的成员变量,以保证单例对象可以在类初始化的过程中被实例化。其利用了ClassLoader的线程安全机制。ClassLoader的loadClass方法在加载类的时候使用了synchronized关键字。所以, 除非被重写,这个方法默认在整个装载过程中都是线程安全的。所以在类加载过程中对象的创建也是线程安全的。

完全不使用synchronized实现单例模式

1. CAS(AtomicReference)实现单例模式

public class Singleton {
    private static final AtomicReference<Singleton> INSTANCE = new AtomicReference<Singleton>();

    private Singleton() {}

    public static Singleton getInstance() {
        for (;;) {
            Singleton singleton = INSTANCE.get();
            if (null != singleton) {
                return singleton;
            }

            singleton = new Singleton();
            if (INSTANCE.compareAndSet(null, singleton)) {
                return singleton;
            }
        }
    }
}

CAS实现单例模式优缺点
CAS是一种基于忙等待的算法,依赖底层硬件的实现,相对于锁它没有线程切换和阻塞的额外消耗,可以支持较大的并行度。但CAS的缺点在于如果忙等待一直执行不成功,一直在 for (;;)死循环中,会对CPU造成较大的执行开销。另外,代码中,如果N个线程同时执行到 singleton = new Singleton();的时候,会有大量对象被创建,也就违背了单例原则,可能导致内存溢出。

2. ThreadLocal实现单例模式

public class Singleton {
     private static final ThreadLocal<Singleton> singleton =
     new ThreadLocal<Singleton>() {
         @Override
         protected Singleton initialValue() {
            return new Singleton();
         }
     };
     public static Singleton getInstance() {
        return singleton.get();
     }
     
     private Singleton() {}
}

如何破坏单例

  1. 反射
    通过反射获取单例对象的构造器,暴力破解后即可创建多个不同实例。怎么防止:私有构造方法加双重检查锁。
  2. 序列化
    通过深克隆复制对象,可生成多个实例。怎么防止:重写在单例对象中readObject()方法。

如何防止单例被破坏

枚举的单例模式。枚举在序列化的时候Java仅仅是将枚举对象的name属性输出到结果中,反序列化的时候则是通过java.lang.Enum的valueOf方法来根据名字查找枚举对象。同时,编译器是不允许任何对这种序列化机制的定制的,因此禁用了writeObject、readObject等方法。
而使用反射时,EnumSingleton.class.getDeclaredConstructors()获取所有构造器,然而并没有设置无参构造器,而且在反射在通过newInstance创建对象时,会检查该类是否是ENUM,如果是则抛出异常,反射失败,所以枚举是不怕反射攻击的。

// newInstance源码
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;
        if (ca == null) {
            ca = acquireConstructorAccessor();
        }
        @SuppressWarnings("unchecked")
        T inst = (T) ca.newInstance(initargs);
        return inst;
    }

参考文章

面试官真是搞笑!让实现线程安全的单例,又不让使用synchronized!

你可能感兴趣的:(设计模式)