单例模式:(Singleton Pattern)是指确保一个类在任何情况下都绝对只有一个实例,并提供一个全局访问点。单例模式是创建型模式。单例模式在现实生活中应用也非常广泛。 在 J2EE 标准中,ServletContext、 ServletContextConfig 等;在 Spring 框架应用中 ApplicationContext;数据库的连接 池也都是单例形式。

一、饿汉模式

饿汉模式,是在类加载的时候立即初始化,并实例对象,线程绝对按全,在线程还没出现前就已经实例化,不存在访问安全问题

  优点:没有加任何的锁、执行效率比较高,在用户体验上来说,比懒汉式更好。

  缺点:类加载的时候就初始化,不管用与不用都占着空间,浪费了内存。  

Spring 中 IOC 容器 ApplicationContext 本身就是典型的饿汉式单例。

  下面我们来看饿汉模式的两种基本写法

/**
 * 第一种写法
 * @Description 单例模式:全局只能有一个实例,构造方法私有化,对外提供一个全局访问点
 *      这是一个简单的饿汉式 单例模式,类加载的时候,就会初始化,并创建已经静态常量实例。
 *      绝对线程安全,在线程还没出现以前就是实例化了,不可能存在访问安全问题
 *      优点:没有加任何的锁、执行效率比较高,
 *      缺点:如果有很多个单例,都是饿汉模式,类加载的时候就初始化很影响性能,不管用不用,都占着空间,浪费了内存
 * @Author Bert
 * @Date 2019\5\25 0025
 */
public class HungerSingleton {
    
    private static final HungerSingleton SINGLETON = new HungerSingleton();

    //构造方法私有化
    private HungerSingleton() {}

    //对外提供一个全局访问点
    public static HungerSingleton getInstance(){
        return SINGLETON;
    }
}

/**
 * 第二种写法
 * @Description 饿汉模式 静态类加载块写法
 *     在静态代码块里面创建一个静态常量实例
 * @Author Bert
 * @Date 2019\5\25 0025
 */
public class HungerStaticSingleton {

    private static final HungerStaticSingleton SINGLETON;

    //静态代码块,类加载的时候初始化
    static {
        SINGLETON = new HungerStaticSingleton();
    }

    //私有的构造方法
    private HungerStaticSingleton(){}

    //全局访问点
    public static HungerStaticSingleton getInstance(){
        return SINGLETON ;
    }
}

/**
 * @Description 单例模式 多线程测试类
 * @Author Bert
 * @Date 2019\5\25
 */
public class HungerSingletonThread extends Thread{
  @Override
  public void run() {
      HungerSingleton singleton = HungerSingleton.getInstance();
      System.out.println("HungerSingleton:"+Thread.currentThread().getName()+":"+singleton);

      HungerStaticSingleton singleton1 = HungerStaticSingleton.getInstance();
      System.out.println("HungerStaticSingleton"+Thread.currentThread().getName()+":"+singleton1);
  }
} 

/** 
* @Description 饿汉模式测试类 
* @Date 2019\5\25
*/ 
public class HungerSingletonTest { 

  public static void main(String[] args) {
    //饿汉 单例模式 测试
    new Thread(new HungerSingletonThread()).start();
    new Thread(new HungerSingletonThread()).start();
    new Thread(new HungerSingletonThread()).start();
    new Thread(new HungerSingletonThread()).start();
    new Thread(new HungerSingletonThread()).start();
    new Thread(new HungerSingletonThread()).start();
  } 
}

  运行结果如下

HungerSingleton:Thread-3:com.bert.singleton.hunger.HungerSingleton@54477291
HungerStaticSingletonThread-3:com.bert.singleton.hunger.HungerStaticSingleton@5a808669
HungerSingleton:Thread-1:com.bert.singleton.hunger.HungerSingleton@54477291
HungerStaticSingletonThread-1:com.bert.singleton.hunger.HungerStaticSingleton@5a808669
HungerSingleton:Thread-9:com.bert.singleton.hunger.HungerSingleton@54477291
HungerStaticSingletonThread-9:com.bert.singleton.hunger.HungerStaticSingleton@5a808669
HungerSingleton:Thread-7:com.bert.singleton.hunger.HungerSingleton@54477291
HungerStaticSingletonThread-7:com.bert.singleton.hunger.HungerStaticSingleton@5a808669
HungerSingleton:Thread-5:com.bert.singleton.hunger.HungerSingleton@54477291
HungerStaticSingletonThread-5:com.bert.singleton.hunger.HungerStaticSingleton@5a808669
HungerSingleton:Thread-11:com.bert.singleton.hunger.HungerSingleton@54477291
HungerStaticSingletonThread-11:com.bert.singleton.hunger.HungerStaticSingleton@5a808669

  这两种写法都非常的简单,也非常好理解,饿汉式适用在单例对象较少的情况。

