单例模式及在源码中的实践

单例模式

定义:保证一个类仅有一个实例,并提供一个全局访问点
适用场景:想确保任何情况下都绝对只有一个实例(单机情况下的计数器,数据库连接池等)

重点:

1:私有构造器
2:线程安全
3:延迟加载
4:序列化和反序列化安全
5:反射攻击

懒加载单例:
public class LazySingleton {
    private static LazySingleton lazySingleton = null;
    private LazySingleton(){
       
    }
    public synchronized static LazySingleton getInstance(){
        if(lazySingleton == null){
            lazySingleton = new LazySingleton();
        }
        return lazySingleton;
    }
}

注意这里synchronized是加在静态方法上,相当于锁的是当前类的class文件;若此方法是非静态方法,则锁的是在堆内存中生成的对象。

懒汉式双重检查锁:
public class LazyDoubleCheckSingleton {
    private volatile static LazyDoubleCheckSingleton lazyDoubleCheckSingleton = null;
    private LazyDoubleCheckSingleton(){

    }
    public static LazyDoubleCheckSingleton getInstance(){
        if(lazyDoubleCheckSingleton == null){
            synchronized (LazyDoubleCheckSingleton.class){
                if(lazyDoubleCheckSingleton == null){
                    lazyDoubleCheckSingleton = new LazyDoubleCheckSingleton();
                }
            }
        }
        return lazyDoubleCheckSingleton;
    }
}
饿汉式:
public class HungrySingleton{

    private final static HungrySingleton hungrySingleton = new HungrySingleton();

    private HungrySingleton(){
       
    }
    public static HungrySingleton getInstance(){
        return hungrySingleton;
    }
}

使用了final属性,所以在类加载的时候就创建对象了,不用考虑线程安全问题。

序列化和反序列化的对象不是同一个:

HungrySingleton 类实现Serializable接口

public class HungrySingleton implements Serializable{

    private final static HungrySingleton hungrySingleton = new HungrySingleton();

    private HungrySingleton(){

    }
    public static HungrySingleton getInstance(){
        return hungrySingleton;
    }
}

然后写一个测试类看看通过序列化和反序列化后的对象

public class Test {
    public static void main(String[] args) throws IOException, ClassNotFoundException {

        HungrySingleton hungrySingleton = HungrySingleton.getInstance();
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("singleton_file.txt"));
        oos.writeObject(hungrySingleton);

        File file = new File("singleton_file.txt");
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));

        HungrySingleton newHungrySingleton = (HungrySingleton) ois.readObject();

        System.out.println(hungrySingleton);
        System.out.println(newHungrySingleton);
        System.out.println(hungrySingleton == newHungrySingleton);

    }
}

可以看到通过序列化和反序列化之后的对象并不是之前的对象
在这里插入图片描述
要想解决上述问题,可以在类中加上readResolve方法并返回单例对象即可,具体如下:

public class HungrySingleton implements Serializable{

    private final static HungrySingleton hungrySingleton = new HungrySingleton();

    private HungrySingleton(){

    }
    public static HungrySingleton getInstance(){
        return hungrySingleton;
    }
    //防止序列化和反序列化安全
	private Object readResolve(){
        return hungrySingleton;
    }
}

可以看到修改后对象就相等了
在这里插入图片描述
为什么会这样呢?我们进入readObject方法找找原因
在这里插入图片描述
可以看到这是一个从ObjectInputStream读取Object的方法,并将obj对象返回

/**
     * Read an object from the ObjectInputStream. 
     * ...
**/
	public final Object readObject()
        throws IOException, ClassNotFoundException
    {
    	//判断是否在ObjectOutputStream的子类中重写
        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();
            }
        }
    }

进入readObject0()方法继续查看,从注释看出这个是readObject 的底层实现

