设计模式--单例模式

单例模式

定义:保证一个类仅有一个实例,并提供一个全局访问点;

类型:创建型

适用场景:(1)想确保任何情况下都绝对只有一个实例

优点:(1)在内存里只有一个实例,减少了内存开销,(2)可以避免对资源的多重占用,(3)设置全局访问点,严格控制访问。

缺点:(1)没有接口,扩展困难

重点:(1)私有构造器,(2)线程安全,(3)延时加载,(4)序列化和反序列化安全,(5)反射

懒汉式

// 懒汉式 的标准写法:

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();
                    //1.分配内存给这个对象
// 指令重排序             //3.设置lazyDoubleCheckSingleton 指向刚分配的内存地址
                    //2.初始化对象
//                    intra-thread semantics
//                    ------//3.设置lazyDoubleCheckSingleton 指向刚分配的内存地址
                }
            }
        }
        return lazyDoubleCheckSingleton;
    }
}


//有3点需要注意:
(1)synchronized (LazyDoubleCheckSingleton.class) 锁的是这个类对象。
(2)双if的判断,提高了性能
(3)volatile 关键字,防止指令重排序
(4)私有构造函数

基于静态内部类初始化的延时加载

public class StaticInnerClassSingleton {
    private static class InnerClass{
        private static StaticInnerClassSingleton staticInnerClassSingleton = new StaticInnerClassSingleton();
    }
    public static StaticInnerClassSingleton getInstance(){
        return InnerClass.staticInnerClassSingleton;
    }
    private StaticInnerClassSingleton(){
        
    }
}

饿汉式

// 代码示例
public class HungrySingleton implements Serializable{
    private final static HungrySingleton hungrySingleton;

    static{
        hungrySingleton = new HungrySingleton();
    }

    private HungrySingleton(){
    }

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

// 注意 使用 final 关键字
// 也可以不使用静态代码块的方式,定义变量时直接new

序列化破坏单例模式原理解析及解决方案

利用序列化,和反序列化,得到的对象不是同一个。

public class HungrySingleton implements Serializable,Cloneable{

    private final static HungrySingleton hungrySingleton;

    static{
        hungrySingleton = new HungrySingleton();
    }

    private HungrySingleton(){
    }

    public static HungrySingleton getInstance(){
        return hungrySingleton;
    }

    // 在使用序列化时,注意使用下面这个函数,不然在反序列化后得到的对象和系列化得到的对象不一致。
    private Object readResolve(){
        return hungrySingleton;
    }
}


// 测试代码:
public class Test {
    public static void main(String[] args){
        HungrySingleton instance = HungrySingleton.getInstance();
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("singleton_file"));
        oos.writeObject(instance);

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

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

        System.out.println(instance.getData());
        System.out.println(newInstance.getData());
        System.out.println(instance.getData() == newInstance.getData());
    }
}

反射攻击解决方案及原理分析

利用反射得到的对象不是同一个。

public class HungrySingleton implements Serializable,Cloneable{

    private final static HungrySingleton hungrySingleton;

    static{
        hungrySingleton = new HungrySingleton();
    }

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

    public static HungrySingleton getInstance(){
        return hungrySingleton;
    }

    // 在使用序列化时,注意使用下面这个函数,不然在反序列化后得到的对象和系列化得到的对象不一致。
    private Object readResolve(){
        return hungrySingleton;
    }
}


// 测试代码:
public class Test {
    public static void main(String[] args){
        Class objectClass = HungrySingleton.class;
        Constructor constructor = objectClass.getDeclaredConstructor();
        constructor.setAccessible(true);   //因为构造函数是私有的,所有设置权限
        HungrySingleton newInstance = (HungrySingleton) constructor.newInstance();
        HungrySingleton instance = HungrySingleton.getInstance();

        System.out.println(instance);
        System.out.println(newInstance);
        System.out.println(instance == newInstance);

    }
}


// 这种解决方法,适合类加载的时候就把单例创建好了,也适用于: 静态内部类方式,如下:
public class StaticInnerClassSingleton {
    private static class InnerClass{
        private static StaticInnerClassSingleton staticInnerClassSingleton = new StaticInnerClassSingleton();
    }
    public static StaticInnerClassSingleton getInstance(){
        return InnerClass.staticInnerClassSingleton;
    }
    private StaticInnerClassSingleton(){
        if(InnerClass.staticInnerClassSingleton != null){
            throw new RuntimeException("单例构造器禁止反射调用");
        }
    }
}

// 测试代码:
public class Test {
    public static void main(String[] args){
        Class objectClass = StaticInnerClassSingleton.class;
        Constructor constructor = objectClass.getDeclaredConstructor();
        constructor.setAccessible(true);   //因为构造函数是私有的,所有设置权限
        StaticInnerClassSingleton instance = StaticInnerClassSingleton.getInstance();
        StaticInnerClassSingleton newInstance = (StaticInnerClassSingleton) constructor.newInstance();

        System.out.println(instance);
        System.out.println(newInstance);
        System.out.println(instance == newInstance);

    }
}

对于懒汉式的单例,由于反射可以修改成员属性的值,所有不管构造方法多么的复杂,还是不能避免反射攻击,导致可以通过反射调用构造方法实例化对象。

 

Enum枚举单例、原理源码解析以及反编译实战(不理解)

这个暂时还不理解,先记录一下吧,等以后再研究。

 

容器单例

public class ContainerSingleton {

    private ContainerSingleton(){

    }
// HashMap 不是线程安全的,可以换成 ConcurrentHashMap
    private static Map<String,Object> singletonMap = new HashMap<String,Object>();

    public static void putInstance(String key,Object instance){
        if(StringUtils.isNotBlank(key) && instance != null){
            if(!singletonMap.containsKey(key)){
                singletonMap.put(key,instance);
            }
        }
    }

    public static Object getInstance(String key){
        return singletonMap.get(key);
    }
}


public class T implements Runnable {
    @Override
    public void run() {
        ContainerSingleton.putInstance("object",new Object());
        Object instance = ContainerSingleton.getInstance("object");

        System.out.println(Thread.currentThread().getName()+"  "+instance);

    }
}

// 测试函数
public class Test {
    public static void main(String[] args) {

        Thread t1 = new Thread(new T());
        Thread t2 = new Thread(new T());
        t1.start();
        t2.start();
        System.out.println("program end");
    }
}

基于ThreadLocal 的 线程单例

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();
    }

}

public class T implements Runnable {
    @Override
    public void run() {
        ThreadLocalInstance instance = ThreadLocalInstance.getInstance();

        System.out.println(Thread.currentThread().getName()+"  "+instance);

    }
}


// 测试代码
public class Test {
    public static void main(String[] args)  {

        System.out.println("main thread"+ThreadLocalInstance.getInstance());

        Thread t1 = new Thread(new T());
        Thread t2 = new Thread(new T());
        t1.start();
        t2.start();
        System.out.println("program end");
     }
}

源码举例

jdk 的 Runtime 类,属于饿汉式;

Spring 框架中的 bean 的单例是基于容器的,保证每个容器下的单例唯一。如 AbstractFactoryBean 类中的 getObject() 方法。

mybatis 中的 ErrorContext 类中的 instance() 方法,属于 ThreadLocal 的 基于线程的单例。

 

参考:Java设计模式透析之 —— 单例(Singleton),这个文章很适合初级,告诉我们单例是一步步如何来的,但是有两个问题,一个是 没有volatile关键字防止指令重排序,第二个是没有提到序列化和反序列化会导致单例对象不一致的问题。

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值