一步一步剖析单例模式

一、单例模式的满足条件?

1.必须一个类在任何情况下都绝对只有一个实例,并提供一个全局访问点

2.单例模式属于创建型模式

二、饿汉式单例

1.代码

代码如下(示例):

/**
 * @author zhuwenhua
 * @description 饿汉式单例模式
 */
public class HungryStaticSingleton {

    private static final HungryStaticSingleton hungrystaticsingleton = new HungryStaticSingleton();

    public HungryStaticSingleton getInstance(){
        return hungrystaticsingleton;
    }
}

2.优缺点

优点是简单效率高,没有加锁,用户体验比懒汉式好;缺点是无论用不用都会加载,浪费内存。

为了解决这个内存问题引入懒汉式单例


三、懒汉式单例

1.代码

/**
 * @author zhuwenhua
 * @description 懒汉式 在外部需要的时候才会实例化
 */
public class LazySimpleSington {

    private static LazySimpleSington lazySimpleSington = null;

    public LazySimpleSington getInstance() {
        if (null == lazySimpleSington) {
            return new LazySimpleSington();
        }
        return lazySimpleSington;
    }
}

2.优缺点

乍一看在使用的时候才实例化看起来解决了内存浪费的问题,但是新的问题是多线程的情况下就不是单例的了,那么改进一下,加个锁

3.改进

代码

/**
 * @author zhuwenhua
 * @description 懒汉式 在外部需要的时候才会实例化
 */
public class LazySimpleSington {

    private static LazySimpleSington lazySimpleSington = null;

    public synchronized LazySimpleSington getInstance() {
        if (null == lazySimpleSington) {
            return new LazySimpleSington();
        }
        return lazySimpleSington;
    }
}

但是这样的话每次进来都会走加锁的这个方法,效率低;再次优化一下,改成双重检测单例

4.双重检测型单例

优化之后如下

/**
 * @description: 单例模式
 * @author: zhuwenhua
 */
public class Singleton {

    public Singleton() {
    }

    private static Singleton singleton;

    public  static  Singleton getInstance(){
        //检查是否要阻塞
        if(null==singleton){
            synchronized(Singleton.class){
                //检查是否要重新创实例
                if(null==singleton){
                    return new Singleton();
                }
            }
        }
        return singleton;
    }
}

但是新的问题又来了,我们知道,在实例化的时候分为了三步

1. 分配内存给这个对象(即赋初始值,如果是int类型则给一个0)
2. 初始化对象
3. 将singleton指向这个对象

此时就存在一个指令重排序的问题,如果2和3的位置颠倒了,那么singleton指向的就是一个未初始化的对象(如果是int类型就是初始值0),就会出现问题。解决办法就是加上volatile关键字,代码如下

/**
 * @description: 单例模式
 * @author: zhuwenhua
 */
public class Singleton {

    public Singleton() {
    }

    private static volatile Singleton singleton;

    public  static  Singleton getInstance(){
        //检查是否要阻塞
        if(null==singleton){
            synchronized(Singleton.class){
                //检查是否要重新创实例
                if(null==singleton){
                    return new Singleton();
                }
            }
        }
        return singleton;
    }
}

但是吧,这样感觉写的不是很优雅,于是新一轮的优化来了

5.内部类型单例

代码如下

/**
 * @author zhuwenhua
 * @description
 */
public class LazyInnerClassSingleton {

    //内部类单例 不使用的话就不会加载内部类
    private LazyInnerClassSingleton() {
    }
    //static 单例空间共享
    //final  以免被重写重载
    public static final LazyInnerClassSingleton getInstance(){
        return LazyHoder.lazyinnerclasssingleton;
    }

    private static class LazyHoder{
        private static final LazyInnerClassSingleton lazyinnerclasssingleton = new LazyInnerClassSingleton();
    }
}

四、破坏单例

1. 反射

代码如下

		Class<?> clazz = LazyInnerClassSingleton.class;
        Constructor<?> constructor = clazz.getDeclaredConstructor(null);
        constructor.setAccessible(true);
        Object o1 = constructor.newInstance();
        Object o2 = constructor.newInstance();
        System.out.println(instance1);
        System.out.println(instance2);