/**
     * Underlying readObject implementation.
     */
    private Object readObject0(boolean unshared) throws IOException {
    	//若为块数据模式,则为true
        boolean oldMode = bin.getBlockDataMode();
        if (oldMode) {
        	//currentBlockRemaining()方法注释
        	//如果处于块数据模式,则返回当前数据块中剩余的未使用字节数。
        	//如果不在块数据模式下,则抛出IllegalStateException。
            int remain = bin.currentBlockRemaining();
            if (remain > 0) {
                throw new OptionalDataException(remain);
            } else if (defaultDataEnd) {
                /*
                 * Fix for 4360508: stream is currently at the end of a field
                 * value block written via default serialization; since there
                 * is no terminating TC_ENDBLOCKDATA tag, simulate
                 * end-of-custom-data behavior explicitly.
                 */
                 //从上面这段话看出来这是修复了bug,先不管,重点不在这
                throw new OptionalDataException(true);
            }
            bin.setBlockDataMode(false);
        }

        byte tc;
        //从流中读取数据
        while ((tc = bin.peekByte()) == TC_RESET) {
            bin.readByte();
            handleReset();
        }

        depth++;
        totalObjectRefs++;
        try {
        	//根据标志位判断
            switch (tc) {
                case TC_NULL:
                    return readNull();
                case TC_ENUM:
                    return checkResolve(readEnum(unshared));
                ...
                
				//因为我们读取的是Object对象,那么下面条件就会满足
                case TC_OBJECT:
                    return checkResolve(readOrdinaryObject(unshared));

                case TC_EXCEPTION:
                    IOException ex = readFatalException();
                    throw new WriteAbortedException("writing aborted", ex);
                    
            	...
                default:
                    throw new StreamCorruptedException(
                        String.format("invalid type code: %02X", tc));
            }
        } finally {
            depth--;
            bin.setBlockDataMode(oldMode);
        }
    }

继续进入readOrdinaryObject()方法查看,可以看到其返回一个Object对象

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 {
        	//这里是重点
        	//isInstantiable方法大概意思是:如果表示的类是可序列化和在运行期间可用序列化实例化,返回true
        	//newInstance是通过构造器创建对象
        	//很明显我们这里的obj走到这一步已经创建出来了
            obj = desc.isInstantiable() ? desc.newInstance() : null;
        } catch (Exception ex) {
            throw (IOException) new InvalidClassException(
                desc.forClass().getName(),
                "unable to create instance").initCause(ex);
        }

       ...
       
       //下面来看看hasReadResolveMethod方法
       //其内容是:如果当前类实现了serializable接口,并实现了readResolve,返回true
       //由此可见,下面全部条件都是满足的
       if (obj != null &&
            handles.lookupException(passHandle) == null &&
            desc.hasReadResolveMethod())
        {
        	//执行到这里,来看看invokeReadResolve方法
            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;
    }

继续查看invokeReadResolve方法,看注释已经明白其调用readResolve方法的返回值,就是返回之前的单例对象

/**
     * Invokes the readResolve method of the represented serializable class and
     * returns the result.  Throws UnsupportedOperationException if this class
     * descriptor is not associated with a class, or if the class is
     * non-serializable or does not define readResolve.
     */
    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();
        }
    }

在这里插入图片描述
在这里插入图片描述
通过HungrySingleton实现Serializable接口并增加readResolve方法,可以将ObjectInputStream反序列化的对象与序列化之前的对象相同,从而达到单例的目的

	private Object readResolve(){
        return hungrySingleton;
    }
通过反射破坏单例:

还是刚刚的HungrySingleton代码

public class HungrySingleton implements Serializable{

    private final static HungrySingleton hungrySingleton = new HungrySingleton();

    private HungrySingleton(){
    
    }
    public static HungrySingleton getInstance(){
        return hungrySingleton;
    }

    private Object readResolve(){
        return hungrySingleton;
    }

}

测试代码

public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {

        Class  objectClass = HungrySingleton.class;
        Constructor constructor = objectClass.getDeclaredConstructor();
        HungrySingleton instance = HungrySingleton.getInstance();
        HungrySingleton object = (HungrySingleton) constructor.newInstance();

        System.out.println(instance);
        System.out.println(object);
    }

可以看到这个类不能进入
在这里插入图片描述
现在我们加上一行代码,把无参构造器的private给忽略掉

constructor.setAccessible(true);

最后输出,可以看到两个不同的实例
在这里插入图片描述
对于防御像HungrySingleton在类加载就创建的对象,我们可以在无参构造器判断是否调用反射来破坏单例

	private HungrySingleton(){
        if(hungrySingleton != null){
            throw new RuntimeException("单例构造器禁止反射调用");
        }
    }

