单例模式是什么?如何实现它?

Posted Crystal_bit

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了单例模式是什么?如何实现它?相关的知识,希望对你有一定的参考价值。

1. 单例模式基本概念及分类

       ● 基本概念:软件开发的一种设计模式(不止一种).单例代表单个实例,也就是说通过Java语法让一个类只能创建一个实例(对象).

       ● 分类:根据单例模式可再细分为"饿汉"和"懒汉"模式.


2. 单例模式的实现

2.1 "饿汉"模式 实现

          ● 简单解释:饿代表急迫,单例类中不管需不需要这个唯一对象都默认实例化它.例如,在疫情期间大家都不管价钱和自己当前的实际需求(吃一顿买一个月的量),所有吃的基本统统拿下.秉持着把超市抢购一空的原则,我成功得啥也没买着,被我妈骂了一顿.那我们如何用Java代码实现呢?

          ● 实现思路:

             ① 首先,新建一个类让该类只能实例化一个对象,且让这个对象原地就直接实例化.根据Java知识,我们可以知道在Jvm中,类对象唯一,那么其中的成员也唯一,所以我们将这个对象用static关键字修饰,让这个对象变成类对象.为了防止外部类随意访问,我们把它的访问权限设为私有(private关键字修饰).让外部通过只能通过get方法读取.

             ② 然后,让单例类的构造方法变成私有,这样外部类就无法重新实例化该单例类对象.

//饿汉模式
class Singleton1 
    private static Singleton1 instance = new Singleton1();

    private Singleton1() 

    

    public static Singleton1 getInstance() 
        return instance;
    

注:通过代码我们可以知道饿汉模式的线程是安全的,因为它只涉及到读操作(多个线程读取同一个变量线程是安全的).

2.2 "懒汉" 模式实现

           ● 简单解释: 懒就是不急,单例类中只有唯一对象为空的时候才去实例化它.例如,除非家里没吃的再去买能够做一顿的菜.

           ● 实现思路:

              ①,②步和饿汉模式一样,但是对象就不是原地直接初始化了.而是在get方法中判断该对象是否为空,为空我们再去实例化,否则直接返回该对象.但是如果我们直接这样实例化单例类的对象可以吗?

public static Singleton2 getInstance() 
        if (instance == null) 
            instance = new Singleton2();
        
        return instance;

在这之前我分享过线程不安全的原因及解决为什么会出现线程安全问题?如何解决?-_Crystal_bit的博客-CSDN博客

我们可以知道判断instance对象是否为空和初始化instance对象可能会造成修改操作不是原子的线程安全问题.多个线程同时启动可能会造成实例化多个单例类的对象的问题.所以我们需要让if和new这两个操作变成原子的,给这两个操作都加锁.

    public static Singleton2 getInstance() 
        //让if判断和实例化instance对象操作变成原子的
        synchronized (Singleton2.class) 
            if (instance == null) 
                instance = new Singleton2();
            
        
        return instance;
    

在这基础之上,多个线程调用getInstance()方法就不会实例化出多个单例类的对象了,但是因为加了锁,那么多个线程加锁就会导致锁竞争,所以为了减少不必要的加锁操作,我们就先判断instance对象是否为空,为空了我们再去加锁,如果不为空,我们就直接返回instance对象.

    public static Singleton2 getInstance() 
        //减少不必要的加锁操作
        if (instance == null) 
            //让if判断和实例化instance对象操作变成原子的
            synchronized (Singleton2.class) 
                if (instance == null) 
                    instance = new Singleton2();
                
            
        
        return instance;
    

嘿嘿~大家觉得没有任何问题了吗?并没有,new操作还可能发生指令重排序线程不安全问题,为什么呢? new操作主要分为三步:

1) 创建出对象(建好房子)

2) 构造对象(装修房子)

3) 将生成的地址赋值给对象引用(拿到钥匙).

在多线程情况下,线程调度无序,那么某个线程可能会拿到一个没有构造好的对象(啥也没有,属性都是默认的),那么我们去使用该对象成员变量或方法时,可能就会发生一系列的错误.所以为了保证线程拿到完整的对象,我们需要用volatile关键字修饰单例类的唯一对象.完整代码如下:

class Singleton2 
    //用volatile关键字修饰解决指令重排序问题
    volatile private static Singleton2 instance;

    private Singleton2() 

    

    public static Singleton2 getInstance() 
        //减少不必要的加锁操作
        if (instance == null) 
            //让if判断和实例化instance对象操作变成原子的
            synchronized (Singleton2.class) 
                if (instance == null) 
                    instance = new Singleton2();
                
            
        
        return instance;
    

注:是否存在内存可见性的线程不安全问题仍有疑问.


这样单例模式的实现就分享完毕啦~

如何实现单例模式

参考技术A   单例模式实现方式有以下五种:

  1、饿汉模式。线程安全,调用效率高,不能延时加载。

  2、懒汉模式。线程安全,调用效率不高,能延时加载。

  3、双重检测锁模式。由因JVM底层模型原因,偶尔会出问题,不建议使用。

  4、静态内部类式。线程安全,调用效率高,可以延时加载。

  5、枚举类。线程安全,调用效率高,不能延时加载,可防止反射和反序列化调用。

以上是关于单例模式是什么?如何实现它?的主要内容,如果未能解决你的问题,请参考以下文章

spring为啥是单例模式

java基础之----23种设计模式(单例模式)

如何实现单例模式

单例模式中为什么用枚举更好

单例模式中为什么用枚举更好?

为什么用枚举类来实现单例模式越来越流行?