0
点赞
收藏
分享

微信扫一扫

【笔记】CarrierConfig 配置参数的解析规则

回溯 04-13 07:00 阅读 1
java-eejava

在这里插入图片描述

T04BF

👋专栏: 算法|JAVA|MySQL|C语言

🫵 小比特 大梦想

目录

九、多线程代码案例(单例模式)

1.单例模式

单例模式是设计模式里面比较简单的一种,顾名思义,单例就是只有一个实例,也就是对于进程中的某个类,它只能实例化一个对象,不会new出来多的对象
那我们如何保证一个类只能有一个对象呢???单凭我们口头保证肯定是不行的,我们就需要通过特定代码的手段来实现这个功能
单例模式我们主要认识两种写法:饿汉模式、懒汉模式

1.1饿汉模式

表示这个类的唯一实例创建得比较早,类似于饿了很久的人,一看到吃的就迫不及待想去吃
用代码体现就是:

public class Singleton {
    private static Singleton instance = new Singleton();
    public static Singleton getInstance(){
        return instance;
    }
}

static修饰的其实是类属性,就是在"类对象"上的,每个类的类对象在jvm中只有一个,那么里面的静态成员就只有一个了
此处后续需要使用这个类的实例,就可以直接通过getInstance来获取已经new好的这个,而不是重新new
但是我们怎么保证,这个类就只能实例化一次呢??我们直接将构造方法私有化即可

public class Singleton {
    private static Singleton instance = new Singleton();
    public static Singleton getInstance(){
        return instance;
    }
    private Singleton(){}//代码里面不需要有任何逻辑
}

将构造方法私有化是单例模式里面最核心的一步,类之外的代码尝试实例化对象的时候,就必须要通过构造方法,但是由于构造方法是私有的,无法调用,尝试实例化的时候就会编译出错
我们验证一下:
在这里插入图片描述
当我们尝试实例化对象:
在这里插入图片描述
就会编译出错

1.2懒汉模式

在计算机领域,"懒"往往是提高效率的表现
在懒汉模式中,不是在程序启动的时候就实例化好唯一对象了,而是在后续使用这个类的时候才去创建实例,此时如果不去使用这个类,那么创建实例的代价就很好地省下了

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

    private SingletonLazy(){}
    
}

什么时候使用这个类就什么时候创建这个实例,本质上就是能偷懒的,能少做的就少做

1.3使用场景

代码中的有些对象,本身就不适合是有多个实例的,从业务角度就应该是单个实例

比如,你写的服务器,要从硬盘上加载100G的数据到内存里面,肯定要写一个类,封装上述加载操作,并且写一些获取/处理业务数据的业务逻辑

这样的类就应该是单例的,一个实例就管理100G的数据,搞N个实例就是N*100G的内存数据,机器肯定吃不消,也没必要,因为都是重复的数据

例如在java的JDBC中,DataSourse就应该是单例的,这种对象就类似于配置管理的对象(存储服务器的地址,端口号,用户名,密码,选项参数…)

1.4上述单例模式的线程安全问题

我们需要考虑,如果有多个线程同时调用getInstance(),线程是否安全??
如果是在饿汉模式下,实例创建的时间是程序启动的时候,比main线程调用还早,因而后续使用这个类的时候,调用getInstance()一定比创建实例要晚,此时就只是读取上述变量的值了,而在多个线程里同时读取同一个变量,是不会有线程安全问题的
我们主要是来看懒汉模式下的线程安全问题
在这里插入图片描述
此时一旦出现这种执行顺序,那么就会创建多一个实例
因此我们需要做的就是将if操作和new操作打包成一个原子操作
那么就要进行加锁操作

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

    private SingletonLazy(){}

}

但是还有一个细节问题,我们知道在懒汉模式下,只有在第一次调用getInstance()才是创建实例的.而一旦创建好了,后面的就只是读操作了
但是如果我们单纯这样加,那么后面尽管是读,也是需要加锁的,而加锁的开销是很大的,也有可能导致线程堵塞
因此我们可以在外层在加上if条件判断,如果需要创建实例,才加锁

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

    private SingletonLazy(){}

}

此时两个if的作用是不一样的,外层的if是为了防止没必要的加锁操作,里层的if是判断要不要加锁(如果两个线程都进去了第一层if,那么第二层if就是防止多创建对象)

同时,为了防止编译器进行优化操作,我们还需要对instance加上volatile关键字

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

    private SingletonLazy(){}

}

1.5指令重排序问题

指令重排序实际上也是编译器优化的一种策略
我们写的代码在被编译成一条一条的二进制指令后,正常来说CPU都是按照顺序一条一条执行,但是编译器比较智能,会根据实际情况,调整指令执行的顺序,调整的目的就是为了提高效率
在单线程下,编译器的优化策略是比较安全的,能够保证优化前后代码的逻辑不变,但是在多线程环境下,就可能出现问题
就拿我们上面的单例模式 - 懒汉模式来说

instance = new SingletonLazy();

这一句代码,我们简单来看就可以分成3个步骤
(1)申请内存空间
(2)调用构造方法
(3)把此时内存的地址赋值到instance
在指令重排序下,可能会出现1,2,3或者1,3,2两种执行顺序(但是1一定是再前面的),在单例模式下这两种都是OK的
但是在多线程就可能会出现下面的情况:
在这里插入图片描述
在t1线程进行完地址赋值操作后,意味着instance就是非null,只是此时执行的对象是一个未初始化的对象
此时t2线程恰好进来,由于instance已经非空,第一个if的条件无法满足,就直接return了,如果在这个时候,进行Singleton s = …,s.func(),这里的后续操作都是针对未初始化的对象进行操作的,会出现严重的问题

解决上述问题的方法还是使用volatile
即volatile不仅能够解决内存可见性的线程安全问题,还能解决指令重排序的问题

感谢您的访问!!期待您的关注!!!

在这里插入图片描述

T04BF

🫵 小比特 大梦想
举报

相关推荐

0 条评论