可以看到修改后抛出异常
在这里插入图片描述
下面来看看延迟加载的类中使用反射破坏单例的例子

public class LazySingleton {
    private static LazySingleton lazySingleton = null;
    private LazySingleton(){
        if(lazySingleton != null){
            throw new RuntimeException("单例构造器禁止反射调用");
        }
    }
    public synchronized static LazySingleton getInstance(){
        if(lazySingleton == null){
            lazySingleton = new LazySingleton();
        }
        return lazySingleton;
    }
}

测试代码

		Class  objectClass = LazySingleton.class;
        Constructor constructor = objectClass.getDeclaredConstructor();
        constructor.setAccessible(true);

        LazySingleton instance = LazySingleton.getInstance();
        LazySingleton object = (LazySingleton) constructor.newInstance();

        System.out.println(instance);
        System.out.println(object);

在这里插入图片描述
可以发现在懒加载时候,先创建单例对象,再用反射构造会抛出异常,但是反过来保证也会吗
仅仅调换两行的顺序

	LazySingleton object = (LazySingleton) constructor.newInstance();
    LazySingleton instance = LazySingleton.getInstance();

可以看到又出现两个不同的对象,原因是反射是根据构造器来创建对象的,并没有调用getInstance方法;而后面调用getInstance方法创建的对象就给该类的静态属性赋值上了,所以两个不是同一个对象。
在这里插入图片描述

接下来看看Effective Java里面推荐用枚举类来写的单例模式:

创建一个EnumInstance 枚举类

public enum EnumInstance {
    INSTANCE;
    private Object data;

    public Object getData() {
        return data;
    }

    public void setData(Object data) {
        this.data = data;
    }

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

先来看看反射条件下是否可以破坏单例

 		EnumInstance instance = EnumInstance.getInstance();
        instance.setData(new Object());

        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("singleton_file.txt"));
        oos.writeObject(instance);

        File file = new File("singleton_file.txt");
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
        EnumInstance newInstance = (EnumInstance) ois.readObject();
        
        System.out.println(instance);
        System.out.println(newInstance);
        System.out.println(instance == newInstance);

可以看到输出的是相同的实例,显然能破坏单例
在这里插入图片描述
究竟是为什么呢?进入ObjectInputStream看看,找到readEnum方法进入

    case TC_ENUM:
           return checkResolve(readEnum(unshared));

    case TC_OBJECT:
           return checkResolve(readOrdinaryObject(unshared));

因为枚举类中的name是唯一的,而readEnum方法就是通过唯一的name来获取对象,通过反序列化创建的对象根据name来赋值对象,那么当然与之前枚举类创建的对象是同一个。