如何解决呢,其实很简单,反射不是暴力访问无参构造方法的吗,那么我们在构造方法中判断该对象使用存在即可,如果存在就抛出异常

2.序列化破坏

        SeriableSingleton s1=null;
        SeriableSingleton s2 = SeriableSingleton.getInstance();
        FileOutputStream fos = new FileOutputStream("SeriableSingleton.obj");
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        oos.writeObject(s2);
        oos.flush();
        oos.close();
        FileInputStream fis = new FileInputStream("SeriableSingleton.obj");
        ObjectInputStream ois = new ObjectInputStream(fis);
        s1= (SeriableSingleton)ois.readObject();
        ois.close();
        System.out.println(s1);
        System.out.println(s2);

序列化的解决办法是在该单例类中添加一个方法

/**
 * 序列化破坏解决办法
 */
public class SeriableSingleton implements Serializable {

    public final static SeriableSingleton singleton =new SeriableSingleton();

    public SeriableSingleton() {
    }

    public static SeriableSingleton getInstance(){return singleton;}

    //使用了一个桥接模式
    //只不过是覆盖了反序列化出来的对象
    //创建了两次,之前的对象会被回收
    private Object readResolve(){return singleton;}
}

3.readResolve方法原理(多图预警)

1.点击该方法往下看在这里插入图片描述
2.可以看到调用了这个readObject0方法,继续往下看
在这里插入图片描述
3.可以看到调用这个readOrdinaryObject这个方法继续
在这里插入图片描述
4.此处判断是否存在构造方法,咱们的单例类是存在的,所以它调用的是desc.newInstance()这个方法,即新建了一个对象
在这里插入图片描述
5.我继续往下走,可以看到这个方法,点进去
在这里插入图片描述
6.其实就是判断这个实例是否为空,往上翻可以看到这个类其实就是
在这里插入图片描述
7. readResolve这个方法无参的反射
在这里插入图片描述
8.然后我们的单例类里边有这个方法,往下走就看到他调用了这个方法,即desc.invokeReadResolve()这个方法,反射调用咱单例类的readResolve()方法,到这里您应该就知道了为什么要写一个readResolve()就可以解决序列化破坏单例模式的问题了
在这里插入图片描述
那么我们有没有一种更为优雅的方式来实现呢,肯定是有的,这就是《Effective java》中极为推荐的一种方法,即枚举型单例

五、注册式单例

1. 枚举式单例

代码如下

/**
 * @author zhuwenhua
 * @description 枚举式单例
 */
public enum EnumSingleton {
    SINGLETON;

    private Object object;

    public Object getObject() {
        return object;
    }

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

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

他能不被反射破坏的原因是,如图,如果这个clazz是枚举类型的,jdk层面会帮你抛出异常。
在这里插入图片描述

2. 容器式单例

代码如下

/**
 * @author zhuwenhua
 * @description 容器式单例
 */
public class ContainerSingleton {

    public ContainerSingleton() {
    }

    private static Map<String,Object> ioc = new ConcurrentHashMap<String,Object>();

    //容器式单例线程不安全的问题可以借鉴spring中的解决方式,双重检查锁方式
    public static Object getInstance(String className) throws Exception {
        Object instance=null;
        if(!ioc.containsKey(className)){
            Object o = Class.forName(className).newInstance();
            ioc.put(className,o);
            return o;
        }else {
            return ioc.get(className);
        }
    }
}

其实枚举底层也是个map,这种容器式跟枚举式某种意义上来说有着异曲同工之妙,也是spring使用的方式。

六、ThreadLocal单例

代码如下

/**
 * 线程内单例
 */
public class ThreadLocalSingleton {
    static ThreadLocal<ThreadLocalSingleton> singleton=new ThreadLocal<ThreadLocalSingleton>(){
        @Override
        protected ThreadLocalSingleton initialValue() {
            return new ThreadLocalSingleton();
        }
    };

    public ThreadLocalSingleton() {
    }

    public static  ThreadLocalSingleton getInstance(){return singleton.get();};
}

其实严格意义上来说,这个并不是一种真正意义上的单例,因为他只是在一个线程中是单例,但是在某些场合依然有着举足轻重的作用

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值