美文网首页
单例模式

单例模式

作者: 杨殿生 | 来源:发表于2019-01-10 17:26 被阅读0次

这可能是我们遇上的最简单的模式了,不过他真的简单么?面试的时候如果真的细问你,你可能真的会被问的哑口无言。什么你不信,那我们来一起思考一下下面几个问题
1,单例模式定义
2,写一下你知道的所有单例模式
3,写一个饿汉式单例模式
4,写一个懒汉式单例模式
5,写出你知道的能保证线程安全的单例模式,以及说一说他们为什么能保证线程安全
6,单例如何和其他设计模式结合使用
7,如何防止反射创建单例

哈哈,现在的你是不是一脸懵逼,这些问题你都能行云流水的回答上来么,看完本篇你就可以

1,单例模式定义

这个问题不难,大家应该都知道,总结起来一句话

单例模式:一个类只能创建一个对象

类图如下:


单例模式.png

2,写一下你知道的所有单例模式

单例最简单的写法

public class SingleHungry {
    private SingleHungry(){}
    private static final SingleHungry INSTANCE = new SingleHungry();
    public static SingleHungry getInstance(){
        return INSTANCE;
    }
}

其实吧我们工作上知道这种最简单的写法就可以了,但是我们面试的时候他们一定会问题后几个问题。

3,写一个饿汉式单例模式

我们在2中写的单例就是一个饿汉式的单例模式,有同学可能会问啥叫饿汉式,懒汉式啊。其实他说的就是创建对象的时机,如果你还没使用单例就创建了单例对象那你说你是不是很饥饿,这种情况就叫饿汉式。如果在使用单例的时候才去创建单例,你也是够懒的因此叫懒汉式。

4,写一个懒汉式单例模式

懒汉式不就是延迟加载,在使用的时候才去创建单例的对象这还不简单,刷刷写下下面代码

public class SingleLazy {
    private SingleLazy(){}
    private static SingleLazy INSTANCE = null;
    public static SingleLazy getInstance(){
        if (INSTANCE == null){
            INSTANCE = new SingleLazy();
        }
        return INSTANCE;
    }
}

那这个单例有没有问题呢?

5,写出你知道的能保证线程安全的单例模式,以及说一说他们为什么能保证线程安全

第一种线程安全单例:问题1中的就是一个线程安全的
它为什么能保证线程安全下面有解释


第二种线程安全单例:

4问题中的单例答案是有问题,它的问题在于在多线程状态下是不安全的,那我们如何写能让它变成线程安全的呢,那就是加同步关键字被

public class SingleSafeLazy {
    private SingleSafeLazy(){}

    //这里使用volatile 是保证多线程状态下可见的
    private static volatile SingleSafeLazy INSTANCE = null;

    //同步锁保证多线程安全
    public static synchronized SingleSafeLazy getInstance(){
        if (INSTANCE == null){
            INSTANCE = new SingleSafeLazy();
        }
        return INSTANCE;
    }
}

如何保证线程同步的呢?

1,我们在getInstance()方法添加了synchronized关键字保证了线程同步
2,在给成员变量添加了volatile关键字,这时候一定会有好事的面试官问题volatile关键字是干啥的,这个关键字的作用是保证多线程状态下的可见性和顺序性,注意它不能保证原子性(这里可能也会有面试官问你会不会保证原子性)
3,有的面试官会问那我把synchronized去掉了为什么就会导致线程不安全了呢,这你一定不要怂,我们往下看


image.png

我们来看当如果说我们去掉了synchronized那么当线程2执行到了第13行,还没有创建成功单例对象,现在1执行到第12行,判断INSTNCE也是null,这时候线程1也会进入到13行,这样就会创建两个对象导致数据不一致。

好了我们看了第一个线程安全的单例模式,下面我们来思考一下,这个第一个线程安全的单例模式真的好么,我们每次在getInstance()的时候都会判断同步,这样的效率是不高的,可不可以改改呢,来我们继续


第三种线程安全单例:第二种升级版

public class SingleSafeLazyUpper {
    private SingleSafeLazyUpper(){}

