Java对象的发布与逃逸

先来解释一下两个概念:

对象的发布: 将对象的引用返回给作用范围以外的代码使用
对象的逃逸: 错误的对象发布方式,在对象没有构造完成时,就把对象提供给其他线程使用,这种发布方式是不安全的对象发布的实例

对象的发布

看一个demo:

public class UnsafeObject {
    private String[] value = {"a","b","c"};
	//使用get发布对象
    public String[] getValue() {
        return value;
    }
    public static void main(String[] args) {
        UnsafeObject unsafeObject = new UnsafeObject();
        //启动另外一个线程,对value数组进行修改
        new Thread(() -> {
            String[] value1 = unsafeObject.getValue();
            value1[0] = "d";
        }).start();
        //主线程等待一会,这里只是为了显现出来错误:其他线程有可能修改
        //有可能比主线程快,有可能慢,所以打印的结果是随机的
        try {
            Thread.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //主线程打印value数组
        String[] value = unsafeObject.getValue();
        System.out.println(Arrays.toString(value));
    }
}

  demo中使用get方法把私有属性value数组返回出去,这就是对象的发布,在外面的代码也就是main方法拿到value数组时,同时又启动了一个线程,也就是两个线程同时对value数组进行操作,新线程对value数组进行了修改,而主线程在读取value数组,这时的结果是不确定的,有可能新线程没有修改完成主线程就读到了abc,有可能主线程读到的是修改后的dbc,所以这种对象的发布是不安全

对象的逃逸

  刚才在上面已经讲过了,对象的逃逸是错误对象发布的特例,指的是在对象没有完成构造的时候,就发布了对象。对象逃逸经常发生在构造函数中启动线程或注册监听器时,先来看一个demo

public class ThisEscape4 {
    private int value;
    private ThisEscape4(){
        //新创建一个线程,直接访问了value,也就相当于发布了this对象
        new Thread(() -> {
            System.out.println(this.value);
        }).start();
        try {
            Thread.sleep(300);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //未完成的构造函数逻辑
        this.value = 1;
    }
    public static void main(String[] args) {
        ThisEscape4 thisEscape4 = new ThisEscape4();
        System.out.println(thisEscape4.value);
    }
}

  可以看到在构造函数中启动了一个线程,访问了this.value,这就相当于把this对象发布给了其他线程,其他线程这时访问了this对象,但是this对象并没有构造完成,在后面还有未完成的构造函数逻辑,这种情况就叫做对象的逃逸,也叫this逃逸

看一下运行情况:
在这里插入图片描述

  对于这种情况,可以设置为私有构造,然后提供一个工厂方法,在工厂方法种启动线程即可,看一下改造后的demo:

public class ThisEscape5 {
    private int value;
    private static Thread thread;
    private ThisEscape5() {
        thread = new Thread(() -> {
            System.out.println(value);
        });
        try {
            Thread.sleep(300);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        this.value = 1;
    }

    private static void init() {
        thread.start();
    }

    public static ThisEscape5 getInstance() {
        //调用私有构造
        ThisEscape5 thisEscape5 = new ThisEscape5();
        //启动线程
        init();
        return thisEscape5;
    }

    public static void main(String[] args) {
        ThisEscape5 instance = ThisEscape5.getInstance();
        System.out.println(instance.value);
    }
}

  改造完成后,新线程的启动总会在构造完成之后,这种发布对象的方式就是安全的,私有构造加上工厂方法,这其实就是 单例模式 的思想,看一下结果:
在这里插入图片描述

对象如何安全发布

  在了解了单例模式之后,如何安全的发布的对象,可以分为四个方面:

  1. 在静态初始化函数中初始化一个对象引用
  2. 将对象的引用保存到volatile类型域或者AtomicReference
  3. 将对象的引用保存到某个正确构造对象的final类型域中
  4. 将对象的引用保存到一个由锁保护的域中

1,2,4点就是单例模式的实现,而final是通过不变来保证对象的发布安全

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值