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

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

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


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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值