Java并发编程实践学习笔记(三)——共享对象之发布和异常

目录

1 公共静态变量逸出

2 非私有方法逸出私有变量

3 this引用逸出

4 构造函数中的可覆盖方法调用逸出


        发布(publishing)一个对象的意思是:使对象能够在当前作用域之外的代码中使用。例如,将一个指向该对象的引用保存到其他代码可以访问的地方,或者在某一个非私有的方法中返回该引用,或者将引用传递到其他类的方法中。

        发布内部状态可能会破坏封装性,并使程序难以维持不变性条件。例如,如果在对象构造完成之前就发布该对象,就会破坏线程安全性。 当某个不应该发布的对象被发布时,这种情况就成为逸出(escape)。

        简而言之,发布就是把对象暴露给他人使用,这就是为什么会需要用到封装;逸出就是把不应该发布的对象发布了,比如对象还没完成实例化,就被外界使用了。

1 公共静态变量逸出

        发布对象的最常见方式就是将对象的引用保存到一个公有的静态变量中,任何类和线程都能看见该对象。如下代码所示,initialize方法实例化一个新的HashSet实例,并通过将它存储到knownSecrets引用,从而发布这个实例:

// 3-5 发布一个对象
public static Set<Secret> knownSecrets;
public void initialize() {
      knownSecrets = new HashSet<Secret>();
}

        当发布某个对象时,可能会间接地发布其他对象。如果将一个Secret对象添加到集合knownSecrets中,那么同样会发布这个对象,因为任何代码都可以遍历这个集合,并获得对这个新Secret对象的引用。

2 非私有方法逸出私有变量

        从非私有方法中返回一个引用,也能发布返回的对象。下面的代码发布了包含洲名缩写的数组,而这个数组本应是私有的:

//     3-6   使内部可变状态逸出(不要这样做)
public class UnsafeStates {

    private String[] states = new String[] { "AK", "AL", "LW" };

    public String[] getStates() {
        return states;
    }

    public static void main(String[] args) {
        UnsafeStates us = new UnsafeStates();
        System.out.println(Arrays.toString(us.getStates()));
        us.getStates()[0] = "NY";
        System.out.println(Arrays.toString(us.getStates()));
    }
}

        这样发布states会出现问题,因为任何调用者都能修改这个数组的内容。通过访问对象中的共有方法获取私有变量的值,然后更改内部数据,则导致变量逸出作用域。数组states已经逸出了它所在的作用域,这个本该私有的数据,事实上已经变成共有了。 

        发布一个对象时,该对象的非私有域中引用的所有对象同样会被发布。更一般的,一个已发布的对象中,那些非私有的引用链及方法调用链中的可获得对象也都会被发布。

3 this引用逸出

        最后一种发布对象或其内部状态的机制就是发布一个内部的类实例。当ThisEscape发布内部类EvnetLister时,也隐含地发布了ThisEscape实例本身,因为在这个内部类的实例中也包含了对ThisEscape实例的隐含引用。

 1、EventListener接口

public interface EventListener {
    void onEvent(Object obj);
}

2、EventSource

public class EventSource<T> {

    private final List<T> eventListeners;

    public EventSource() {
        eventListeners = new ArrayList<>();
    }

    public synchronized void registerListener(T eventListener) {
        this.eventListeners.add(eventListener);
        this.notifyAll();
    }

    public synchronized List<T> retrieveListeners() throws InterruptedException {
        List<T> dest = null;
        if (eventListeners.size() <= 0) {
            this.wait();
        }
        dest = new ArrayList<>(eventListeners.size());
        dest.addAll(eventListeners);
        return dest;
    }

}

3、ThisEscape

public class ThisEscape {

    public final int id;
    public final String name;

    public ThisEscape(EventSource source) {
        id = 100;

        // ThisEscape尝试在构造函数中注册一个事件监听器
        source.registerListener(new EventListener() {
            @Override
            public void onEvent(Object obj) {
                System.out.println("id: " + ThisEscape.this.id);
                System.out.println("name: " + ThisEscape.this.name);
            }
        });

        try {
            // 调用sleep模拟其他耗时的初始化操作
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        name = "ThisEscape初始化完成";
    }

    @Override
    public String toString() {
        return "ThisEscape{" +
                "id=" + id +
                ", name='" + name + '\'' +
                '}';
    }
}