二、懒汉模式

懒汉模式:被外部类调用的时候内部类才会加载,线程不安全。

1、简单的懒汉模式:

/**
 * @Description 简单的懒汉式 单例  私有化的构造方法 对外提供一个全局访问点, 调用的时候初始化
 *          线程不安全,可以用idea 在线程模式下deg,当两个以上线程同时进入就会各自创建实例
 * @Author Bert
 * @Date 2019\5\25 0025
 */
public class LazySimpleSingleton {

    private static LazySimpleSingleton singleton = null;

    //私有化的构造方法
    private LazySimpleSingleton(){}

    //对外提供一个全局访问点
    public static LazySimpleSingleton getInstance(){
        if(null == singleton)
            singleton = new LazySimpleSingleton();
        return singleton;
    }
}


/**
 * @Description 懒汉 单例模式 多线程测试类
 * @Author Bert
 * @Date 2019\5\25 0025
 */
public class LazySingletonThread extends Thread{

    @Override
    public void run() {
        LazySimpleSingleton singleton = LazySimpleSingleton.getInstance();
        System.out.println(Thread.currentThread().getName()+":"+singleton);
    }
}

/**
 * @Description 懒汉模式测试类
 * @Author Bert
 * @Date 2019\5\25 0025
 */
public class LazyleSingletonTest {

    public static void main(String[] args) {

        new Thread(new LazySingletonThread()).start();

        new Thread(new LazySingletonThread()).start();
    }
}
用idea在多线程情况下deg让两个线程同时进入到getInsstance()方法中就活出现以下结果:
Thread-3:com.bert.singleton.lazy.LazySimpleSingleton@d9362c6
Thread-1:com.bert.singleton.lazy.LazySimpleSingleton@7a9f5b3f

  这就足以证明线程不安全,下面我们再看另一种优化写法,在getInstance()方法上面加synchronized关键字

/**
 * @Description 简单的懒汉式 单例  私有化的构造方法 对外提供一个全局访问点, 调用的时候初始化
 *          线程不安全,可以用idea 在线程模式下deg,当两个以上线程同时进入就会各自创建实例
 * @Author Bert
 * @Date 2019\5\25 0025
 */
public class LazySimpleSingleton {

    private static LazySimpleSingleton singleton = null;

    //私有化的构造方法
    private LazySimpleSingleton(){}

    //对外提供一个全局访问点, 在该方法上面加上synchronized 关键字,使其变成线程同步方法
    public static synchronized LazySimpleSingleton getInstance(){
        if(null == singleton)
            singleton = new LazySimpleSingleton();
        return singleton;
    }
}

  再看运行结果:

Thread-1:com.bert.singleton.lazy.LazySimpleSingleton@3d437d89
Thread-3:com.bert.singleton.lazy.LazySimpleSingleton@3d437d89

但是当我们将其中一个线程执行并调用 getInstance()方法时,另一 个线程在调用 getInstance()方法,线程的状态由 RUNNING 变成了 MONITOR,出现阻塞。直到第一个线程执行完,第二个线程才恢复 RUNNING 状态继续调用 getInstance(),完美的展现了 synchronized 监视锁的运行状态,线程安全的问题便解决了。但是,用 synchronized 加锁,在线程数量比较多情况下,如果 CPU 分配压力上升,会导致大批 量线程出现阻塞,从而导致程序运行性能大幅下降。接下来就看下一种,既兼顾线程安全又提升程序性能的懒汉模式

  2.双重检查锁的单例模式

/**
 * @Description 简单的懒汉式 单例  私有化的构造方法 对外提供一个全局访问点, 调用的时候初始化
 *          线程不安全,可以用idea 在线程模式下deg,当两个以上线程同时进入就会各自创建实例
 * @Author Bert
 * @Date 2019\5\25 0025
 */
public class LazySimpleSingleton {

