JavaEE初阶----多线程单例模式

唠嗑
居然把这个多线程的典型案例给忘记写博客了~,此时的心情和可达鸭一样一脸懵逼哈哈哈,写博客是件不容易的事情啊,一篇就得半天,还欠着网络部分好几篇呢。慢慢来把,贪多嚼不烂
JavaEE初阶----多线程单例模式_第1张图片

多线程单例模式(面试常见问题):

首先我们得知道单例模式是一种设计模式~

写代码时有些常见场景,设计模式就是针对这些常见场景给出得一些经典解决方案~

单例模式的两种典型实现(Singleton~~singleDog):

1.饿汉模式
举个洗碗的例子吧:中午这顿饭使用了四个碗,吃完之后,立即把这四个碗给洗了~~【饿汉】=》着急
饿汉的单例模式,是比较着急的去创建实例的。

2.懒汉模式
同上:中午这顿饭使用了四个碗,吃完之后,先不洗~晚上这顿只需要两个碗,然后只洗2个即可【懒汉】=》在计算机中一般都是褒义词(提高效率)
懒汉的单例模式,是不太着急去创建实例的,只是在用的时候,才真正的去创建~~

JavaEE初阶----多线程单例模式_第2张图片

⭐️饿汉模式的代码:

public class Singleton {
    //1.使用static创建一个实例,并且立即进行实例化。
    //这个instance对应的实例,应该是该类的唯一实例。
    private static Singleton instance=new Singleton();
    //2为了防止程序猿在其他地方不小心new 这个Singleton,就可以把构造方法设为private
    private Singleton(){};
    //3提供一个方法,让外面能够拿到唯一实例
    public static Singleton getInstance(){
        return  instance;
    }
}

JavaEE初阶----多线程单例模式_第3张图片

⭐️懒汉模式的代码:

class Singleton2{
    //1.这里不是立即就初始化实例
    private static Singleton2 instance=null;
    //2.把构造方法设为private
    private Singleton2(){};
    //3.提供一个方法来获取到上述单例的实例
    //只有当真正需要这个实例的时候,才会真正的去创建这个实例
    public static Singleton2 getInstance(){
        if(instance==null){
            instance=new Singleton2();
        }
        return instance;
    }
}

JavaEE初阶----多线程单例模式_第4张图片

☀️接下来真正要解决的问题,是实现一个线程安全的单例模式!(懒汉模式)

JavaEE初阶----多线程单例模式_第5张图片
那么如何保证懒汉模式的线程安全呢?

1.我们首先想到的当然是给他加锁,那么又如何加锁才合适呢?

JavaEE初阶----多线程单例模式_第6张图片
当前虽然加锁以后,线程安全问题得到了解决,但是呢又存在了新的问题~~

对于刚才这个懒汉模式的代码来说,线程不安全是发生在instance被初始化之前的,未初始化的时候,多线程调用getinstance,就可能同时涉及到读和修改。

但是一旦instance被初始化之后(一定不是null,if的条件一定不成立了),getinstance操作就只剩下两个读操作~~也就线程安全了

而按照上述的加锁方式,无论代码是初始化之后,还是初始化之前,每次调用getinstance都会进行加锁,也意味着即使是初始化之后(线程已经安全了),但是仍然存在大量的锁竞争。
JavaEE初阶----多线程单例模式_第7张图片

改进方案:
getinstance,初始化之前,才进行加锁,初始化之后,就不再进行加锁操作了(在加锁这里加上一层条件判定即可,条件就是当前是否已经初始化完成~~instance==null
代码如下:

 //如果这个条件成立,说明当前的单例未初始化过,存在线程安全的风险,就需要加锁
        if (instance==null)
        {
            synchronized (Singleton2.class){
                if(instance==null){
                    instance=new Singleton2();
                }
            }
        }
        return instance;

ps:可能有些小伙伴会在想怎么这两个if条件一模一样,其实这只是一个美丽的巧合而已~~这两条件起到的预期目的是完全不一样的,上面的条件判定是是否要加锁,下面的条件判定的是是否要创建实例,只是碰巧这两个目的都是判定instance是否为null

我们来看看现在的完整代码:

class Singleton2{
    //1.这里不是立即就初始化实例
    private static Singleton2 instance=null;
    //2.把构造方法设为private
    private Singleton2(){};
    //3.提供一个方法来获取到上述单例的实例
    //只有当真正需要这个实例的时候,才会真正的去创建这个实例
    public static Singleton2 getInstance(){
        //如果这个条件成立,说明当前的单例未初始化过,存在线程安全的风险,就需要加锁
        if (instance==null)
        {
            synchronized (Singleton2.class){
                if(instance==null){
                    instance=new Singleton2();
                }
            }
        }
        return instance;
    }
}
2.其实当前的代码还存在一个重要的问题:

JavaEE初阶----多线程单例模式_第8张图片
解决方案:
给instance加上volatile即可~~

class Singleton2{
    //1.这里不是立即就初始化实例
    private static  volatile  Singleton2 instance=null;
    //2.把构造方法设为private
    private Singleton2(){};
    //3.提供一个方法来获取到上述单例的实例
    //只有当真正需要这个实例的时候,才会真正的去创建这个实例
    public static Singleton2 getInstance(){
        //如果这个条件成立,说明当前的单例未初始化过,存在线程安全的风险,就需要加锁
        if (instance==null)
        {
            synchronized (Singleton2.class){
                if(instance==null){
                    instance=new Singleton2();
                }
            }
        }
        return instance;
    }
}

这个代码是完全体的线程安全单例模式:
1)正确的位置加锁
2)双重if判定
3)volatile

小拓展:
在这里插入图片描述
static表示的意思和字面这个单词,没有任何的联系(历史遗留问题)
追溯到C语言,即使是C语言中,static的效果也是不和字面意思一样
其实在上古时期,那时候的static是表示把变量放到“静态内存区”,于是就引入了关键字static
随着计算机的发展,静态内存区这个东西逐渐没了~~但是static关键字仍然在,并且被赋予了其他的功能,C++static除了上述C中的功能之外,又有了新的用法,修饰一个类的成员变量和成员函数,此处static修饰的成员就表示类的成员,
JavaC++static给继承过来了
为啥后来把static给保留下来并且成为表示类属性,而不用其他的词呢?
答:一个编程语言,要想新增加关键字,是一件非常有风险的事情。

你可能感兴趣的:(java,java,面试,intellij-idea,经验分享,系统安全)