       ThisEscape演示了一种重要的逸出特例,this引用在构造时逸出。发布的内部EventListener实例是一个封装的ThisEscape中的实例。但是这个对象只有通过构造器函数返回后,才处于可预言的、稳定的状态,所以从构造器函数内部发布的对象,只是一个未完成构造的对象。

        内部类、匿名内部类都可以访问外部类的对象的域,因为内部类构造的时候,会把外部类的对象this隐式的作为一个参数传递给内部类的构造方法,这个工作是编译器做的,它会给内部类所有的构造方法添加这个参数,所以这个例子的匿名内部类在构造ThisEscape时就把ThisEscape创建的对象隐式的传给匿名内部类了。这样 source就持有ThisEscape的内部类EvenListener,而Evenlistener可能会带出ThisEscape中的保护数据引用,如果此时ThisEscape还未初始化完成,Evenlistener可能会访问到ThisEscape中未完成初始化的数据,因为this引用提前被EventListener实例对象拿到,这就是this引用的逸出。

public class ThisEscapeTest {

    public static void main(String[] args) throws InterruptedException {
        EventSource<EventListener> source = new EventSource<>();
        new Thread(() -> {
            try {
                List<EventListener> listeners = source.retrieveListeners();
                for(EventListener listener : listeners) {
                    listener.onEvent(new Object());
                }
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }).start();
        ThisEscape escape = new ThisEscape(source);
        System.out.println("ThisEscape 构造完成结果:"+escape);
    }
}

        运行结果:

         这个测试案例中,另一个线程在ThisEscape还未完成初始化时,就访问ThisEscape的内部数据了。

        总结这个案例,造成this逸出,一个是在构造函数中创建内部类(EventListener) ,另一个是在构造函数中就把这个内部类给发布了出去(source.registerListener)。那么,对应的解决方法就是:如果要在构造函数中创建内部类,那么就不能在构造函数中将其发布了,应该在构造函数外发布,即等构造函数执行完毕,初始化工作已全部完成,再发布内部类。如果需要在构造函数中注册一个事件监听器或者启动线程,可以使用一个私有的构造函数和一个公共的工厂方法(Factory Method),从而避免不正确的构造过程。

public class SafeListener {
    private final EventListener listener;

    private SafeListener() {
        listener = new EventListener() {
            public void onEvent(Event e) {
                doSomething(e);
            }
        };
    }

    public static SafeListener newInstance(EventSource source) {
        SafeListener safe = new SafeListener();
        source.registerListener(safe.listener);
        return safe;
    }

    void doSomething(Event e) {
    }


}

        如上示例代码所示,注册监听在构造之后执行,保证onEvent()方法在SafeListener的构造之后才能被调用,对象正确初始化后再调用this引用指向的对象的方法修改属性就不是逸出,而是发布。 

        在构造函数过程中使this引用逸出的一个常见错误是,在构造器中启动一个线程。当对象在其构造函数中创建一个线程时,无论是显式创建还是隐式创建,this引用都会被新创建的线程共享。在对象尚未完全构造之前,新的线程就可以看见它。在构造函数中创建线程并没有错误,但最好不要立即启动它,而是通过一个start或initialize方法来启动。

4 构造函数中的可覆盖方法调用逸出

        在构造函数中调用一个可覆盖的实例方法时(既不是private,也不是final的),同样会导致this引用在构造期间逸出。

Base类:

public abstract  class Base {

    Base() {
        System.out.println("Base构造函数");
        // 在构造函数中调用可重写的方法
        overrideMe();
    }

    // 一个可重写的方法
    abstract void overrideMe();
}

子类:

public class Child extends Base{

    final int x;

    Child(int x) {
        System.out.println("Child构造函数");
        this.x = x;
    }

    @Override
    void overrideMe() {
        System.out.println(x);
    }

    public static void main(String[] args) {
        new Child(42);
    }
}

        在子类初始化时,会先调用父类Base的构造函数,而父类的构造函数中调用了可重写的方法,实际上调用的是子类中重载的方法,然而此时子类尚未完成初始化,造成的结果就是尚未完成初始化的父类逸出到子类中。

