zl程序教程

您现在的位置是:首页 >  工具

当前栏目

大战设计模式(第二季)【2】———— 从源码看单例模式

源码模式设计模式 单例 大战
2023-09-27 14:29:12 时间

前言

对于单例模式我们其实已经非常熟悉了,如果不熟悉的话,那你随便百度一下就能看的一大堆关于单例模式的介绍。单例模式的实现是当前设计模式中可以说实现最简单的一种模式了,也很容易理解,但是其实单例模式并非你想的那么简单哦。
下面就从几个实际的源码中来看看,单例模式在什么样的情况下会被使用。

 

单例模式(高级点)

问你下面几个问题,如果你对这几个问题烂熟于心,那么证明你真的了解单例模式。

1、有几种常见的实现单例模式的方法?
2、通过序列化和反序列化可以破坏某些单例模式
3、通过反射可以破坏某些单例模式
4、可以用枚举实现单例模式,并且非常安全
5、如何针对每一个线程单独创建一个单例

 

单例模式的实现

单例模式的实现有很多方式,这里只是个人做一个记录,供参考。

package com.linkinstars.singleton;

/**
 * (双重检查)单例模式
 * @author LinkinStar
 */
public class DoubleCheckSingleton {
    
    private DoubleCheckSingleton(){}
    
    public volatile static DoubleCheckSingleton doubleCheckSingleton = null;
    
    public static DoubleCheckSingleton getInstance(){
        if (doubleCheckSingleton == null) {
            synchronized(DoubleCheckSingleton.class){
                if (doubleCheckSingleton == null) {
                    doubleCheckSingleton = new DoubleCheckSingleton();
                }
            }
        }
        
        return doubleCheckSingleton;
    }
}
package com.linkinstars.singleton;

/**
 * 饿汉式单例
 * @author LinkinStar
 */
public class HungrySingleton {
    private final static HungrySingleton INSTANCE = new HungrySingleton();

    public static HungrySingleton getInstance() {
        return INSTANCE;
    }

    private HungrySingleton() {
        if (INSTANCE != null) {
            throw new RuntimeException("Can't create singleton by reflect.");
        }
    }
}
package com.linkinstars.singleton;

/**
 * 静态内部类实现单例模式
 * @author LinkinStar
 */
public class StaticInnerClassSingleton {
    private static class InnerClass {
        private static StaticInnerClassSingleton staticInner = new StaticInnerClassSingleton(); 
    }
    
    public StaticInnerClassSingleton getInstance(){
        return InnerClass.staticInner;
    }
    
    private StaticInnerClassSingleton(){}
}
package com.linkinstars.singleton;

/**
 * 枚举实现单例模式
 * @author LinkinStar
 */
public enum EnumSingleton {
    /** 单例 **/
    INSTANCE;
    
    private Object data;

    public Object getData() {
        return data;
    }

    public void setData(Object data) {
        this.data = data;
    }
    
    public static EnumSingleton getInstance() {
        return INSTANCE;
    }
}
package com.linkinstars.singleton;

/**
 * 单线程级别的单例
 * @author LinkinStar
 */
public class ThreadLocalSingleton {
    public static final ThreadLocal<ThreadLocalSingleton> THREAD_LOCAL_SINGLETON_THREAD_LOCAL 
            = ThreadLocal.withInitial(ThreadLocalSingleton::new);
    
    private ThreadLocalSingleton(){
        
    }
    
    public static ThreadLocalSingleton getInstance(){
        return THREAD_LOCAL_SINGLETON_THREAD_LOCAL.get();
    }
}

 

 

 

从jdk中的Runtime来看饿汉式单例

jdk中有一个Runtime的类,这个类用于获取java程序运行时的一些环境,如内存等信息。这些获取信息的方法很多都是会调用底层原生的native方法。
如:Runtime.getRuntime().maxMemory();
然后我们进入Runtime可以看见源码中:

这个是标准的饿汉式的单例
因为java程序运行时,如果要获取运行时的环境信息,总需要一个载体,并且在任何地方获取的都应该是一样的,所以用一个单例来维护这个信息,并且提供些方法,再好不过了。

 

从Spring的getSingleton方法来看单例

Spring单例模式的bean只会创建一次,如果再获取该bean,是直接从单例缓存中获取,该过程就在getSingleton(String beanName)中。

这里的方法需要你细细品读,我要说明的是,单例模式肯定有自己的好处,不然spring为什么bean默认是单例的呢?很简单的一点是因为单例的速度快,并且对象少,占用的内存就少,如果是多例的,每次使用需要进行创建会耗时间,并且消耗内存。
但是同时单例模式也有相应的问题,就是多线程访问的时候会有线程安全的问题,多个线程操作私有变量的时候就会出现。所以在使用的单例模式的时候也要同时考虑到线程安全的问题。

 

从MyBatis的ErrorContext看线程级的单例

ErrorContext是MyBatis中的一个很有意思的类,从名字我们也可以猜到,它是用于记录当前线程的发生错的信息,获取到当前错误发生的上下文信息。


我们可以想到,当我们每个线程出现错误的时候不可能去影响别的线程的错误信息的记录,每个线程肯定记录属于自己的错误信息,所以ErrorContext一开始被创建,当获取的时候,针对于每个线程本身都是获取到的对象应该是一样的。

 

总结

单例模式使用的很多,理解它的目的和优点知道它的使用场景,可以对你的系统设计上面有更多优化的可能性。