private Enum<?> readEnum(boolean unshared) throws IOException {
        	...
        String name = readString(false);
        Enum<?> result = null;
        Class<?> cl = desc.forClass();
        if (cl != null) {
            try {
            	//可以看到这里用name来获取枚举对象,本来枚举对象的name就是全局唯一的,拿到的当然是同一个对象
                @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;
    }

下面来看看通过反射是否可以破坏枚举类的单例

		Class  objectClass = EnumInstance.class;
        Constructor constructor = objectClass.getDeclaredConstructor();
        constructor.setAccessible(true);

        EnumInstance object = (EnumInstance) constructor.newInstance();
        EnumInstance instance = EnumInstance.getInstance();


        System.out.println(instance);
        System.out.println(object);

输出无方法异常
在这里插入图片描述
进入枚举类代码查看构造器,发现只有一个有参构造器

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

在方法中传入String.class,int.class两个参数

		Class  objectClass = EnumInstance.class;
        Constructor constructor = objectClass.getDeclaredConstructor(String.class, int.class);
        constructor.setAccessible(true);

        EnumInstance object = (EnumInstance) constructor.newInstance("Rory", 666);
        EnumInstance instance = EnumInstance.getInstance();


        System.out.println(instance);
        System.out.println(object);

输出很明显,不能通过反射来创建枚举对象。由此可见,用枚举类来保证单例还是很靠谱的
在这里插入图片描述
下面来反编译Enum类来看看为什么有这么靠谱的单例功能,首先在JAD官网上下载相应的版本
这里解压后有两个文件
在这里插入图片描述
然后找到class的存储位置
在这里插入图片描述
最后在终端上用jad命令行来反编译class文件,生成jad文件
在这里插入图片描述
在这里插入图片描述
打开JAD文件如下:

// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://www.kpdus.com/jad.html
// Decompiler options: packimports(3) 
// Source File Name:   EnumInstance.java

package cn.rory.design.pattern.creational.singleton;


public final class EnumInstance extends Enum
{

    public static EnumInstance[] values()
    {
        return (EnumInstance[])$VALUES.clone();
    }

    public static EnumInstance valueOf(String name)
    {
        return (EnumInstance)Enum.valueOf(cn/rory/design/pattern/creational/singleton/EnumInstance, name);
    }

    private EnumInstance(String s, int i)
    {
        super(s, i);
    }

    public Object getData()
    {
        return data;
    }

    public void setData(Object data)
    {
        this.data = data;
    }

    public static EnumInstance getInstance()
    {
        return INSTANCE;
    }

	//可以看到类编类INSTANCE用了static修饰,是静态的
    public static final EnumInstance INSTANCE;
    private Object data;
    private static final EnumInstance $VALUES[];
	//下面这个静态块在类初始化的时候就会执行,没有延迟初始化,即线程安全的
    static 
    {
        INSTANCE = new EnumInstance("INSTANCE", 0);
        $VALUES = (new EnumInstance[] {
            INSTANCE
        });
    }
}

枚举类实现单例有点像我们上面实现的饿汉式(在类加载时候就初始化),也有上面的IO类,反射类来保护枚举类防止破坏单例

线程单例:

创建一个ThreadLocalInstance 类

public class ThreadLocalInstance {
    private static final ThreadLocal<ThreadLocalInstance> threadLocalInstanceThreadLocal
            = new ThreadLocal<ThreadLocalInstance>(){
        @Override
        protected ThreadLocalInstance initialValue() {
            return new ThreadLocalInstance();
        }
    };
    private ThreadLocalInstance(){

    }

    public static ThreadLocalInstance getInstance(){
        return threadLocalInstanceThreadLocal.get();
    }

}

写测试代码,可以看到main主线程拿到的都是同一个对象
在这里插入图片描述
每个ThreadLocal都维持了一个ThreadLocalMap的表,由当前线程信息作为key来获取ThreadLocalMap对象,因此在多线程访问的时候,不会相互影响。

/**
     * Variant of set() to establish initialValue. Used instead
     * of set() in case user has overridden the set() method.
     *
     * @return the initial value
     */
    private T setInitialValue() {
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
        return value;
    }
单例模式在源码中的实现:

搜索Runtime类(win下Ctrl+N快捷键)
在这里插入图片描述
可以看到Runtime的currentRuntime 属性用static修饰了,与之前的饿汉式类似,在类初始化时候就加载了

public class Runtime {
    private static Runtime currentRuntime = new Runtime();

    /**
     * Returns the runtime object associated with the current Java application.
     * Most of the methods of class <code>Runtime</code> are instance
     * methods and must be invoked with respect to the current runtime object.
     *
     * @return  the <code>Runtime</code> object associated with the current
     *          Java application.
     */
    public static Runtime getRuntime() {
        return currentRuntime;
    }
    
    ...
}

下面看看MyBatis里的ErrorContext类,可以看到LOCAL 属性是不可变的静态ThreadLocal

/**
 * @author Clinton Begin
 */
public class ErrorContext {

  private static final String LINE_SEPARATOR = System.getProperty("line.separator","\n");
  private static final ThreadLocal<ErrorContext> LOCAL = new ThreadLocal<ErrorContext>();

  private ErrorContext stored;
  private String resource;
  private String activity;
  private String object;
  private String message;
  private String sql;
  private Throwable cause;

  private ErrorContext() {
  }

  public static ErrorContext instance() {
  	//确保同一个线程拿到的都是自己线程内的上下文,线程之间并不会相互影响
    ErrorContext context = LOCAL.get();
    if (context == null) {
      context = new ErrorContext();
      LOCAL.set(context);
    }
    return context;
  }
  
  ...
}
参考:

http://www.imooc.com/t/2705746

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值