    private static LazySimpleSingleton singleton = null;

    //私有化的构造方法
    private LazySimpleSingleton(){}

    //对外提供一个全局访问点, 既保证性能,也保证线程安全的双重检查锁
    public static LazySimpleSingleton getInstance(){
        if(null == singleton)
            synchronized(LazySimpleSingleton.class){
                if(null == singleton)
                singleton = new LazySimpleSingleton();
            }
        return singleton;
    }
}

这种线程也是安全的,但是,用到 synchronized 关键字,总归是要上锁,对程序性能还是存在一定影响的。下面请看静态内部类方式

3、静态内部类的懒汉模式

/**
 * @Description 静态内部类 兼容饿汉模式的 懒汉模式
 *       默认使用 LazyStaticSingleton 的时候,会先初始化内部类
 *       如果没使用的话,内部类是不加载的
 * @Author Bert
 * @Date 2019\5\25 0025
 */
public class LazyStaticSingleton {

    //私有构造
    private LazyStaticSingleton(){}

    //对外访问点,static 为了是单例的空间共享, final 保证这个方法不会被重写,重载
    public static final LazyStaticSingleton getInstance(){
        //在返回结果以前,一定会先加载内部类
        return lazySingleton.LAZY;
    }

    //静态内部类 ,默认不加载
    private static class lazySingleton{
        private static final LazyStaticSingleton LAZY = new LazyStaticSingleton();
    }
}

 这种形式兼顾饿汉式的内存浪费,也兼顾 synchronized 性能问题。内部类一定是要在方 法调用之前初始化,巧妙地避免了线程安全问题。

三、防止反射破坏单例

  前面单例模式的构造方法除了加上 private 以外,没有做任何处 理。如果我们使用反射来调用其构造方法,然后,再调用 getInstance()方法,应该就会 两个不同的实例。

  下面我们通过反射调用单例模式,以LazyStaticSingleton为例

/**
 * @Description 通过反射机制强行调用单例
 * @Author Bert
 * @Date 2019\5\25 0025
 */
public class LazyleSingletonTest {

