多线程(2)-设计模式:单列模式

Java设计模式有很多种,这些设计模式是为了约束代码,保证代码的正确性,本次介绍单例模式,是Java开发中比较典型的开发模式。

1.单例模式:单例模式意味着单个实例(对象),即在实现某个类的同一个进程中,只能出现一个实例(对象);实例(Instance)也是一个很广泛的词:一个对象,一个进程,一个服务器...

2.实现一个单例模式的类:

2.1:

static:修饰方法和属性,被此关键词修饰之后是类属性 / 类方法 ,不带static修饰的方法和属性称为 " 实例方法 / 实列属性";

这里在借助static,把类属性创建出来了,由于instance的属性是static,这里的初始化(new)操作,只会在该类(Singleton这个类)被加载时初始化一次;

java 复制代码
class Singleton{
    private static Singleton instance =new Singleton(); 
}

2.2:饿汉模式的单例模式:

通过这三个步骤就可以创建出一个单例模式的类

java 复制代码
class Singleton{
    //通过static的作用,把这个类实例创建出来
    private static Singleton instance =new Singleton();
    //如果想使用这个实例,需要用方法获取他
    public static Singleton getInstance(){
        return instance;
    }
    //还需要防止程序员通过其他途径创建Singleton的实例
    //这里的核心方法是把类的构造方法设置为private
    private Singleton(){}

}

2.3:

这里再main方法内部再创建一个Singleton实例,发现会报出如下的报错提示,说明此时这个类遵循单例模式。

上面这种写法,创建单例的时机是在类加载的时候创建的,可以近似认为Java一创建就加载这个类 ,但是这样会有一个问题,在程序启动时,如果有很多这样的单例模式的类,那么就会扎堆初始化 ,这样可能会是服务器爆满,所以Java给出一种 " 懒汉模式 " 的单例模式,可以很好的解决在这个问题。

3.懒汉模式的单例模式(非必要,不创建new):

所谓懒汉模式就是可以有程序员决定创建单例类的时机,这里的创建时机由第一次调用getInstance()方法来决定;

java 复制代码
class Singletonlazy{
    //此处设置为null不会被初始化,只有new的时候才会被初始化;
    private static Singletonlazy Instance =null;
    public static Singletonlazy getInstance(){
        if(Instance==null){
            //对Instance进行初始化
            Instance = new Singletonlazy();
        }
        return Instance;
    }
    private Singletonlazy(){};
}

那么他们是否线程安全呢?

很明显是不安全的,这里我们先介绍一下Java的GC(垃圾回收)机制;

3.1GC机制:GC(Garbage Collection,垃圾回收) 是自动内存管理机制:识别并释放不再被引用的对象,避免内存泄漏与溢出,减少手动管理出错。

3.2:

再2步骤中t2将线程调度走后会创建(new)一次新对象,而当t1再次调度走线程后,会根据上下文再次创建(new)一次对象,这导致对象被创建了两次,随着第二次的创建完成,第一个对象就没有引用指向了,会被GC给释放掉,虽然最后结果确实是只剩下了一个对象,但我们仍然认为这里是存在bug的;

3.3:

这里根据之前学过的知识,可以对这个操作加锁,保证其原子性,就可以避免3.2的问题;

java 复制代码
 private static Singletonlazy Instance =null;
    public static Singletonlazy getInstance() {
        synchronized (Singletonlazy.class) {
            if (Instance == null) {
                //对Instance进行初始化
                Instance = new Singletonlazy();
            }
           
        }
          return Instance;
    }

这样加上锁之后,就可以保证在Instance实例在初始化之后,代码不涉及到修改,从而达到天然 " 线程安全 "。但是这样的代码只要线程一调度就自动加锁,导致开销很大性能变低,锁竞争很激烈,我们可以对代码做出下面的修改:在加锁之前先判定Instance是否被初始化,如果已经初始化过了,就不再进行加锁操作,这样大大降低了锁竞争的激烈程度。

DCL双重检查锁定:这也就是DCL双重检查锁定,第一层if判断是否需要加锁;第二层if判断是否需要进行初始化。

java 复制代码
class Singletonlazy{
    //此处设置为null不会被初始化,只有new的时候才会被初始化;
    private static Singletonlazy Instance =null;
    public static Singletonlazy getInstance() {
        if (Instance == null) {
            synchronized (Singletonlazy.class) {
                if (Instance == null) {
                    //对Instance进行初始化
                    Instance = new Singletonlazy();
                }
            }

        }
        return Instance;
    }
    private Singletonlazy(){};
}

举个例子来解释:

4.指令重排序引起的线程安全问题(只有这里存在这个问题!!!)

指令重排序也是编译器优化的一种手段,在保证逻辑不变的前提下,按照更优化的顺序来执行指令;

解决方案也很简单:加上volatile关键字,禁止编译器在读或写的时候进行优化。

java 复制代码
class Singletonlazy{
    //此处设置为null不会被初始化,只有new的时候才会被初始化;
    private volatile static Singletonlazy Instance =null;
    public static Singletonlazy getInstance() {
        if (Instance == null) {
            synchronized (Singletonlazy.class) {
                if (Instance == null) {
                    //对Instance进行初始化
                    Instance = new Singletonlazy();
                }
            }

        }
        return Instance;
    }
    private Singletonlazy(){};
}

这样,就构成了一个完整的单例模式!

相关推荐
Dabei17 小时前
Android 无障碍服务实现美团/微信自动化:客户端开发实践
前端·设计模式
巴沟旮旯儿20 小时前
vite项目配置文件和打包
前端·设计模式
雪度娃娃21 小时前
设计模式——单例模式
开发语言·c++·设计模式
逝水如流年轻往返染尘1 天前
设计模式之单例模式
单例模式·设计模式
雪度娃娃2 天前
设计模式-UML
设计模式
kyriewen112 天前
代码写成一锅粥?3个设计模式让你的项目“起死回生”
开发语言·前端·javascript·设计模式·ecmascript
geovindu2 天前
go: Mediator Pattern
设计模式·golang·中介者模式
kyriewen3 天前
代码写成一锅粥?3个设计模式让你的项目“起死回生”
前端·javascript·设计模式