一个单例模式就能考这么多问题,你知道么?

@(设计模式)

类图

《一个单例模式就能考这么多问题,你知道么?》

简介

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

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

问题

首先我们需要来考虑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;
    }
}

client

Singleton s = Singleton.getInstance();

问题1

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

饱汉式

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 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

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

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

总结

感觉这篇不是在讲设计模式,在讲java基础,呵呵!为什么会有问题1,2,3,4,其实这些问题也是我以前别面过的,一个单例模式就能考这么多东西,希望对大家有一定帮助。

    原文作者:算法小白
    原文地址: https://juejin.im/entry/581851662e958a0054b238e6
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