面试题首页 > 设计模式面试题

单例模式面试题

001什么是单例模式?

单例设计模式(singleton)最常用、最简单的设计模式。单例模式的目的是保证在整个应用中某一个类有且只有一个实例(一个类在堆内存只存在一个对象)。怎么样让一个类一个类有且只有一个实例呢?最核心的就是一句话就是构造方法私有化。单例模式的编写有很多种写法。比如饿汉式、懒汉式、双重加锁机制静态内部类、枚举。

002单例模式中的饿汉式是什么?

饿汉式,从名字上理解像是有个人很容易饿,所以他每次不管自己饿不饿,没事就提前把要吃的东西先准备出来,也就是 “比较勤”,所以实例在初始化的时候就已经建好了,不管你有没有用到,都先建好了再说。

003饿汉式如何实现?

[1] 构造方法私有化,防止外界通过构造器创建新的工具类对象;
[2] 必须在该类中,自己先创建出一个对象;
[3] 向外暴露一个公共的静态方法用于返回自身的对象;

// 单例模式(饿汉式)
public class Singleton1 {
    // [1]构造方法私有化,防止外界通过构造器创建新的工具类对象
    private Singleton1() {
    }
    // [2] 必须在该类中,自己先创建出一个对象(这行代码在类加载的时候就执行了)
    private static Singleton1 instance = new Singleton1();
    // [3] 向外暴露一个公共的静态方法用于返回自身的对象
    public static Singleton1 getSingleton() {
        return instance;
    }
}

注意:我们知道,类加载的方式是按需加载,且加载一次。因此,在上述单例类被加载时,就会实例化一个对象并交给自己的引用,供系统使用;而且,由于这个类在整个生命周期中只会被加载一次,因此只会创建一个实例,即能够充分保证单例。

004饿汉模式的优缺点?

好处:这种写法比较简单,就是在类装载的时候就完成实例化。避免了线程同步问题。
坏处:在类装载的时候就完成实例化,没有达到Lazy Loading(懒加载)的效果。 如果从始至终从未使用过这个实例,则会造成内存的浪费。

005什么是单例的懒汉式?

懒汉式,顾名思义就像一个人比较懒,平时不爱动,事情火烧眉毛了才迫不得已去做,“比较懒”,用的时候才去检查有没有实例,如果有则返回,没有则新建。有线程安全和线程不安全两种写法,区别就是synchronized关键字。

006线程不安全懒汉式实现方式?

[1] 构造方法私有化,防止外界通过构造器创建新的工具类对象
[2] 事先创建好当前类的一个私有静态对象
[3] 向外暴露一个公共的静态方法用于返回自身的对象

// 单例模式(懒汉式)
public class Singleton2 {
    // [1]私有化构造方法。
    private Singleton2() {
    }
    
    // [2] 事先创建好当前类的一个私有静态对象
    private static Singleton2 instance = null;
    
    // [3] 向外暴露一个公共的静态方法用于返回自身的对象
    public static Singleton2 getInstance() {
        // 被动创建,在真正需要使用时才去创建
        if (null == instance) {
            instance= new Singleton2();
        }
        return instance;
    }
}

注意:我们从懒汉式单例可以看到,单例实例被延迟加载,即只有在真正使用的时候才会实例化一个对象并交给自己的引用。

007线程不安全懒汉式优缺点?

优缺点:这种写法起到了Lazy Loading(懒加载)的效果,但是只能在单线程下使用。如果在多线程下,一个线程进入了if (singleton == null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。所以在多线程环境下不可使用这种方式。

008线程安全懒汉式实现方法?

为了解决上面的问题,最简单的方法是将整个 getInstance() 方法设为同步(synchronized)。

// 单例模式(懒汉式)
public class Singleton2 {
    //  [1]私有化构造方法。
    private Singleton2() {
    }

    // [2] 事先创建好当前类的一个私有静态对象
    private static Singleton2 instance = null;

    // [3] 向外暴露一个公共的静态方法用于返回自身的对象
    public static synchronized Singleton2 getInstance() {
    	// 被动创建,在真正需要使用时才去创建
        if (null == instance) {
            instance= new Singleton2();
        }
        return instance;
    }
}

注意:虽然做到了线程安全,并且解决了多实例的问题,但是它并不高效。因为同步操作只需要在第一次调用时才被需要,即第一次创建单例实例对象时,也就是if条件判断里面的内容。这就引出了双重检验锁。

009单例中的双重加锁机制是什么?

双重检验锁,又叫双重校验锁,综合了懒汉式和饿汉式两者的优缺点整合而成。看上面代码实现中,特点是在synchronized关键字内外都加了一层 if 条件判断,这样既保证了线程安全,又比直接上锁提高了执行效率,还节省了内存空间。

// 单例模式(懒汉式)
public class Singleton3 {
    private static Singleton3 instance;
    private Singleton3 (){}

    public static Singleton3 getSingleton() {
        if (instance == null) {                         
            synchronized (Singleton3.class) {
                if (instance == null) {       
                    instance = new Singleton3();
                }
            }
        }
        return instance;
    }
}

010静态内部类如何实现单例?

public class Singleton4 {  
    private static class SingletonHolder {  
        private static final Singleton4 INSTANCE = new Singleton4();  
    }
    private Singleton4 (){}
    public static final Singleton4 getInstance() {  
        return SingletonHolder.INSTANCE; 
    }  
}

注意:这种写法仍然使用JVM本身机制保证了线程安全问题;由于 SingletonHolder 是私有的,除了 getInstance() 之外没有办法访问它,因此它是懒汉式的;同时读取实例的时候不会进行同步,没有性能缺陷;也不依赖 JDK 版本。

011用枚举如何实现单例?

public enum EasySingleton{
	// 定义枚举常量
    INSTANCE;
}

使用:我们可以通过EasySingleton.INSTANCE.工具方法() 的方式来访问实例,这比调用getInstance()方法简单多了。创建枚举默认就是线程安全的,所以不需要担心double checked locking,而且还能防止反序列化导致重新创建新的对象。

012那些地方用到了单例模式?

1)网站的计数器,一般也是采用单例模式实现,否则难以同步。
2)应用程序的日志应用,一般都是单例模式实现,只有一个实例去操作才好,否则内容不好追加显示。
3)多线程的线程池的设计一般也是采用单例模式,因为线程池要方便对池中的线程进行控制
4)Windows的(任务管理器)就是很典型的单例模式,他不能打开俩个
5)windows的(回收站)也是典型的单例应用。在整个系统运行过程中,回收站只维护一个实例。

目录

返回顶部