当前位置: 首页 > article >正文

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

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

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


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


http://www.kler.cn/a/4708.html

相关文章:

  • Postgres对外提供服务流程
  • (12)springMVC文件的上传
  • JSON.stringify(res,null,2)的含义
  • springboot 加载本地jar到maven
  • ubuntu20.04 安装RTX2060驱动
  • 量子计算:从薛定谔的猫到你的生活
  • 【华为OD机试 2023最新 】 最大利润(C++)
  • 秃头警告 | 年薪50万PM总结的20个成功项目管理经验
  • GPT4中文调教指南。各种场景使用指南。学习怎么让它听你的话。
  • NOIP模拟赛 轰炸(bomb)
  • Callable接口
  • springboot整合redis实现秒杀功能
  • AI开发之——Leonardo—Finetuned Models及利用模型制图(5)
  • vue vben admin 使用, (个人感觉这项目封装的太深了!!!!)
  • 【故障诊断】用于轴承故障诊断的性能增强时变形态滤波方法及用于轴承断层特征提取的增强数学形态算子研究(Matlab代码实现)
  • JavaScript 基础 - 第3天
  • 刷题笔记【1】| 快速刷完67道剑指offer(Java版)
  • 四个常见的Linux面试问题
  • 2023年全国最新保安员精选真题及答案37
  • Qt 5基础 | 创建Hello World程序
  • day 14-文件操作
  • HTTP协议加强
  • 【C++】异常
  • Java中常见的密码学知识
  • 对于PM来说:拥有PMP证书,就拥有更多机会
  • 健身房训练计划—背部