    //这里使用volatile 是保证多线程状态下可见的
    private static volatile SingleSafeLazyUpper INSTANCE = null;

    //同步锁保证多线程安全
    public static SingleSafeLazyUpper getInstance(){
        if (INSTANCE == null){
            synchronized (SingleSafeLazyUpper.class){
                if (INSTANCE == null){
                    INSTANCE = new SingleSafeLazyUpper();
                }
            }
        }
        return INSTANCE;
    }
}

我们把synchronized关键字放到了判断非null之后,这样只有在单例为null的时候才需要加同步,提高了效率,不过你有没有注意到,我们在同步的里面又加了一个判断null的语句这是为什么呢?


image.png

我们看不加判断null的状态,线程1执行到17行,这时候线程2执行了null判断,它会等在16行,线程1出了同步代码块,线程2会立刻进入同步代码块中这样就会导致创建了两个对象,所以要加判空

那除了这两个这么复杂有没有简单点的多线程安全的呢,答案是有的


第四种线程安全单例:静态内部类方式

public class SingleInnerClass {

    private SingleInnerClass(){}

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

首先这个也是懒汉式,使用了内部类的方式,在使用的时候在去创建单例对象,并且是线程安全的,那么它为啥就是线程安全的呢,市面上好像很少有人去说这个事,其实这个就是因为happen-before原则,java在创建的对象的时候是线程安全的,什么你说你不知道啥是happen-before原则,那快去看看《java并发编程实战》


第五种线程安全单例:枚举
这是最简单的一种线程安全的单例,下面给你看看有多简单

public enum  SingleEnum {
    INSTANCE
}

一行代码结束一个单例的创建,很厉害是不是,他能保证单例也是happen-before原则


6,单例如何和其他设计模式结合使用

可以使用单例工厂来实现工厂模式,详见工厂模式


7,如何防止反射创建单例

第一种方式使用枚举的方式创建单例
第二种方式在构造函数处使用标记位

/**
 * 防止反射创建单例
 */
public class SingleReflect {

    private static boolean flag = true;

    private SingleReflect(){
        if (flag){
            flag = false;
        } else {
            throw new RuntimeException("init one more time");
        }
    }

    public static SingleReflect instance = new SingleReflect();

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

//主函数调用
public class Main {
    public static void main(String[] args){
        try {
            Class classType = SingleReflect.class;
            Constructor constructor = classType.getDeclaredConstructor();
            constructor.setAccessible(true);
            System.out.println(SingleReflect.getInstance() == constructor.newInstance());
        }
        catch (Exception e) {
            e.printStackTrace();
        }
    }
}

输出


image.png

总结
我们写的第一个单例能保证线程安全也是happen-before原则
从一个简单的单例可以看出,如果追问细节的话其实我们都会含糊,所以我们平时学习的时候多思考多注意细节在面试的时候就不会被问的哑口无言了,想比别人更优秀就需要比别人多思考


相关文章

  • 【设计模式】单例模式

    单例模式 常用单例模式: 懒汉单例模式: 静态内部类单例模式: Android Application 中使用单例模式:

  • Android设计模式总结

    单例模式:饿汉单例模式://饿汉单例模式 懒汉单例模式: Double CheckLock(DCL)实现单例 Bu...

  • 2018-04-08php实战设计模式

    一、单例模式 单例模式是最经典的设计模式之一,到底什么是单例?单例模式适用场景是什么?单例模式如何设计?php中单...

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

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

  • Telegram开源项目之单例模式

    NotificationCenter的单例模式 NotificationCenter的单例模式分析 这种单例模式是...

  • 单例模式Java篇

    单例设计模式- 饿汉式 单例设计模式 - 懒汉式 单例设计模式 - 懒汉式 - 多线程并发 单例设计模式 - 懒汉...

  • IOS单例模式的底层原理

    单例介绍 本文源码下载地址 1.什么是单例 说到单例首先要提到单例模式,因为单例模式是单例存在的目的 单例模式是一...

  • 单例

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

  • 单例模式

    单例模式1 单例模式2

  • java的单例模式

    饿汉单例模式 懒汉单例模式

网友评论

      本文标题:单例模式

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