58.Java单例模式面试怎么面?


  • 前言

    • 1.什么是单例?

    • 2.单例模式的应用场景和优缺点?

    • 3.几种单例模式实现:

    • 4.重点聊聊单例模式DCL:

  • 总结


前言

单例模式是Java中最简单也是最重要的一种设计模式,面试的时候也会经常被问到。本篇文章来进行一个总结,提供给读者面试和工作使用。

1.什么是单例?

对象创建模式,单例涉及到一个单一的类,确保只有单个对象被创建,这个单一类提供一种唯一访问对象的方式,不需要再实例化对应的类对象。

  1. 单例类只能有一个实例(对象)
  2. 单例类必须自己创建自己唯一实例(对象)
  3. 单例类提供给其他对象这个实例

2.单例模式的应用场景和优缺点?

单例模式主要解决全局使用的类的频繁的创建和销毁的问题,当想控制实例数目,节省系统资源的时候考虑单例设计。其中的关键代码为构造函数是私有的。

优点 缺点
1.内存只有一个实例,减少内存开销,频繁的创建和销毁 1.没有接口,不能继承,扩展困难
2.避免资源的多重占用 2.与单一职责冲突,一个类应该只关心内部逻辑,不该关心外面的实例化。

3.几种单例模式实现:

3.1 懒汉式,线程不安全:

代码:

public class LazySingle {
    private static LazySingle instance;

    private LazySingle() {
        System.out.println("我是实例");
    }

    public static LazySingle getInstance() {
        if (instance == null) {
            instance = new LazySingle();
        }
        return instance;
    }
}

这种线程不安全,多线程不能正常的工作,并发的时候没加锁。

3.2懒汉式,线程安全:

代码:

public class LazySingle {
    private static LazySingle instance;

    private LazySingle() {
        System.out.println("我是实例");
    }

    public static synchronized  LazySingle getInstance() {
        if (instance == null) {
            instance = new LazySingle();
        }
        return instance;
    }

    public static void main(String[] args) {
        System.out.println(LazySingle.getInstance());

    }
}

相比上面而言,加上了锁synchronized,实现了线程安全,但是性能会降低,99%不需要同步。

3.3饿汉式:

饿汉式相比懒汉式是先初始化完实例,会浪费内存空间,但是因为没有加锁的关系,在线程安全的情况下效率高。代码:

public class HungrySingle {
    private static HungrySingle instance = new HungrySingle();

    private HungrySingle() {
        System.out.println("我是饿汉模式啊");
    }
    public static HungrySingle getInstance() {
        return instance;
    }

    public static void main(String[] args) {
        System.out.println(HungrySingle.getInstance());
    }
}

3.4 双检锁/双重校验锁(DCL:double-checked locking):

这种的下面会重点进行介绍这里不再上代码,这种方式是比较推崇的一种设计,在多线程保证安全的情况下保持高性能。

3.5 静态内部类实现单例模式:

代码:

public class InnerSingle {
    private static class InnerSingleHolder {
        private static final InnerSingle INSTANCE = new InnerSingle();
    }

    private InnerSingle() {
        System.out.println("内部类的实现");
    }

    public static final InnerSingle getInstance() {
        return InnerSingleHolder.INSTANCE;
    }

    public static void main(String[] args) {
        System.out.println(InnerSingle.getInstance());
    }
}

内部类的单例模式是利用了内部类只会初始化一次静态方法的原理实现的单例,线程安全,高效,但是试用场景就是静态域的业务场景下。

4.重点聊聊单例模式DCL:

双锁机制下实现线程安全的高效单例模式:上代码:

public class Singleton {
    //1.volatile修饰
    private volatile static Singleton singleton;

    private Singleton() {
        System.out.println("双重检查的单例模式");
    }

    public static Singleton getSingleton() {
        if (singleton == null) {//2.第一次判断
            synchronized (Singleton.class) {//3.开始加锁
                if (singleton == null) {//4.第二次判断
                    singleton = new Singleton();//5.存在指令重排的可能
                }
            }
        }
        return singleton;//6.返回实例
    }

    public static void main(String[] args) {
        System.out.println(Singleton.getSingleton());
    }

}

这里提一下,就是解决并发的三个问题:

  1. 原子性
  2. 可见性
  3. 有序性

synchronizs:保证了原子性、可见性、线程的执行顺序

volatile:保证可见性、禁止指令重排

上面的volatile修饰变量,就是为了防止步骤5的指令重排。

位置5有三个动作:

  • 1.分配内存
  • 2.初始化对象
  • 3.设置定义的单例对象singleton指向刚才分配的地址。

synchronize只是保证了多线程的执行顺序,对于单个线程的内部指令的执行顺序,针对上面的三个动作,线程可能为1-2-3,也可能是1-3-2,当第二个线程拿到的实例对象还没初始化的时候就有可能报错。所以变量就需要volatile进行修饰,禁止指令重排,保证代码的执行正确性。

总结

上面就是我关于java单例模式的总结,如果面试的时候你把上面我总结的东西形成自己的语言进行描述出来,那么肯定是没问题的,最重要的是形成自己的语言。如果想跟我有更多的交流,欢迎关注我的公众号:Java时间屋 进行交流。


原文始发于微信公众号(Java时间屋):58.Java单例模式面试怎么面?

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/251818.html

(0)
java小白的头像java小白

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!