多线程四大经典案例_单例模式

举报
bug郭 发表于 2022/09/30 22:59:41 2022/09/30
【摘要】 大家好,我是bug郭,一名双非科班的在校大学生。对C/JAVA、数据结构、Linux及MySql、算法等领域感兴趣,喜欢将所学知识写成博客记录下来。 希望该文章对你有所帮助!如果有错误请大佬们指正!共同学习交流作者简介:CSDN java领域新星创作者blog.csdn.net/bug…掘金LV3用户 juejin.cn/user/bug…阿里云社区专家博主,星级博主,developer.a...

大家好,我是bug郭,一名双非科班的在校大学生。对C/JAVA、数据结构、Linux及MySql、算法等领域感兴趣,喜欢将所学知识写成博客记录下来。 希望该文章对你有所帮助!如果有错误请大佬们指正!共同学习交流

作者简介:

本节要点

  • 了解一些线程安全的案例
  • 学习线程安全的设计模型
  • 掌握单例模式,阻塞队列,生产在消费者模型

单例模式

我们知道多线程编程,因为线程的随机调度会出现很多线程安全问题! 而我们的java有些大佬针对一些多线程安全问题的应用场景,设计了一些对应的解决方法和案例,就是解决这些问题的一些套路,被称为设计模式,供我们学习和使用!

单例模式是校招最常考的一个设计模式之一!!!

什么是单例模式呢?

单例模式能保证某个类在程序中只存在唯一一份实例, 而不会创建出多个实例.
这一点在很多场景上都需要. 比如JDBC 中的DataSource 实例就只需要一个

单例模式的具体实现方法又分为饿汉懒汉两种!
而这里所说的饿并不是贬义词!
饿汉指的是在创建一个类的时候就将实例创建好!比较急!
懒汉指的是在需要用到实例的时候再去创建实例!比较懒!

饿汉模式

饿汉模式联系实际生活中例子:
就是一个人性子比较急,也许一件事情的期限还有好久,而他却把事情早早干完!

因为我们单例模式只能有一个实例
那如何去保证一个实例呢?
我们会马上想到类中用static修饰的类属性,它只有一份!保证了单例模式的基本条件!

显然生活中这样的人很优秀,但是我们的计算机如果这样却不太好!
因为cpu和内存的空间有限,如果还不需要用到该实例,却创建了实例,那不就增加了内存开销,显然不科学.但事实问题也不大!

class Singleton{
    //饿汉模式, static 创建类时,就创建好了类属性的实例!
    //private 这里的instance实例只有一份!!!
    private static Singleton instance = new Singleton();
    //私有的构造方法!保证该实例不能再创建
    private Singleton(){
    }
    //提供一个方法,外界可以获取到该实例!
    public static Singleton getInstance() {
        return instance;
    }
}

我们可以看到这里饿汉模式,当多个线程并发时,并没有出现线程不安全问题,因为这里的设计模式只是针对了读操作!!! 而单例模式的更改操作,需要看懒汉模式!

懒汉模式

联系实际中的例子就是.就是这个人比较拖延,有些事情不得不做的时候,他才会去做完!

//懒汉模式(线程不安全版本)
class Singleton1{
    //懒汉模式, static 创建类时,并没有创建实例!
    //private 保证这里的instance实例只有一份!!!
    private static Singleton1 instance = null;
    //私有的构造方法!保证该实例不能再创建
    private Singleton1(){
    }
    //提供一个方法,外界可以获取到该实例!
    public static Singleton1 getInstance() {
        if(instance==null){//需要时再创建实例!
            instance = new Singleton1();
        }
        return instance;
    }
}

我们分析一下上述代码,该模式,对singleton进行了修改,而我们知道多线程的修改可能会出现线程不安全问题!
当我们多个线程同时对该变量进行访问时!

我们将该代码的情况分成两种,一种是初始化前要进行读写操作,初始化后只需要进行读操作!

  • instance未初始化化前
    多个线程同时进入getInstance方法!那就会创建很多次instance实例!
    联系之前的变量更改内存cpu的操作:
    在这里插入图片描述
    显然很多线程进行了无效操作!!!也会触发内存不可见问题!!!
  • instance初始化后,进行的读操作,就像上面的饿汉模式一样,并没有线程安全问题!

我们下面进行多次优化

