「面试」单例模式这一篇就够了

目录

饿汉

懒汉-非线程安全

懒汉-线程安全

懒汉-双重检查锁机制

静态内部类(推荐使用)

枚举类


 

单例模式实现的四种方式:饿汉、懒汉、静态内部类、枚举

饿汉

由于使用了static关键字,所以类加载时就会创建单例对象

优点:避免了多线程下线程安全的问题(只会在类加载时创建一次)

缺点:在类加载时就创建了对象,会出现如果用不上该对象从而浪费内存空间的情况

public class Instance {

    /**
     * 也可以吧创建对象放在static静态块中
     */
    private static Instance instance = new Instance();

    /**
     * 私有构造
     */
    private Instance() {
    }

    /**
     * 获取实例
     *
     * @return
     */
    public static Instance getInstance() {
        return instance;
    }
}

懒汉-非线程安全

创建时机是第一次被调用,延时加载

优点:需要的时候才会创建实例,节省内存

缺点:多线程并发获取单例对象的情况下回创建多个对象,线程不安全

public class Instance {

    private static Instance instance;

    /**
     * 私有构造
     */
    private Instance() {
    }

    /**
     * 获取实例
     *
     * @return
     */
    public static Instance getInstance() {
        if (null == instance) {
            instance = new Instance();
        }
        return instance;
    }
}

懒汉-线程安全

添加synchronized来保证线程安全

优点:需要的时候才会创建实例,节省内存

缺点:解决了线程安全问题,但是在高并发的情况下性能不高

public class Instance {

    private static Instance instance;

    /**
     * 私有构造
     */
    private Instance() {
    }

    /**
     * 使用synchronized关键字对方法加标记,串行调用
     * 获取实例
     *
     * @return
     */
    public static synchronized Instance getInstance() {
        if (null == instance) {
            instance = new Instance();
        }
        return instance;
    }
}

懒汉-双重检查锁机制

使用双重检查锁避免过多的同步,第一次的检查是无锁的,如果对象已经创建则直接返回了。

重点在于第二次的同步锁校验和volatile关键字,Java中new Instance()并不是一条原子指令,通过javap -c 查看字节码时可以看到创建对象一共分了三个步骤

1.给分配对象内存
        2.调用构造器方法,执行初始化
        3.将对象引用赋值给变量

为什么要使用volatile:

当JVM实际运行时,又有可能会对2和3指令进行重排序(因为2和3依赖于1所以1不会重排),指令重排不了解的自行百度,当有两个线程A、B同时调用获取实例方法时,第一重判断 ①都判断为空,同时进入到获取同步锁 ②阶段,假设有A获取到锁,B线程则阻塞等待,A线程则去创建对象后释放锁,由于创建对象需要经过以上三个步骤,假设new Instance()执行到第3个步骤时(还未执行第2个步骤),由于锁已经释放,B线程获取锁执行第二重判断 ③,这时对象已经不为空(已经分配了内存)了,则可以自由访问该对象,然而这时对象并没有被初始化(A线程还未执行第2个步骤),这个时候B线程使用该对象时则会出现异常,使用volatile关键字可以保证可见性和防止指令重排序。

优点:需要的时候才会创建实例,节省内存,锁粒度比较小

缺点:在某种程度上来说解决了性能问题,但是使用时需要了解对象创建的过程和volatile关键字原理,另外Java1.4及以前版本中,很多JVM对于volatile关键字的实现有问题,会导致双重检查加锁的失败。

public class Instance {

    /**
     * 使用volatile关键字修饰
     * 保证可见性和防止指令重排序
     */
    private volatile static Instance instance;

    /**
     * 私有构造
     */
    private Instance() {
    }

    /**
     * 双重检查锁
     * 获取实例
     *
     * @return
     */
    public static Instance getInstance() {
        // 第一重判断 ①
        if (null == instance) {
            // 获取同步锁 ②
            synchronized (Instance.class) {
                // 第二重判断 ③
                if (null == instance) {
                    instance = new Instance();
                }
            }
        }
        return instance;
    }
}

静态内部类(推荐使用)

静态内部类实现单例某种意义上来说也是数据懒汉类,实现了延时加载的同时也没有线程安全性的问题

优点:外部类加载时并不需要立即加载内部类,内部类不被加载则不去初始化instance,故而不占内存。

缺点:第一次创建对象时需要重新加载子类,在某种程度上影响了执行效率

public class Instance {

    /**
     * 私有构造
     */
    private Instance() {
    }

    /**
     * 使用静态内部类获取
     *
     * @return
     */
    public static Instance getInstance() {
        return InstanceHolder.instance;
    }

    private static class InstanceHolder {
        /**
         * 内部类持有外部类对象
         */
        static Instance instance = new Instance();
    }
}

枚举类

枚举类实现单例模式是 effective java 作者极力推荐的单例实现模式。

优点:枚举类型是线程安全的,并且只会装载一次、天然支持防止反序列化重新创建新的对象。

缺点:某种意义上来说我没有用过

public enum Instance {
    INSTANCE;

    /**
     * 添加业务操作
     */
    public void toDo() {
    }
}

本期到这里啦,写的不对的地方巨佬们多多指点,喜欢的话来一个一键三连吧

 

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值