Java发布和溢出简述

前言

回顾《Java并发编程实战》,这里附上一些总结和小案例加深理解。这里重点是对溢出的阐释。

前置知识

发布

发布一个对象,也就是使得其他代码块能够取得并使用该对象。最常见的发布方式就是将对象存储到公共静态域中。像Java Web中consts目录下的public static类型的变量就可以看作是对这些对象的发布。

溢出

简述

由于不当的发布,就会引起溢出。如上述将对象的存储到公共静态域中,那么任何类和线程都能看见这个域,在这个过程中就可能发生逃逸——所有该发布的对象中所能获取的数据都变成公有了。

类型

  • 对象中原本不该暴露的、私有的数据,因为该对象的发布,导致其他类或者线程能够通过该对象获取到其中的私有数据,那么这些私有数据就等于是间接的溢出了。
  • 类的构造函数在构造对象时,尚未完全构造,就已经隐式地将自身(this)引用暴露出去了,导致了运行过程中出现了未知错误。这里有两种形式,具体看下面代码。

案例

环境

Maven3.X、JDK17

发布

这里展示最简单的一个发布案例,现在的URL就是所有的类和线程都能进行URL的使用了。

public class UrlConsts {
    public static final String URL = "www.baidu.com";
}

溢出

溢出类型1——未完全初始化就企图获取该对象中数据

这里ThisEscape 构造过程中,隐含的就把this引用给了CustomizeListener ,如果线程执行过程中,val在doSomething 前尚未初始化,打印出来的val结果就是0。

public class EscapeTest {
    @Test
    public void test() {
        new MethodEscapeTest ("qyl");
    }
}

class ThisEscape {
    private int val;

    public ThisEscape() {
        // 构造还未完成就已经发布了this引用,导致val还未初始化就被CustomizeListener调用了打印val的方法。
        new CustomizeListener ( ) {
            @Override
            public void run() {
                doSomething ( );
            }
        }.run ( );
        val = 10;
    }

    // 溢出1,val大概率是0,属于未完成初始化就this引用溢出了
    public void doSomething() {
        System.out.println (val);
    }
}

class CustomizeListener implements Runnable {

    @Override
    public void run() {}
}

执行结果如下:
在这里插入图片描述
解决方案:延迟线程的启动,通过工厂模式来解决。

public class EscapeTest {
    @Test
    public void test2(){
        CustomizeListener listener = ThisEscape2.newInstance ( );
        listener.run ();
    }
}


class ThisEscape2{
    private int val;
    private final CustomizeListener listener;
    private ThisEscape2() {
        listener = new CustomizeListener (){
            @Override
            public void run() {
                System.out.println (val);
            }
        };
        this.val = 10;
    }

    public static CustomizeListener newInstance(){
        ThisEscape2 escape2 = new ThisEscape2 ( );
        return escape2.listener;
    }
}

class CustomizeListener implements Runnable {

    @Override
    public void run() {
        System.out.println ("hello");
    }
}

运行结果如下:
在这里插入图片描述

溢出类型2——在构造函数中调用非private和final的方法

在初始化子类的时候,隐含着先对父类无参构造函数的调用,而父类的构造函数又调用了methodEscape ,该方法被子类重写了,所以会去执行子类重写后的方法,那么很明显name是未进行初始化的,所以导致name这里出现了问题,始终是null,通过private和final可以解决这个问题的出现。

public class EscapeTest {
    @Test
    public void test() {
        new MethodEscapeTest ("qyl");
    }
}

class ThisEscape {
    public ThisEscape() {
        System.out.println ("invoke father's constructor");

        // 构造函数中调用非 final 和 private的方法,导致溢出
        methodEscape ( );
        // private 来解决上述问题
        methodEscapeSolution1();
        // final 解决上述问题
        methodEscapeSolution2();
    }

    // 溢出2. 方法可能被重写,那么可能this引用溢出,导致后面的方法调用有问题
    public void methodEscape() {
        System.out.println ("call father's method");
    }

    // 正常功能 通过private解决构造函数中调用方法出现this引用溢出
    private void methodEscapeSolution1(){
        System.out.println ("call father's method1");
    }
    // 正常功能 通过 final 解决构造函数中调用方法出现this引用溢出
    public final void methodEscapeSolution2(){
        System.out.println ("call father's method2");
    }
}
class MethodEscapeTest extends ThisEscape {
    private final String name;

    public MethodEscapeTest(String name) {
        this.name = name;
        System.out.println ("invoke son's constructor");
    }

    @Override
    public void methodEscape() {
        System.out.println ("son's name ----> " + name);
    }

}

该代码块执行结果如下:
在这里插入图片描述
由于修正后的method1、method2子类无法重写所以不会产生问题。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值