三、多线程中如何安全发布对象

本文详细阐述了多线程编程中如何通过final域保证构造函数中的初始化值安全,以及如何通过静态初始化、volatile、final域和锁机制来避免对象的不安全发布。理解并应用这些技术能有效防止指令重排序和对象溢出问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

多线程中如何安全发布对象

一、final域

对于final域,编译器和处理器要遵守两个重排序规则。

  1. 在构造函数内对一个final域的写入

    与随后把这个被构造对象的引用赋值给一 个引用变量,这两个操作之间不能重排序。

  2. 初次读一个包含final域的对象的引用,与随后初次读这个final域,这两个操 作之间不能重排序。

这两个规则,可以防止指令重排序,来解决可见性问题。

1 写final域的重排序规则

i的值可能等于0,但j的值一定等于2

在这里插入图片描述

写普通变量i的操作,被编译器重排序到构造函数之外。

在这里插入图片描述

2 读final域的重排序规则

在一个线程中,初次读对象引用与初次读该对象包含的final 域,

JMM禁止处理器重排序这两个操作,编译器会在读final 域操作的前面插入一个LoadLoad屏障。

上面代码中,读普通域i代码,可以在写普通域i之前

在这里插入图片描述

3 溢出带来的重排序问题

在构造函数内部,不能让被构造对象的引用被其他线程可见。

分析:

  • 构造函数中 i = 1 在底层不是一个原子操作

  • 接着构造函数中 obj = this 将构造的对象发不出去,有可能 i = 1 还未操作完成

  • 带来了溢出的问题。

  • final:只是保证了指令没有重排序。

在这里插入图片描述

在这里插入图片描述

总结

final域可以确保:

构造函数中初始化的值

  1. 写入值:构造函数中写入值不会被重排序。(重排序的话,赋值操作可能会在构造函数之外)
  2. 读取值:保证读取值的操作,在构造函数之后。

二、对象的发布与逃逸

在某些情况下:希望对象、以及对象的内部状态不被暴漏出去、有时为了正当的使用目的需要发布一个对象,这就涉及到安全发布。

发布的意思是:使一个对象能够被当前范围之外的代码所使用

对象溢出:一种错误的发布,当一个对象还没有构造完成时,就使它被其他线 程所见

安全发布对象4种方法:

  1. 在静态初始化函数中,初始化一个对象引用(为什么安全:因为是在类的初始化阶段去执行的,只能保证构造安全并不能保证后续使用的安全)
  2. 将对象的引用保存到volatile类型的域或者AtomicReference对象中(利 用volatile happen-before规则)[写在读之前]
  3. 将对象的引用保存到某个正确构造对象的final类型域中(防止变量的初始化重排序到构造函数之外)
  4. 将对象的引用保存到一个由锁保护的域中(读写都上锁)

Java中看到的指令并不代表指令底层的执行逻辑(可能有多个底层的指令),且多个指令会出现重排序的情况

例如:new VolatileSyncDemo();

  1. memory = allocate() :分配内存
  2. ctorInstance(memory) :初始化
  3. instance=memory :设置对象指向被分配的内存

DCL问题(double check lock):

有可能执行顺序: 1.3.2(会返回一个不完整实例),instance不为空,但是instance还没有完成初始化(修改方案二)

  • 那么这个时候就返回了一个不完整的instance实例。

  • volatile作用:可以防止指令重排序,new VolatileSyncDemo():执行顺序1.2.3.能够返回一个完整的对象。

  • 说明final域(final修饰的变量不会重排序到构造函数之外)

// 单例模式
public class VolatileSyncDemo {

    private VolatileSyncDemo(){}

    private volatile static VolatileSyncDemo instance=null;
    
    // 问题代码:如在单例模式代码,有可能会创建出多个实例。
    public static VolatileSyncDemo getInstance(){
        if(instance==null){
			instance = new VolatileSyncDemo();
        }
        return instance;
    }
	// 修改方案一:性能差,有可能instance不为空了,但是方法上还是有锁。
    public static synchronized VolatileSyncDemo getInstance(){
        if(instance==null){
			instance = new VolatileSyncDemo();
        }
        return instance;
    }

    // 修改方案二:双重检查锁,缩小锁的范围,提高性能。
    // 会有DCL问题(double check lock),变量添加volatile
    public static VolatileSyncDemo getInstance(){
        if(instance==null){
            synchronized(VolatileSyncDemo.class) {
                if(instance==null) {
                    instance = new VolatileSyncDemo();
                }
            }
        }
        return instance;
    }
    /**
     * instance = new VolatileSyncDemo();
     * ->
     * 1. memory=allocate()     :分配内存
     * 2. ctorInstance(memory)  :初始化
     * 3. instance=memory       :设置对象指向被分配的内存
     *    
     *
     * 1.3.2 (不完整实例)
     */
}

发布一个对象时:

  • 会不会出现逃逸现象
  • 是不是会存在不安全发布
    1. instance=memory :设置对象指向被分配的内存
    • 1.3.2 (不完整实例)
      */
      }



> 发布一个对象时:
>
> - 会不会出现逃逸现象
> - 是不是会存在不安全发布





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值