延迟初始化带来的并发错误

Q:为什么会出现延迟初始化

1.以下这段代码,如果有多个线程竞争这个锁的话,将会有很大的开销

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

为了降低锁带来的性能损耗,所以使用了双重检查锁(double-check locking),如下代码所示

public class DoubleCheckedLocking {                 
    private static Instance instance;                    
    public static Instance getInstance() {               
        if (instance == null) {                       //4:第一次检查
            synchronized (DoubleCheckedLocking.class) {  //5:加锁
                if (instance == null)                    //6:第二次检查
                    instance = new Instance();           //7:问题的根源出在这里
            }                                           
        }                                                
        return instance;                              
    }                                                  
}                                                      

在第一次检查的时候,另外一个线程访问到instance变量不空,不需要等待获得锁,大大降低了锁的损耗。

Q:延迟初始化带来的并发错误是什么?

但这个是错误的优化,另外一个线程当程序执行到第4行代码的时候,判断instance不为空的时候,程序还没有完成初始化。
一个对象要想初始化需要经历三步:

instance = new Instance();  
这行的代码可由三部分构成,
// 1.分配内存空间
alloctMemory(instance);
// 2.初始化对象
initial(instance);
// 3.设置instance指向刚分配的内存地址
instance = memory

在这里插入图片描述

因为2、3会因为指令重排序的原因带来错误,所以需要优化这个问题。
只要保证2在4的前面,就不会有问题,初次访问对象的守候,必须已经初始化完对象。

A:有两种解决方案

解决方案1.使用volatile禁止重排序

解决方案(1)是禁止重排序,这个办法就是使用volatile修饰变量

public class SafeDoubleCheckedLocking {
    private volatile static Instance instance;
    public static Instance getInstance() {
        if (instance == null) {
            synchronized (SafeDoubleCheckedLocking.class) {
                if (instance == null)
                    instance = new Instance();//instance为volatile,现在没问题了
            }
        }
        return instance;
    }
}

在这里插入图片描述

解决方案2.基于类的初始化的同步机制

解决方案(2)运行该线程重排序,但是不允许其他线程看到这个重排序的过程。
jvm在类的初始化阶段,会先加载class对象到内存中,然后使用jvm加锁同步多个线程对类的初始化。

public class InstanceFactory {
    // 使用静态内部类
    private static class InstanceHolder {
        public static Instance instance = new Instance();
    }
    
    // 调用静态内部类的方法
    public static Instance getInstance() {
        return InstanceHolder.instance ;  //这里将导致InstanceHolder类被初始化
    }
}

在这里插入图片描述
这种方式是直接对类的初始化上了锁,所以线程B肯定看不到重排序
那么可以用这个思路,将双重检查的代码写到这个用进行类的初始化的代码里面。

(扩展)那么哪时候会进行类的初始化呢?

在这里插入图片描述

(详解)类初始化中的同步处理机制

总共分为4个阶段:

第一阶段(竞争获得初始化锁)

在class对象上同步,即获取class对象的初始化锁,来初始化类和接口,这个获取锁的线程会一直等待,直到获取锁。

Class A = classLoader.loadClass(InstanceHolder.class);

如果线程A和B同时竞争初始化Class对象A
在这里插入图片描述

第二阶段(进行类的初始化和静态变量的初始化)

当A获得初始化锁的时候,A将会进行类的初始化(如果是静态类,那么将进行静态类的初始化)和静态字段的初始化,B将获得初始化锁,检查锁的状态
在这里插入图片描述
在这里插入图片描述

第三阶段:线程A设置class的state为initialized,并唤醒锁中condition中的所有线程,释放持久化锁

在这里插入图片描述
在这里插入图片描述

第四阶段:线程B结束类的初始化处理过程

线程A在第二阶段A1进行类的初始化,并且在第三阶段A4阶段释放锁后,线程B才获得初始化锁,这里存在着happen-before关系
在这里插入图片描述

※注1:这里的condition和state标记是虚构出来的。JVM的具体实现只要实现类似功能即可。

(说明)解决方案2的使用场景

解决方案2的使用场景:每次类执行的时候,首先会初始化锁,然后读取锁的state状态,如果锁的state状态时initialized,就会释放该锁,直接调用方法。如果类已经初始化完成,那如果有两个线程同时访问延迟初始化的代码,还是会存在重排序导致的错误问题。
所以使用类初始化解决延迟初始化带来的问题只适用于类还没有初始化的时候。

参考:https://zhuanlan.zhihu.com/p/34169213

### 电子器件初始化的概念和过程 #### 1. 电子器件初始化的定义 电子器件初始化是指在设备启动或重新配置期间,设置硬件和软件参数以使系统进入预定工作状态的过程。此操作确保所有内部寄存器、缓冲区以及外设接口被正确设定,从而能够正常执行预期的功能[^3]。 - **目的** 初始状态下可能存在的确定因素会影响后续工作的稳定性与可靠性;通过初始化可以消除这些确定性,并建立统一的标准环境以便于管理控制。 - **作用** 提升系统的稳定性和一致性,减少因随机初始条件引发错误的概率,同时也有助于加快调试速度缩短开发周期[^4]。 #### 2. 初始化的主要内容 根据同类型的电子器件,具体的初始化流程可能会有所差异,但一般包括以下几个方面: - **电源管理单元(PMU)** 设置合适的供电电压等级及时序安排对于保障各部分电路平稳运作至关重要。这一步骤涉及开启/关闭某些区域供应线路开关动作,调整稳压芯片输出数值直至满足规格书规定为止[^5]。 - **时钟树(Clock Tree) 配置** 几乎所有的现代微控制器都依赖精确同步机制来进行高效运算处理活动。因此需要精心规划主振荡源频率分配路径长度延迟补偿策略等方面细节问题,最终达成全局最优解方案[^6]。 - **GPIO口方向性指定及其他通用I/O选项调节** 对外部世界交互最直接的方式莫过于开放一组可编程引脚供开发者自由支配了。在这里我们仅要决定每一个单独实例应该表现为输入还是输出角色还要考虑拉高/下钳位电阻启用情况等等附加特性支持状况[^7]。 ```c void gpio_init(void){ GPIOA->MODER &= ~(0b11 << (2*PA_PIN)); // Clear previous mode setting GPIOA->MODER |= (0b01 << (2*PA_PIN)); // Set PA_PIN to output mode GPIOA->OTYPER &= ~(1 << PA_PIN); // Push-pull configuration } ``` - **中断向量表重定位及优先级排列顺序确立** 当多个事件请求并发到达处理器核芯处等待处置的时候如果没有事先明确规定好先后次序那么很可能就会陷入混乱局面难以恢复秩序。为此有必要提前做好充分准备功课明确告知哪些情形应当获得更高关注度优先得到解决机会[^8]。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值