[JavaEE]单例设计模式


 

专栏简介: JavaEE从入门到进阶

题目来源: leetcode,牛客,剑指offer.

创作目标: 记录学习JavaEE学习历程

希望在提升自己的同时,帮助他人,,与大家一起共同进步,互相成长.

学历代表过去,能力代表现在,学习能力代表未来! 


目录

单例模式

1.饿汉模式--单线程版

2.懒汉模式--单线程版

3.懒汉模式--多线程版


单例模式

单例模式是常见的设计模式之一.

什么是设计模式?

设计模式就好比象棋中的棋谱 , "红方当头炮 , 黑方马来跳" , 针对红方的走法 , 黑方可以使用一些固定套路来应对 . 软件开发中也是如此 , 针对一个固定的问题场景 , 业界大佬以及总结出一些固定的套路 , 按照这个固定套路可以少走不少弯路.

单例模式能保证每个类只有一个实例对象 , 而不会创建出多个实例.

这一点在很多场景都有应用 , 如JDBC编程中 , 要求DataSource类只能创建一个实例对象.

单例模式的具体实现方式主要分为两种 , "饿汉模式"和"懒汉模式".


1.饿汉模式--单线程版

顾名思义 , 一个饿了很久的人 , 看到吃的就会很急切."饿汉模式"在类加载阶段 , 就把实例对象给创建出来 , 给人一种很急切的感觉.

代码示例:

这段代码最关键的就是 , 在类中创建实例对象时用static修饰 .

  • 1.static 保证了这个实例的唯一性 , static操作让instance属于类属性 , 类属性属于类对象 , 类对象又是唯一的 , 因此这个实例对象也是唯一的.
  • 2.static 保证对象在类加载阶段就创建好 , 否则由于构造方法被private修饰 , 在main方法中无法直接创建实例对象.
class Singleton{
    //此处 , 先把实例创建出来.
    private static Singleton instance = new Singleton();
    public static Singleton getInstance(){
        return instance;
    }
    private Singleton(){}
}
public class ThreadDemo4 {
    public static void main(String[] args) {
        Singleton s = Singleton.getInstance();
    }
}

2.懒汉模式--单线程版

顾名思义 , 一个很懒的人绝对不会提前做一件事 , 只有等到非做不可的时候才去做.同样在代码中 , 这个实例并非是类加载的时候创建 , 而是第一次使用的时候才去创建.

class SingletonLazy{
    private static SingletonLazy instance = null;
    public static SingletonLazy getInstance(){
        if (instance == null){
            instance = new SingletonLazy();
        }
        return instance;
    }
    private SingletonLazy(){}
}
public class ThreadDemo4 {
    public static void main(String[] args) {
        SingletonLazy s1 = SingletonLazy.getInstance();
        SingletonLazy s2 = SingletonLazy.getInstance();
        System.out.println(s1 == s2);//true
    }
}

3.懒汉模式--多线程版

说到多线程 , 最先考虑的就是安全性 , 通过代码可以看出 , 饿汉模式多线程调用, 只涉及到的操作 , 因此饿汉模式在多线程中是天然安全的.而懒汉模式多线程调用象 , 即需要也需要.

由于操作不是原子性的 , 很容易出现出现多个线程同时load到null , 这时就会创建多个实例对象 , 很显然不符合单例模式.

解决方式:

出现上述线程安全问题 , 本质上是 比较 操作不是原子的 , 因此需要加锁 , 才能保证这些操作是一个整体.

class SingletonLazy{
    private static SingletonLazy instance = null;
    public static SingletonLazy getInstance(){
        synchronized (SingletonLazy.class) {
            if (instance == null){
                instance = new SingletonLazy();
            }
        }
        return instance;
    }
    private SingletonLazy(){}
}

 改进方式:

我们都知道加锁操作在程序中有很大的开销 , 因此为了提高代码执行效率 , 我们的加锁操作需要更加精确 , 分析上述代码可知 , 这里加锁操作在new对象之前是有必要的 , 但new对象之后 , 后续调用getnstance还有必要吗?后续instance的值一定是非空的 , 因此一定会触发return操作 , 相当于一个比较操作和一个返回操作 , 这两个操作都是读操作 , 此时不加锁也没事 , 因此我们对代码进行更进一步的修改.

此时不再是无脑加锁 , 只有满足条件才加锁. 

class SingletonLazy{
    private static SingletonLazy instance = null;
    public static SingletonLazy getInstance(){
        if(instance == null){
            synchronized (SingletonLazy.class) {
                if (instance == null){
                    instance = new SingletonLazy();
                }
            }
        }
        return instance;
    }
    private SingletonLazy(){}
}

此时多线程安全性还是没有做到万无一失 , 这段代码还存在指令重排序问题 , 之前我们把new操作看做是一个整体 , 这在单线程中没有任何问题 , 可一旦涉及到多线程问题就显而易见.new操作通常需要三步:

  • 1.申请内存空间
  • 2.调用构造方法 , 把内存空间初始化成一个合理的对象.
  • 3.把内存对象地址赋值给instance引用.

正常情况下是按照1->2->3的顺序来执行 , 但有时编译器为了提高执行效率 , 会进行指令重排序的操作 , 此时执行顺序就可能是1->3->2 , 如果是单线程没有任何区别 , 但如果是多线程就有问题了 , 假设 t1 按照1->3->2的顺序执行 , 当 t1 执行到1->3之后就被CPU挂起 , 此时 t2 来执行 , 站在 t2 的视角此时的 instance 引用就非空了 , 那么 t2 就会返回 instance 引用 , 并且尝试使用引用中的属性 , 但由于 t1 中的->2还没执行完 , 所以 t2 拿到的就是非法对象.

所以我们需要使用 volatile 去修饰该对象 , 来防止指令重排序.

volatile有两个功能:

  • 1.解决内存可见性问题.
  • 2.禁止指令重排序.

最终完全版:

class SingletonLazy{
    private volatile static SingletonLazy instance = null;
    public static SingletonLazy getInstance(){
        if(instance == null){
            synchronized (SingletonLazy.class) {
                if (instance == null){
                    instance = new SingletonLazy();
                }
            }
        }
        return instance;
    }
    private SingletonLazy(){}
}

评论 31
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Node_Hao

您的支持是我创作的不懈动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值