美文网首页Android笔试&&面试经验
单例模式(详解,面试问题)

单例模式(详解,面试问题)

作者: DakerYi | 来源:发表于2016-10-31 22:32 被阅读332次

单例模式(从放弃到入门)

@(设计模式)

[TOC]

类图

1.png

简介

单例模式,可以说是设计模式中最简单的模式之一了,因为它不是用来解耦合用的,而是为了让整个系统有且只有一个这样的对象。为什么要只有一个呢??如果有些对象对象非常大,我需要保证内存中只存在一个;如果我希望做一些控制,只能由一个事例对象来控制,都会用到单例模式。

接下来我们就由浅入深的来探索单例模式的写法:

  1. 饿汉式
  2. 饱汉式
  3. 线程安全
  4. 双重检查加锁
  5. 静态内部类
  6. 枚举单例

问题

首先我们需要来考虑2件事情,既然单例模式需要保证系统中最多只有一个这样的对象事例,那么我怎么才能确保只能有一个实例呢??

  1. 构造方法:如果构造方法是 public,那么任何类都可以 new 我的对象,不能保证单例,所以单例模式构造方法一定是 private的
  2. 如果构造方法是 private的,那么我怎么 new对象呢?说明我只能在我自己的类中,先new出自己,然后提供给外部。
  3. 由于系统只有一个单例对象,那么线程同步是一定需要的,怎么来做呢?

就是因为上面的第2步,我们就出现了很多单例模式的写法,下面我们一一介绍:

饿汉式

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

    private Singleton(){}

    public static Singleton getInstance() {
        return instance;
    }
}

也可以这样写

public class Singleton {
    private static Singleton instance;

    static{
        instance = new Singleton();
    }
    
    private Singleton(){}

    public static Singleton getInstance() {
        return instance;
    }
}

client

Singleton s = Singleton.getInstance();

问题1

  1. 为什么 构造方法是 private的? 因为上面提到的,不能由其他类任意new单例模式的类
  2. 为什么 getInstance() 是 static方法,因为用了 类名.来调用getInstance() 方法。
  3. 为什么 instance 是 static的? 静态方法可以访问非静态变量吗?显然不行,所以instance也是static的,而且静态变量是不是只会被初始化一次(感觉3个问题都是java考试题一样,哈哈哈!)
  4. 上面的第二种使用到了静态初始化块,在Singleton第一次被使用的时候初始化。

懒汉式

public class Singleton {
    private static Singleton instance;
    
    private Singleton(){}

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

问题2

与饿汉式的不同:不是一看到 instance 就初始化,饱汉要等到第一次使用的时候才初始化,不像饿汉一样一见到 instance 就初始化,这也被称为 懒加载,如果系统中很多这样的类,显然是懒加载的时候效率更高

线程安全

如果对线程学得还行的同学就应该知道,上面的饱汉式写法其实不是那么的安全!!虽然在 getInstance() 中有非空判断,但是线程这个东西是由时间片控制的,如果一个线程刚刚非空判断通过了,然后切换到下个线程,也执行并通过非空判断,并切换到上一个线程,其实两个线程都会执行new操作,就不单例了!(虽然发生概率不高)但是我们为了更加的准确,我们来看看线程安全的版本怎么写

public class Singleton {
    private static Singleton instance;
    
    private Singleton(){}

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

问题3

只是在 getInstance() 前添加了 synchronized 关键字,保证同一时刻只能有一个线程执行获取到类锁,并执行创建实例代码,保证了只会创建一次。同时也有了懒加载的特性。

但是但是, 由于是单例模式,如果将锁直接锁在 static 的方法上(相当于类锁,其他static方法也不能执行),那么效率是非常低的,所以我们还有一定的改进。

双重检查加锁

public class Singleton {
    private static volatile Singleton instance;

    private Singleton() {
    }

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

这个版本看出优秀在哪里了吗?

  1. 懒加载
  2. 确保线程安全
  3. 只有第一次创建类的时候可能发生阻塞,后面由于非空判断都不会阻塞
  4. volatile是什么鬼?如果你关注线程安全的话就会了解,volatile是用来保证多个线程并发时,访问的都是内存中的同一个volatile对象。

缺点

仍然可以通过反射等方式产生多个对象!(其实我不懂,大家在后面评论之后我才知道 T . T)

问题4

