• 【JavaEE】多线程进阶(一)饿汉模式和懒汉模式


    多线程进阶(一)


    本篇主要引入多线程进阶的单例模式,为后面的大冰山做铺垫

    代码案例介绍

    单例模式

    非常经典的设计模式

    啥是设计模式

    设计模式好比象棋中的 “棋谱”. 红方当头炮, 黑方马来跳. 针对红方的一些走法, 黑方应招的时候有一些固定的套路. 按照套路来走局势就不会吃亏.

    软件开发中也有很多常见的 “问题场景”. 针对这些问题场景, 大佬们总结出了一些固定的套路. 按照这个套路来实现代码, 也不会吃亏.

    单例 -> 单个实例(对象)

    在有些场景下,希望有的类,只能有一个对象,不能有多个!在这样的场景下就可以使用单例模式了。

    举个有意思的例子就是,你只能娶一个老婆,不过你可以生很多娃🥵🥵🥵,娃就不一定是单例的


    在代码中,有很多用于管理数据的对象,就应该是”单例“的,例如mysql JDBC DataSource(描述mysql服务器的位置)

    那么这里有个问题:

    单例这个事情,还需要设计模式吗?

    当我们写代码的时候,只给这个类new一次对象,不去new多次不就行了?

    实际上,设计模式是非常必要的,这其实上是属于设计模式,这完全就是取决于程序员的编程素养。

    不过俗话说,宁可相信世界上有鬼,也不能相信人的这张破嘴!人是非常不靠谱的。

    这样的思想方法在很多地方都会涉及到:

    1. final
    2. intreface
    3. @Override
    4. throws

    于是此时就需要让编译器帮我们做监督,确保这个对象不会出现多个new(出现多个的时候就直接编译报错)

    饿汉模式

    类加载的同时, 创建实例.

    // 饿汉模式
    class Singleton{
        private static Singleton instance = new Singleton();
        //通过这个方法来获取到刚才的实例
        //后续如果想使用这个类的实例,都通过 getInstance 方法来获取
        public static Singleton getInstance(){
            return instance;
        }
        //把构造方法设置为私有,此时类外面的其他代码,就无法 new 出这个类的对象
        private Singleton(){}
    }
    
    public class Demo21 {
        public static void main(String[] args) {
            //此外又有一个实例,这就不是单例了
            //Singleton s1 = new Singleton();
            Singleton s1 = Singleton.getInstance();
            Singleton s2 = Singleton.getInstance();
            System.out.println(s1 == s2);
            //true
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    懒汉模式

    相比于饿汉模式,他就比较从容,在第一次使用的时候,再去创建实例

    //懒汉模式
    
    class SingletonLazy{
        private static volatile SingletonLazy instance = null;
        public static SingletonLazy getInstance(){
            if (instance == null) {
                    if (instance == null) {
                        instance = new SingletonLazy();
                    }
                }
            return instance;
        }
        private SingletonLazy(){}
    }
    
    public class Demo22 {
        public static void main(String[] args) {
            //此外又有一个实例,这就不是单例了
            //Singleton s1 = new Singleton();
            SingletonLazy s1 = SingletonLazy.getInstance();
            SingletonLazy s2 = SingletonLazy.getInstance();
            System.out.println(s1 == s2);
            //true
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    在这里插入图片描述

    我们举个例子:

    文本编辑器

    记事本

    需要打开一个非常大的文件

    1. 先把所有的内容都加载到内存中,然后再显示内容(加载过程会很慢)
    2. 只加载一小部分数据到内存,立即显示内容,随着用户翻页,再动态加载其他内容(懒汉模式

    那么这里我们抛出一个问题:

    以上两种模式的写法,是否是线程安全的?(如果有多个线程,同时调用getInstance,是否会出问题?)

    如果多个线程,同时修改同一个变量,此时就可能出现线程安全问题

    如果多个线程,同时读取同一个变量,这个时候就没什么事情,不会有安全问题

    那么我们看看饿汉模式的代码:

    public static Singleton getInstance(){
            return instance;
        }
    
    • 1
    • 2
    • 3

    饿汉模式下的getInstance只是进行读取,不是修改,那么他也就没有线程安全的问题了~

    那我们看看懒汉模式的代码:

    if(instance == null){
       instance = new SingletonLazy();
    }
    return instance;
    
    • 1
    • 2
    • 3
    • 4

    它既会读取,又会修改,就可能存在问题~

    在这里插入图片描述

    那么我们怎么保证懒汉模式是线程安全的呢?

    类似于下面这种改法吗?

    public static SingletonLazy getInstance(){
            if (instance == null) {
                synchronized (SingletonLazy.class) {
                    if (instance == null) {
                        instance = new SingletonLazy();
                    }
                }
            }
            return instance;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    当然不是,我们要明确一个事情,就是:锁,不是加了就安全

    加的位置对不对,才是关键

    1. 锁的{}的范围,是合理的,能够把需要作为整体的每个部分都囊括进去
    2. 锁的对象,也得是能够起到合理的锁竞争的效果

    这样才是真正的加锁

    所以修改如下:

    public static SingletonLazy getInstance(){
            synchronized (SingletonLazy.class) {
                if (instance == null) {
                    if (instance == null) {
                        instance = new SingletonLazy();
                    }
                }
            }
            return instance;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    ifnew合并成一个整体,此时的线程安全就迎刃而解了

    不过一旦这么写的话,后续每次调用getInstance,都需要先加锁了

    不过这种操作属实是画蛇添足了,加锁是一个开销很大的操作,加锁就可能会涉及到锁冲突 一冲突就会引起阻塞等待。

    还有就是:一旦某个代码涉及到 加锁,那么基本上就可以宣告这个代码和“高性能”无缘了~

    但是实际上,懒汉模式,线程安全的问题,只是出现在最开始的时候(对象还没开始new呢)

    一旦对象new出来了,后续多线程调用getInstance,就只有读操作,就不会线程不安全了~

    那么这里抛出一个问题:

    是否有什么办法可以让代码线程安全,又不会对执行效率产生太多影响呢?

    欲知后事如何,敬请期待下一篇博客~

    主要是我临时有事,等明天写完再更😥😥😥😥😥😥😥😥😥

    在这里插入图片描述

  • 相关阅读:
    霍尔闭环电流传感器在电动观光旅游车上的应用
    php://filter协议在任意文件读取漏洞(附例题)
    P1394 山上的国度 题解
    使用Element进行前端开发
    Elasticsearch 为时间序列数据带来存储优势
    新型飞蛾火焰优化算法-附代码
    2475. 数组中不等三元组的数目-快速排序+遍历求和
    水果店在微信小程序中可以实现什么功能
    html+css+javascript+jquery+bootstarp响应式旅行社旅游平台网站模板(14页)
    操作系统 生产者消费者 练习
  • 原文地址:https://blog.csdn.net/m0_73075027/article/details/133619530