    public static void main(String[] args) {
        try {
            //
            Class<?> clazz = LazyStaticSingleton.class;
            //通过反射 获取私有的构造方法
            Constructor c = clazz.getDeclaredConstructor(null);
            //强制访问
            c.setAccessible(true);
            //暴力初始化
            Object o1 = c.newInstance();
            //调用两次构造方法,相当于new 两次,犯了原则性错误。
            Object o2 = c.newInstance();
            System.out.println(o1);
            System.out.println(o2);
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

  运行结果如下:

com.bert.singleton.lazy.LazyStaticSingleton@4b67cf4d
com.bert.singleton.lazy.LazyStaticSingleton@7ea987ac

由此可见,我们应该再多进一步处理,在构造方法中做一些限制,一旦出现多次创建,就抛出一个异常

/**
 * @Description 静态内部类 兼容饿汉模式的 懒汉模式
 *       默认使用 LazyStaticSingleton 的时候,会先初始化内部类
 *       如果没使用的话,内部类是不加载的
 * @Author Bert
 * @Date 2019\5\25 0025
 */
public class LazyStaticSingleton {

    //私有构造
    private LazyStaticSingleton(){
        if(lazySingleton.LAZY != null)
            throw new RuntimeException("warning:Unauthorized access!");
    }

    //对外访问点,static 为了是单例的空间共享, final 保证这个方法不会被重写,重载
    public static final LazyStaticSingleton getInstance(){
        //在返回结果以前,一定会先加载内部类
        return lazySingleton.LAZY;
    }

    //静态内部类 ,默认不加载
    private static class lazySingleton{
        private static final LazyStaticSingleton LAZY = new LazyStaticSingleton();
    }
}

  运行结果如下:

java.lang.reflect.InvocationTargetException
	at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
	at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
	at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
	at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
	at com.bert.singleton.LazyleSingletonTest.main(LazyleSingletonTest.java:24)
Caused by: java.lang.RuntimeException: warning:Unauthorized access!
	at com.bert.singleton.lazy.LazyStaticSingleton.<init>(LazyStaticSingleton.java:15)
	... 5 more

在构造方法里面加判断后,再通过反射强制创建就出抛出我们刚设定的异常。

四、防止序列化破坏单例

当我们将一个单例对象创建好,有时候需要将对象序列化然后写入到磁盘,下次使用时 再从磁盘中读取到对象,反序列化转化为内存对象。反序列化后的对象会重新分配内存, 即重新创建。那如果序列化的目标的对象为单例对象,就违背了单例模式的初衷,相当于破坏了单例。

   我们以 一个简单的 饿汉模式为例

/**
 * @Description 序列 单例模式
 * @Author Bert
 * @Date 2019\5\25 0025
 */
public class SeriableSingleton implements Serializable {

    private final static SeriableSingleton INSTANCE = new SeriableSingleton();

    private SeriableSingleton(){
        if (INSTANCE != null)
            throw new RuntimeException("警告:非法操作!");
    }

    public static SeriableSingleton getInstance() {
        return INSTANCE;
    }
}

/**
 * @Description 反序列 单例模式 测试
 * @Date 2019\5\25 0025
 */
public class SeriableSingletonTest {

    public static void main(String[] args) {
        SeriableSingleton s1 = null;//通过反序列化获取
        SeriableSingleton s2 = SeriableSingleton.getInstance();

        FileOutputStream fos = null;
        try {
            //序列化
            //序列化就是说把内存中的状态通过转换成字节码的形式
            //从而转换一个 IO 流,写入到其他地方(可以是磁盘、网络 IO)
            //内存中状态给永久保存下来了
            fos = new FileOutputStream("SeriableSingleton.obj");
            ObjectOutputStream oos = new ObjectOutputStream(fos);
            oos.writeObject(s2);

            //反序列化
            //讲已经持久化的字节码内容,转换为 IO 流
            //通过 IO 流的读取,进而将读取的内容转换为 Java 对象
            //在转换过程中会重新创建对象 new
            FileInputStream fis = new FileInputStream("SeriableSingleton.obj");
            ObjectInputStream ois = new ObjectInputStream(fis);
            Object o = ois.readObject();

            System.out.println(s2);
            System.out.println(o);

        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

运行结果:

com.bert.singleton.seriable.SeriableSingleton@19dfb72a
com.bert.singleton.seriable.SeriableSingleton@2a5ca609

运行结果中,可以看出,反序列化后的对象和手动创建的对象是不一致的,实例化了两次,违背了单例的设计初衷。

  优化解决方案,添加readResolve()方法

/**
 * @Description 序列 单例模式
 * @Author Bert
 * @Date 2019\5\25 0025
 */
public class SeriableSingleton implements Serializable {

    private final static SeriableSingleton INSTANCE = new SeriableSingleton();

    private SeriableSingleton(){
        if (INSTANCE != null)
            throw new RuntimeException("警告:非法操作!");
    }

    public static SeriableSingleton getInstance() {
        return INSTANCE;
    }

    //防止反序列化 破坏单例,
    private Object readResolve(){
        return INSTANCE;
    }
}

运行结果:

com.bert.singleton.seriable.SeriableSingleton@6e0be858
com.bert.singleton.seriable.SeriableSingleton@6e0be858

这样已经解决这个问题了,为什么要这么写呢,其实在JDK源码中有详细说明,反序列化调用readObject() 的方法里面,调用了readObject0()返回的Object,而这个readObject0()就对有没有resolve方法做了check,如果有就,就判读是否构造方法空。

  我 们 先进 入 ObjectInputStream 类的 readObject()方法,代码如下

public final Object readObject()
        throws IOException, ClassNotFoundException
    {
        if (enableOverride) {
            return readObjectOverride();
        }

        // if nested read, passHandle contains handle of enclosing object
        int outerHandle = passHandle;
        try {
            Object obj = readObject0(false);
            handles.markDependency(outerHandle, passHandle);
            ClassNotFoundException ex = handles.lookupException(passHandle);
            if (ex != null) {
                throw ex;
            }
            if (depth == 0) {
                vlist.doCallbacks();
            }
            return obj;
        } finally {
            passHandle = outerHandle;
            if (closed && depth == 0) {
                clear();
            }
        }
    }

  我们发现在readObject中又调用了我们重写的readObject0()方法。进入readObject0() 方法,代码如下:

private Object readObject0(boolean unshared) throws IOException {
        
		...
		case TC_OBJECT:
			return checkResolve(readOrdinaryObject(unshared));
		...
        
}

  我们看到 TC_OBJECTD 中判断,调用了 ObjectInputStream 的 readOrdinaryObject() 方法,我们继续进入看源码:

private Object readOrdinaryObject(boolean unshared)
        throws IOException
    {
        if (bin.readByte() != TC_OBJECT) {
            throw new InternalError();
        }

        ObjectStreamClass desc = readClassDesc(false);
        desc.checkDeserialize();

        Class<?> cl = desc.forClass();
        if (cl == String.class || cl == Class.class
                || cl == ObjectStreamClass.class) {
            throw new InvalidClassException("invalid class descriptor");
        }

        Object obj;
        try {
            obj = desc.isInstantiable() ? desc.newInstance() : null;
        } catch (Exception ex) {
            throw (IOException) new InvalidClassException(
                desc.forClass().getName(),
                "unable to create instance").initCause(ex);
        }
        ...
        return obj;
	}

 发现调用了 ObjectStreamClass 的 isInstantiable()方法,而 isInstantiable()里面的代码 如下:

boolean isInstantiable() {
        requireInitialized();
        return (cons != null);
    }

这段代码就是判断一下构造方法是否为空,构造方法不为空就返回 true。这时候,其实还没有找到为什么加上 readResolve()方法就避免了单例被破坏的真正原 因。我再回到 ObjectInputStream 的 readOrdinaryObject()方法继续往下看

private Object readOrdinaryObject(boolean unshared)
        throws IOException
    {
        if (bin.readByte() != TC_OBJECT) {
            throw new InternalError();
        }

        ObjectStreamClass desc = readClassDesc(false);
        desc.checkDeserialize();

        Class<?> cl = desc.forClass();
        if (cl == String.class || cl == Class.class
                || cl == ObjectStreamClass.class) {
            throw new InvalidClassException("invalid class descriptor");
        }

        Object obj;
        try {
            obj = desc.isInstantiable() ? desc.newInstance() : null;
        } catch (Exception ex) {
            throw (IOException) new InvalidClassException(
                desc.forClass().getName(),
                "unable to create instance").initCause(ex);
        }
		...
        if (obj != null &&
            handles.lookupException(passHandle) == null &&
            desc.hasReadResolveMethod())
        {
            Object rep = desc.invokeReadResolve(obj);
            if (unshared && rep.getClass().isArray()) {
                rep = cloneArray(rep);
            }
            if (rep != obj) {
                // Filter the replacement object
                if (rep != null) {
                    if (rep.getClass().isArray()) {
                        filterCheck(rep.getClass(), Array.getLength(rep));
                    } else {
                        filterCheck(rep.getClass(), -1);
                    }
                }
                handles.setObject(passHandle, obj = rep);
            }
        }

        return obj;
    }

判断无参构造方法是否存在之后,又调用了 hasReadResolveMethod()方法

boolean hasReadResolveMethod() {
        requireInitialized();
        return (readResolveMethod != null);
    }

这就是判断 readResolveMethod 是否为空,不为空就返回 true。那么 readResolveMethod 是在哪里赋值的呢?通过全局查找找到了赋值代码在私有方法 ObjectStreamClass()方法中给 readResolveMethod 进行赋值,来看代码:

readResolveMethod = getInheritableMethod(
      cl, "readResolve", null, Object.class);

上面的逻辑其实就是通过反射找到一个无参的 readResolve()方法,并且保存下来。现在 再 回 到 ObjectInputStream 的 readOrdinaryObject() 方 法 继 续 往 下 看 , 如 果 readResolve()存在则调用 invokeReadResolve()方法,来看代码:

Object invokeReadResolve(Object obj)
        throws IOException, UnsupportedOperationException
    {
        requireInitialized();
        if (readResolveMethod != null) {
            try {
                return readResolveMethod.invoke(obj, (Object[]) null);
            } catch (InvocationTargetException ex) {
                Throwable th = ex.getTargetException();
                if (th instanceof ObjectStreamException) {
                    throw (ObjectStreamException) th;
                } else {
                    throwMiscException(th);
                    throw new InternalError(th);  // never reached
                }
            } catch (IllegalAccessException ex) {
                // should not occur, as access checks have been suppressed
                throw new InternalError(ex);
            }
        } else {
            throw new UnsupportedOperationException();
        }
    }

我们可以看到在 invokeReadResolve()方法中用反射调用了 readResolveMethod 方法。 通过 JDK 源码分析我们可以看出,虽然,增加 readResolve()方法返回实例,解决了单 例被破坏的问题。但是,我们通过分析源码以及调试,我们可以看到实际上实例化了两 次,只不过新创建的对象没有被返回而已。那如果,创建对象的动作发生频率增大,就意味着内存分配开销也就随之增大。

五、注册式单例

  注册式单例又称为登记式单例,就是将每一个实例都登记到某一个地方,使用唯一的标 识获取实例。注册式单例有两种写法:一种为容器缓存,一种为枚举登记。

  1、枚举式

/**
 * @Description 枚举式 (注册式单例)
 * @Author Bert
 * @Date 2019\5\25 0025
 */
public enum EnumSingleton {

    INSTANCE;

    //赋值的数据
    private Object object;

    public Object getObject() {
        return object;
    }

    public void setObject(Object object) {
        this.object = object;
    }

    public static EnumSingleton getInstance(){
        return INSTANCE;
    }
}


/**
 * @Description 枚举式单例 反序列化 测试
 * @Date 2019\5\25 0025
 */
public class EnumSingletonTest {

    public static void main(String[] args) {

        EnumSingleton instance1 = null;
        EnumSingleton instance2 = EnumSingleton.getInstance();
        instance2.setObject(new Object());

        try {
            //序列化
            FileOutputStream fos = new FileOutputStream("EnumSingletonTest.obj");
            ObjectOutputStream oos = new ObjectOutputStream(fos);
            oos.writeObject(instance2);
            oos.flush();
            oos.close();

            //反序列化
            FileInputStream fis = new FileInputStream("EnumSingletonTest.obj");
            ObjectInputStream ois = new ObjectInputStream(fis);
            instance1 = (EnumSingleton)ois.readObject();
            ois.close();
            
            System.out.println(instance1.getObject());
            System.out.println(instance2.getObject());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

运行结果如下:

java.lang.Object@3feba861
java.lang.Object@3feba861

接下来我就开始解密为什么枚举式的单例可以如此神奇,这需要从源码入手,先下载一个java反编译工具jad(下载地址:https://varaneckas.com/jad/),下载后解压就可以使用命令行调用了,先找到项目中编译后的EnumSingleton.class文件

android 单例 饿汉 java单例模式饿汉式_android 单例 饿汉

将该文件复制到刚才解压的jad.exe 所在目录,当前目录打开cmd命令窗口,输入 jad EnumSingleton.class ,该目录下就会生成一个EnumSingleton.jad文件

android 单例 饿汉 java单例模式饿汉式_构造方法_02

打开EnumSingleton.jad文件,你会惊奇地发现里面有一个static方法

android 单例 饿汉 java单例模式饿汉式_内部类_03

因此我们得知,枚举式单例在静态代码块中就给 INSTANCE 进行了赋值,是饿汉式单例的实现。序列化能不能破坏枚举式单例其实在JDK源码中也有体现,我们继续回到ObjectInputStream 的 readObject0()方法中

private Object readObject0(boolean unshared) throws IOException {
    ...
    case TC_ENUM:
         return checkResolve(readEnum(unshared));
    ...
}

发现readObject0()中调用了 readEnum()方法,我们继续查看readEnum()方法

private Enum<?> readEnum(boolean unshared) throws IOException {
        if (bin.readByte() != TC_ENUM) {
            throw new InternalError();
        }

        ObjectStreamClass desc = readClassDesc(false);
        if (!desc.isEnum()) {
            throw new InvalidClassException("non-enum class: " + desc);
        }

        int enumHandle = handles.assign(unshared ? unsharedMarker : null);
        ClassNotFoundException resolveEx = desc.getResolveException();
        if (resolveEx != null) {
            handles.markException(enumHandle, resolveEx);
        }

        String name = readString(false);
        Enum<?> result = null;
        Class<?> cl = desc.forClass();
        if (cl != null) {
            try {
                @SuppressWarnings("unchecked")
                Enum<?> en = Enum.valueOf((Class)cl, name);
                result = en;
            } catch (IllegalArgumentException ex) {
                throw (IOException) new InvalidObjectException(
                    "enum constant " + name + " does not exist in " +
                    cl).initCause(ex);
            }
            if (!unshared) {
                handles.setObject(enumHandle, result);
            }
        }

        handles.finish(enumHandle);
        passHandle = enumHandle;
        return result;
    }

 从readEnum()方法中,我们得知枚举类是通过类名和class对象找到一个唯一的枚举对象,因此,枚举对 象不可能被类加载器加载多次。

接下来,我们在测试一下枚举类单例会不会被发射破坏。

public class EnumSingletonTest {

    public static void main(String[] args) {
        newInstanceTest();
    }

    //反射测试
    public static void newInstanceTest() {
        try {
            Class clazz = EnumSingleton.class;
            Constructor constructor = clazz.getDeclaredConstructor();
            EnumSingleton singleton = (EnumSingleton) constructor.newInstance();
            System.out.println(singleton);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

运行结果:

java.lang.NoSuchMethodException: com.bert.singleton.register.EnumSingleton.<init>()
	at java.lang.Class.getConstructor0(Class.java:3082)
	at java.lang.Class.getDeclaredConstructor(Class.java:2178)
	at com.bert.singleton.register.EnumSingletonTest.newInstanceTest(EnumSingletonTest.java:22)
	at com.bert.singleton.register.EnumSingletonTest.main(EnumSingletonTest.java:15)

  报的是java.lang.NoSuchMethodException 异常,意思是没找到无参的构造方法。我们打开java.lang.Enum的源码,发现只有一个protected的构造方法

protected Enum(String name, int ordinal) {
        this.name = name;
        this.ordinal = ordinal;
    }

 那我们就传入两个参数,再试一次

public class EnumSingletonTest {

    public static void main(String[] args) {
        newInstanceTest();
    }

    //反射测试
    public static void newInstanceTest() {
        try {
            Class clazz = EnumSingleton.class;
            Constructor constructor = clazz.getDeclaredConstructor(String.class, int.class);
            constructor.setAccessible(true);//强制访问
            EnumSingleton singleton = (EnumSingleton) constructor.newInstance("CHINA", 666);
            System.out.println(singleton);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

  运行结果:

java.lang.IllegalArgumentException: Cannot reflectively create enum objects
	at java.lang.reflect.Constructor.newInstance(Constructor.java:417)
	at com.bert.singleton.register.EnumSingletonTest.newInstanceTest(EnumSingletonTest.java:24)
	at com.bert.singleton.register.EnumSingletonTest.main(EnumSingletonTest.java:15)

报错Cannot reflectively create enum objects,意思是不能通过反射来创建枚举类,关于这个在JDK源码中也有说明,我们来看Constructor 的 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;
    }

 从newInstance()方法中我们可以看出,代码中做出来强制判断,如果修饰词是ENUM,直接抛出异常,因此,我们可以肯定枚举式单例模式不可能被反射破坏。由于JDK 枚举的语法特殊性,加上反射也为枚举类保驾护航,让枚举式单例更受程序员的追捧。

、容器式

**
 * @Description 容器式单例模式
 * @Author Bert
 * @Date 2019\5\29 0029
 */
public class ContainerSingleton {
    //私有的构造方法
    private ContainerSingleton(){}

    //存储实例的map,ConcurrentHashMap中线程安全,spring框架的IOC注册中心就是用这种方式实现的
    private static Map<String,Object> ioc = new ConcurrentHashMap<String,Object>();

    public static Object getBean(String className){
        synchronized (ioc){
            //如果map中没有这个class实例
            if(!ioc.containsKey(className)){
                Object obj = null;
                try {
                    obj = Class.forName(className).newInstance();
                    ioc.put(className, obj);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                return obj;
            }
            else
                return ioc.get(className);
        }
    }
}

  虽然ConcurrentHashMap是线程安全的,只代表map中的线程安全,但是放入map的过程不是线程安全的,所有需要加synchronized关键字。容器式单例更适合创建多个单例模式。这个就不用测试,防反射破坏和和防序列化破坏前面懒汉式单例有详细说明。

接下来我们顺便看一下spring中的容器式单例的实现,例如:AbstractAutowireCapableBeanFactory里面就是容器式单例。

public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory
		implements AutowireCapableBeanFactory {
        ...
    	/** Cache of unfinished FactoryBean instances: FactoryBean name --> BeanWrapper */
	private final Map<String, BeanWrapper> factoryBeanInstanceCache = new ConcurrentHashMap<>(16);
        ...

      private FactoryBean<?> getSingletonFactoryBeanForTypeCheck(String beanName, RootBeanDefinition mbd) {
		synchronized (getSingletonMutex()) {
			BeanWrapper bw = this.factoryBeanInstanceCache.get(beanName);
			if (bw != null) {
				return (FactoryBean<?>) bw.getWrappedInstance();
			}
			if (isSingletonCurrentlyInCreation(beanName) ||
					(mbd.getFactoryBeanName() != null && isSingletonCurrentlyInCreation(mbd.getFactoryBeanName()))) {
				return null;
			}

			Object instance = null;
			try {
				// Mark this bean as currently in creation, even if just partially.
				beforeSingletonCreation(beanName);
				// Give BeanPostProcessors a chance to return a proxy instead of the target bean instance.
				instance = resolveBeforeInstantiation(beanName, mbd);
				if (instance == null) {
					bw = createBeanInstance(beanName, mbd, null);
					instance = bw.getWrappedInstance();
				}
			}
			finally {
				// Finished partial creation of this bean.
				afterSingletonCreation(beanName);
			}

			FactoryBean<?> fb = getFactoryBean(beanName, instance);
			if (bw != null) {
				this.factoryBeanInstanceCache.put(beanName, bw);
			}
			return fb;
		}
	}
      ...
}

 六、线程单例

  线程单例使用ThreadLocal来实现。ThreadLocal 不能保证其 创建的对象是全局唯一,但是能保证在单个线程中是唯一的,天生的线程安全。

/**
 * @Description 线程单例
 * @Author Bert
 * @Date 2019\5\30 0030
 */
public class ThreadLocalSigleton {

    //ThreadLocal不能保证其创建的对象全局唯一,但可以保证在单个线程中是唯一的,天生的线程安全。
    private static final ThreadLocal<ThreadLocalSigleton> threadLocal = new ThreadLocal<ThreadLocalSigleton>(){
        @Override
        protected ThreadLocalSigleton initialValue() {
            return new ThreadLocalSigleton();
        }
    };

    private ThreadLocalSigleton(){};

    public static ThreadLocalSigleton getInstance(){
        return threadLocal.get();
    }
}

/**
 * @Description 线程单例测试类
 * @Author Bert
 * @Date 2019\5\30
 */
public class SingletonThread extends Thread{

    @Override
    public void run() {
        ThreadLocalSigleton singleton = ThreadLocalSigleton.getInstance();
        System.out.println(Thread.currentThread().getName()+":"+singleton);
        ThreadLocalSigleton singleton1 = ThreadLocalSigleton.getInstance();
        System.out.println(Thread.currentThread().getName()+":"+singleton1);
        ThreadLocalSigleton singleton2 = ThreadLocalSigleton.getInstance();
        System.out.println(Thread.currentThread().getName()+":"+singleton2);
        ThreadLocalSigleton singleton3 = ThreadLocalSigleton.getInstance();
        System.out.println(Thread.currentThread().getName()+":"+singleton3);
    }
}

public class ThreadLocalSigletonTest {
    //测试线程单例
    public static void main(String[] args) {
        new Thread(new SingletonThread()).start();
        new Thread(new SingletonThread()).start();
        new Thread(new SingletonThread()).start();
    }
}

运行结果:

Thread-1:com.bert.singleton.thread.ThreadLocalSigleton@4695cf5f
Thread-1:com.bert.singleton.thread.ThreadLocalSigleton@4695cf5f
Thread-1:com.bert.singleton.thread.ThreadLocalSigleton@4695cf5f
Thread-1:com.bert.singleton.thread.ThreadLocalSigleton@4695cf5f

Thread-3:com.bert.singleton.thread.ThreadLocalSigleton@51dca821
Thread-3:com.bert.singleton.thread.ThreadLocalSigleton@51dca821
Thread-3:com.bert.singleton.thread.ThreadLocalSigleton@51dca821
Thread-3:com.bert.singleton.thread.ThreadLocalSigleton@51dca821

Thread-5:com.bert.singleton.thread.ThreadLocalSigleton@25c8063f
Thread-5:com.bert.singleton.thread.ThreadLocalSigleton@25c8063f
Thread-5:com.bert.singleton.thread.ThreadLocalSigleton@25c8063f
Thread-5:com.bert.singleton.thread.ThreadLocalSigleton@25c8063f

 同一个线程里面都是唯一的