  1. 为什么有两次非空判断?
    还是那个问题,时间片这个东西是不稳定的,由于线程切换,有可能两个线程同时都过掉第一层 非空判断,如果第二层不加,还是有可能调用2次new。

  2. 为什么是 synchronized (Singleton.class) 而不是 synchronized (this)
    有时java基本功,getInstance() 是 static 方法static 方法static 方法!,所以可以用this吗?

下面的内容是发之后网友给我的回复,感觉自己又被秀了一脸,主要还是书看得不够多,哈哈,做下面两种方式单例的补充

静态内部类

public class Singleton {
    private Singleton() {
    }

    public static Singleton getInstance() {
        return SingletonHolder.instance;
    }

    public static class SingletonHolder {
        public static final Singleton instance = new Singleton();
    }
}

原理就是说,静态内部类会在第一次被使用的时候被初始化,并且也只会被初始化一次,所以也包含懒加载线程安全的特性。

枚举单例

public enum Singleton {
    INSTANCE;
}

What the fuck? 就3行,好吧,我也是刚刚学的,解释一下原理,枚举类型也是在第一次被使用的时候初始化,并且默认构造函数是private修饰,而且线程安全。

序列化问题

传统单例写法,如果实现了序列化接口,他们就不能再保持单例,因为readObject() 方法每次会返回新的对象,所以你需要重写这个方法:

private Object readResolve(){
    return INSTANCE;
}

参考文章:

http://blog.csdn.net/u014044853/article/details/52460735
http://www.importnew.com/6461.html

总结

还总个啥,反正就是感觉自己看书太少,被秀了,以后再看点高阶的书呗!《Effictive Java》I am coming!

相关文章

  • 单例

    iOS单例模式iOS之单例模式初探iOS单例详解

  • 高并发编程-04-线程安全-单例模式详解

    单例模式详解 1,编写单例模式 饿汉式:不会存在线程安全的问题 public class Singleton1 {...

  • 设计模式之单例模式详解

    设计模式之单例模式详解 单例模式写法大全,也许有你不知道的写法 导航 引言 什么是单例? 单例模式作用 单例模式的...

  • iOS 单例模式

    关于单例模式的详解,看完这几篇,就完全了然了。iOS 单例模式iOS中的单例模式iOS单例的写法

  • 单例模式(详解,面试问题)

    单例模式(从放弃到入门) @(设计模式) [TOC] 类图 简介 单例模式,可以说是设计模式中最简单的模式之一了,...

  • 单例模式

    单例模式及C++实现代码单例模式4种实现详解 c++11改进我们的模式之改进单例模式 单例模式(Singleton...

  • 单例模式安全之反射攻击

    单例模式安全之反射攻击 源码 单例模式这里就不谈了,什么是单例模式可参考七种Java单例模式详解,这里是关于单例模...

  • Java架构师课程

    Java架构班开学典礼 Spring中常用的设计模式概述及工厂模式详解 单例模式及原型模式单例模式及原型模式单例模...

  • Java面试之单例模式浅谈

    单例模式是Java面试中常会问到的一个问题,众所周知,单例模式分为两大部分:饿汉模式和懒汉模式。但是,如果当面试官...

  • volicate、单例模式、jvm内存模型联系

    面试中,面试官问起单例模式时,我们是否只乖乖地回答了七种单例模式呢 七种单例模式实现 在单例模式的懒汉模式(线程安...

网友评论

  • Brian512:还有更好的方式,枚举
    DakerYi:@Brian512 :sweat::sweat::+1::+1:
    Brian512:@yixiaoming 内部类,哈哈
    ```
    private static class SingletonHolder {
    private static final DayNightHelper INSTANCE = new DayNightHelper();
    }
    private DayNightHelper() {
    }
    public static final DayNightHelper getInstance() {
    return SingletonHolder.INSTANCE;
    }
    ```
    DakerYi:卡卡西,怎么枚举,评论给我例子呗

本文标题:单例模式(详解,面试问题)

本文链接:https://www.haomeiwen.com/subject/oenputtx.html