//优化1
class Singleton2{
    //懒汉模式, static 创建类时,并没有创建实例!
    //private 保证这里的instance实例只有一份!!!
    private static Singleton2 instance = null;
    //私有的构造方法!保证该实例不能再创建
    private Singleton2(){
    }
    //提供一个方法,外界可以获取到该实例!
    public static Singleton2 getInstance() {
        synchronized (Singleton.class){ //对读写操作进行加锁!
            if(instance==null){//需要时再创建实例!
                instance = new Singleton2();
            }
            return instance;
        }
    }
}

我们将Singleton类对象加锁后,显然避免了刚刚的一些线程安全问题!但是出现了新的问题!

  • instance初始化前
    在初始化前,我们很好的将读写操作进行了原子封装,并不会造成线程不安全问题!
  • instance初始化后
    然而初始化后的每次读操作却并不好,当我们多个线程进行多操作时,很多线程就会造成线程阻塞,代码的运行效率极具下降!

我们如何保证,线程安全的情况下又保证读操作不会进行加锁,锁竞争呢?

我们可以间代码的两种情况分别处理!

//优化二
class Singleton2{
    //懒汉模式, static 创建类时,并没有创建实例!
    //private 保证这里的instance实例只有一份!!!
    private static Singleton2 instance = null;
    //私有的构造方法!保证该实例不能再创建
    private Singleton2(){
    }
    //提供一个方法,外界可以获取到该实例!
    public static Singleton2 getInstance() {
        if(instance==null){//如果未初始化就进行加锁操作!
            synchronized (Singleton.class){ //对读写操作进行加锁!
                if(instance==null){//需要时再创建实例!
                    instance = new Singleton2();
                }
            }
        }
        //已经初始化后直接读!!!
        return instance;
    }
}

我们看到这里可能会有疑惑,咋为啥要套两个if啊,把里面的if删除不行吗!!!
我们来看删除后的效果:

//删除里层if
class Singleton2{
    //懒汉模式, static 创建类时,并没有创建实例!
    //private 保证这里的instance实例只有一份!!!
    private static Singleton2 instance = null;
    //私有的构造方法!保证该实例不能再创建
    private Singleton2(){
    }
    //提供一个方法,外界可以获取到该实例!
    public static Singleton2 getInstance() {
        if(instance==null){//如果未初始化就进行加锁操作!
            synchronized (Singleton.class){ //对读写操作进行加锁!
                    instance = new Singleton2();
            }
        }
        //已经初始化后直接读!!!
        return instance;
    }
}

在删除里层的if后:
我们发现当有多个线程进行了第一个if判断后,进入的线程中有一个线程锁竞争拿到了锁!而其他线程就在这阻塞等待,直到该锁释放后,又有线程拿到了该锁,而这样也就多次创建了instance实例,显然不可!!!

所以这里的两个if都有自己的作用缺一不可!
第一个if:
判断是否要进行加锁初始化
第二个if:
判断该线程实例是否已经创建!

//最终优化版
class Singleton2{
    //懒汉模式, static 创建类时,并没有创建实例!
    //private 保证这里的instance实例只有一份!!!
    //volatile 保证内存可见!!!避免编译器优化!!!
    private static volatile Singleton2 instance = null;
    //私有的构造方法!保证该实例不能再创建
    private Singleton2(){
    }
    //提供一个方法,外界可以获取到该实例!
    public static Singleton2 getInstance() {
        if(instance==null){//如果未初始化就进行加锁操作!
            synchronized (Singleton.class){ //对读写操作进行加锁!
	            if(instance==null){
	                instance = new Singleton2();
	            }
            }
        }
        //已经初始化后直接读!!!
        return instance;
    }
}

而我们又发现了一个问题,我们的编译器是会对代码进行优化操作的!如果很多线程对第一个if进行判断,那cpu老是在内存中拿instance的值,就很慢,编译器就不开心了,它就优化直接将该值存在寄存器中,而此操作是否危险,如果有一个线程将该实例创建!那就会导致线程安全问题! 而volatile关键字保证了instanse内存可见性!!!

总结懒汉模式

  • if 外层保证未初始化前加锁,创建实例. 里层if保证实例创建唯一一次
  • synchronized加锁,保证读写原子性
  • volatile保证内存可见性,避免编译器优化
【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。