        运行结果:

        这里,当Base构造函数调用时overrideMe,Child尚未完成初始化final int x,并且该方法获取错误的值,这几乎肯定会导致错误。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/19344.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

InnoDB线程模型

新版本结构演变 MySQL 5.7 版本 将 Undo日志表空间从共享表空间 ibdata 文件中分离出来&#xff0c;可以在安装 MySQL 时由用户自行指定文件大小和数量增加了 temporary 临时表空间&#xff0c;里面存储着临时表或临时查询结果集的数据Buffer Pool 大小可以动态修改&#xff0…

你不知道的自动化?使用自动化测试在项目中创造高业务价值...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 脱离数据支撑谈价…

GRPC - JAVA笔记

GRPC - JAVA笔记 gRPC简介 由google开源的一个高性能的RPc框架&#xff0c;由google内部的Stubby框架演化而来。2015年正式开源。云原生时代的RPC标准&#xff0c;由Go语言开发 gRPC的核心设计思路 网络通信 ------> gRPC 自己封装了网络通信的部分&#xff0c;提供了多种…

VS2022编译libiconv-1.17

需求概述 获得最新版本的windows下可用的libiconv静态库。 解决方案 概述 使用VS2022编译libiconv-1.17。需要对源码手动进行配置。 本文所述的方法同样适用于动态库&#xff0c;并且理论上适用于VS2010~2022所有版本。 如果你不在乎libiconv的版本&#xff0c;可以参考 …

Redis缓存

就先不连接数据库了 我们测试缓存 实体类&#xff1a; Data AllArgsConstructor NoArgsConstructor public class User implements Serializable {private int id;private String name;private String sex;private String addr; } service&#xff1a; Service public…

小家电LED显示驱动多功能语音芯片IC方案 WT2003H4 B002

随着时代的进步&#xff0c;智能家电的普及已经成为了一个趋势。而在智能家电中&#xff0c;LED显示屏也成为了不可或缺的一部分。因此&#xff0c;在小家电的设计中&#xff0c;LED显示驱动芯片的应用也越来越广泛。比如&#xff1a;电饭煲、电磁炉、数字时钟、咖啡机、电磁炉…

java版spring cloud 企业电子招投标采购系统源码之首页设计

随着公司的快速发展&#xff0c;企业人员和经营规模不断壮大&#xff0c;公司对内部招采管理的提升提出了更高的要求。在企业里建立一个公平、公开、公正的采购环境&#xff0c;最大限度控制采购成本至关重要。符合国家电子招投标法律法规及相关规范&#xff0c;以及审计监督要…

“正大杯”第十三届市场调查与分析大赛[省一]经验总结+复盘

目录 1 前期组队 2 队员组成 队长-成员1 应用统计学专业 成员2 化学实验专业 成员3-本人 物联网工程专业 成员4 金融ACCA专业 成员5 应用物理学 总结 3 比赛进度 3月中旬 部分图表的制作 问卷设计与制作 稍微改动主题 问卷相关总结 前期调查部分论文框架 3月…

怎么把webp文件转换为jpg?这几种方法值得学习!

怎么把webp文件转换为jpg&#xff0c;我想这样的问题对于那些和图片打交道不多的人来说确实有些困难吧。在我们要处理这个问题之前&#xff0c;我们先来了解一下图片格式webp吧。要是知道Youtube、Gmail、Google Play 中都可以看到 WebP 的身影&#xff0c;而 Chrome 网上商店甚…

高阶python | 堆栈列表:RPN应用(模拟逆波兰式功能实现)

python版本&#xff1a;3.10 在列表中&#xff0c;append和pop方法有一个特殊的用途。可以在列表上使用这两个方法让列表变成一个堆栈使用。 这就是一个栈&#xff0c;它是先进后出&#xff0c;类似单门轿厢电梯一样的设计&#xff0c;出入口共用 堆栈最有用的应用之一就是做逆…

如何解决请求参数为JSON时,采用IO流读取,只能请求一次的问题?

如何解决请求参数为JSON时&#xff0c;采用IO流读取&#xff0c;只能请求一次的问题&#xff1f; 一、错误演示1. 创建项目&#xff0c;添加所需依赖2. 配置redis环境3. 写一个简单的测试请求4. 写一个拦截器&#xff0c;拦截请求5. WebConfig 注册拦截器6. 测试请求 二、问题解…

VR全景园区:数字化旅游业的新未来

VR全景园区是未来数字化旅游业的一种新兴形式。它利用高清晰度的3D图像和360度全景拍摄技术&#xff0c;将景区中的自然风光、历史文化和人文风情等元素呈现在游客面前。VR全景园区不仅可以为游客提供身临其境的参观体验&#xff0c;还可以有效地推广当地的文化和旅游资源。 【…

调试和优化遗留代码

1. 认识调试器 1.1 含义 一个能让程序运行、暂停、然后对进程的状态进行观测甚至修改的工具。 在日常的开发当中使用非常广泛。(PHP开发者以及前端开发者除外) 1.2 常见的调试器 Go语言的自带的 delve 简写为 “dlv”GNU组织提供的 gdbPHP Xdebug前端浏览器debug 调试 1.3…

English Learning - L3 作业打卡 Lesson2 Day8 2023.5.12 周五

English Learning - L3 作业打卡 Lesson2 Day8 2023.5.12 周五 引言&#x1f349;句1: The color green is natural for trees and grass.成分划分弱读语调 &#x1f349;句2: But it is an unnatural color for humans.成分划分弱读连读语调 &#x1f349;句3: A person who h…

1.SpringBoot基础篇

SpringBoot 文档更新日志 版本更新日期操作描述v1.02021/11/14A基础篇 前言 ​ 很荣幸有机会能以这样的形式和互联网上的各位小伙伴一起学习交流技术课程&#xff0c;这次给大家带来的是Spring家族中比较重要的一门技术课程——SpringBoot。一句话介绍这个技术&#xff0c;…

自学软件测试,从10K到40K的技术路线,也就是这些东西...

如果有一天我从梦中醒来时&#xff0c;发现自己的几年自动化测试工程师经验被抹掉&#xff0c;重新回到了一个小白的状态。我想要重新自学自动化测试&#xff0c;然后找到一份自己满意的测试工作&#xff0c;我想大概只需要6个月的时间就够了&#xff0c;如果比较顺利的话&…

头部企业走入无人区,国产数智化厂商挑大梁

本文转自数智前线 文&#xff5c;石兆 编&#xff5c;游勇 央国企数智化与信创化双重需求叠加&#xff0c;国产厂商挑大梁&#xff0c;助力企业升级数智化底座&#xff0c;实现价值化国产替代。 4月&#xff0c;在北京用友产业园的数智剧院里&#xff0c;近千位来自30个行业…

uniapp实现微信小程序横屏适配问题demo效果(整理)

使用VMIN进行布局 先了解css3的两个属性vmax和vmin vmax 相对于视口的宽度或高度中较大的那个。其中最大的那个被均分为100单位的vmax vmin 相对于视口的宽度或高度中较小的那个。其中最小的那个被均分为100单位的vmin竖屏布局的时候&#xff0c;750rpx就是竖屏布局屏幕的宽度…

Golang每日一练(leetDay0062) BST迭代器、地下城游戏

目录 173. 二叉搜索树迭代器 Binary Search Tree Iterator &#x1f31f;&#x1f31f; 174. 地下城游戏 Dungeon Game &#x1f31f;&#x1f31f;&#x1f31f; &#x1f31f; 每日一练刷题专栏 &#x1f31f; Golang每日一练 专栏 Python每日一练 专栏 C/C每日一练 …

ASEMI代理LT8609AJDDM#WTRPBF原装ADI车规级芯片

编辑&#xff1a;ll ASEMI代理LT8609AJDDM#WTRPBF原装ADI车规级芯片 型号&#xff1a;LT8609AJDDM#WTRPBF 品牌&#xff1a;ADI /亚德诺 封装&#xff1a;DFN-10 批号&#xff1a;2023 安装类型&#xff1a;表面贴装型 引脚数量&#xff1a;10 工作温度:-40C~125C 类型…