当前位置: 首页 > news >正文

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() {@Overridepublic 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 blocke.printStackTrace();}name = "ThisEscape初始化完成";}@Overridepublic 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;}@Overridevoid overrideMe() {System.out.println(x);}public static void main(String[] args) {new Child(42);}
}

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

        运行结果:

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

相关文章:

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

目录 1 公共静态变量逸出 2 非私有方法逸出私有变量 3 this引用逸出 4 构造函数中的可覆盖方法调用逸出 发布&#xff08;publishing&#xff09;一个对象的意思是&#xff1a;使对象能够在当前作用域之外的代码中使用。例如&#xff0c;将一个指向该对象的引用保存到其他代…...

Python学习之Image模块图片滤镜效果操作示例

前言 滤镜效果是图像处理中常用的一种技术&#xff0c;可以用来增强图像的视觉效果&#xff0c;实现不同的效果&#xff0c;比如增强对比度、饱和度、色彩等。滤镜效果可以帮助用户快速地调整图像的特性&#xff0c;从而使图像更加适合用户的需求。 Image模块对于图像处理的…...

Grafana 系列-统一展示-5-AWS Cloudwatch 仪表板

系列文章 Grafana 系列文章 &#x1f44d;️强烈推荐 强烈推荐使用 GitHub 上的 monitoringartist/grafana-aws-cloudwatch-dashboards 仪表板。该 repo 有一系列 AWS 资源的仪表板&#xff0c;包括但不限于&#xff1a; EC2EBSAPI GWAutoscalingBillingEKSLambdaLogsRDSS3…...

MySQL---控制流函数、窗口函数(序号函数、开窗聚合函数、分布函数、前后函数、头尾函数、其他函数)

1. 控制流函数 格式 解释 案例 IF(expr,v1,v2) 如果表达式 expr 成立&#xff0c;返回结果 v1&#xff1b;否则&#xff0c;返回结果 v2。 SELECT IF(1 > 0,正确,错误) ->正确 IFNULL(v1,v2&#xff09; 如果 v1 的值不为 NULL&#xff0c;则返回 v1&#xff…...

一心报国的西工大网安人走出新手村

大二下学期5月5日晚上&#xff0c;西工大长安校区教学西楼&#xff0c;作为一名网安专业本科生&#xff0c;从大一便立志学好网安知识&#xff0c;报效祖国&#xff0c;却苦于没有优秀学习资源&#xff0c;就把这事儿拖到了大二&#xff0c;最近上了一门专业课&#xff0c;如同…...

如何安装oracle的sample schema

首先从如下的地址选择合适的版本进行下载 https://github.com/oracle-samples/db-sample-schemas/releases 如果是rac环境&#xff0c;最好是将这个数据库停掉&#xff0c;然后只启动一个instance&#xff0c;然后再开始安装 [Tue May 09 20:26:34][377951][oraclenshqae01adm…...

ChatGPT :国内免费可用 ChatGPT +Midjourney绘图

前言 ChatGPT&#xff08;全名&#xff1a;Chat Generative Pre-trained Transformer&#xff09;&#xff0c;美国OpenAI 研发的聊天机器人程序 &#xff0c;于2022年11月30日发布 。ChatGPT是人工智能技术驱动的自然语言处理工具&#xff0c;它能够通过理解和学习人类的语言来…...

女孩子转数据分析难吗?难在哪里?

对于数据分析&#xff0c;很多人乍一听会觉得没啥技术难度&#xff0c;是个适合女孩子的专业。我们面对很多零基础小白也是用通俗的语言来形容这个专业&#xff1a;一般是通过Excel或者power BI工具对数据进行分析&#xff0c;制作成可视化的报表给领导层&#xff0c;为公司业务…...

基于常用设计模式的业务框架

前言 做开发也有好几年时间了&#xff0c;最近总结和梳理自己在工作中遇到的一些问题&#xff0c;工作中最容易写出BUG的需求就是改造需求了。一个成熟的业务系统是需要经过无数次迭代而成的&#xff0c;也意味着经过很多开发人员之手&#xff0c;最后到你这里&#xff0c;大部…...

ubuntu重启ssh服务

一、开启ssh服务首先需要安装打开ssh服务的库&#xff1a; sudo apt-get install openssh-server 二、检查当前的ssh开启情况&#xff1a; ps -e |grep ssh 三、如果有sshd&#xff0c;则ssh-server已经启动&#xff1b;若仅有agent&#xff0c;则尚未启动&#xff1b; 开启ssh…...

【19】SCI易中期刊推荐——计算机 | 人工智能领域(中科院2区)

💖💖>>>加勒比海带,QQ2479200884<<<💖💖 🍀🍀>>>【YOLO魔法搭配&论文投稿咨询】<<<🍀🍀 ✨✨>>>学习交流 | 温澜潮生 | 合作共赢 | 共同进步<<<✨✨ 📚📚>>>人工智能 | 计算机视觉…...

Vue.js条件、循环语句

文章目录 条件语句v-ifv-elsev-else-ifv-show 循环语句v-for 指令v-for 迭代对象valuevalue ,keyvalue ,key&#xff0c;index v-for 迭代整数 条件语句 v-if 在元素 和 template 中使用 v-if 指令 <div id"app"><p v-if"seen">现在你看到我…...

Go语言学习查缺补漏ing Day4

Go语言学习查缺补漏ing Day4 一、掌握iota的使用 请看下面这段代码&#xff1a; package mainimport "fmt"const (a iota_bc "ReganYue"dd1e iotaf iota )func main() {fmt.Println(a, b, c, d, d1, e, f) }思考一下输出结果会是什么&#xff1f; …...

说服审稿人,只需牢记这 8 大返修套路!

本文作者&#xff1a;雁门飞雪 如果说科研是一场修炼&#xff0c;那么学术界就是江湖&#xff0c;投稿就是作者与审稿人或编辑之间的高手博弈。 在这一轮轮的对决中&#xff0c;有时靠的是实力&#xff0c;有时靠的是技巧&#xff0c;然而只有实力和技巧双加持的作者才能长久立…...

Java 责任链模式详解

责任链模式&#xff08;Chain of Responsibility Pattern&#xff09;是一种行为型设计模式&#xff0c;它用于将请求的发送者和接收者解耦&#xff0c;使得多个对象都有机会处理这个请求。在责任链模式中&#xff0c;有一个请求处理链条&#xff0c;每个处理请求的对象都是一个…...

使用MASA全家桶从零开始搭建IoT平台(三)管理设备的连接状态

文章目录 前言分析方案1:遗嘱消息演示遗嘱消息的使用实施流程 方案2:使用WebHook开启WebHook演示Webhook编写代码 前言 获取一个设备的在线和离线状态&#xff0c;是一个很关键的功能。我们对设备下发的控制指令&#xff0c;设备处于在线状态才能及时给我们反馈。这里的在线和…...

我的新书上架了!

talk is cheap&#xff0c;show you my book&#xff01; 新书《从0开始学ARM》终于在各大平台上架了&#xff01;&#xff01; 一、关于本书 1. 本书主要内容 ARM体系架构是目前市面上的主流处理器体系架构&#xff0c;在手机芯片和嵌入式芯片领域&#xff0c;ARM体系架构…...

语言与专业的奇迹:如何利用ChatGPT优化跨国贸易

贸易公司&#xff0c;在进行跨国贸易时&#xff0c;往往需要面对不同国家的甲方或者乙方&#xff0c;在与之沟通的过程中&#xff0c;语言和专业是必须要过的一关&#xff0c;顺畅的交流&#xff0c;往往会带来更好的收益。 今天以“茶”为例&#xff0c;给大家介绍一“知否AI…...

云服务器安装宝塔Linux面板命令脚本大全

阿里云服务器安装宝塔Linux面板&#xff0c;操作系统不同安装命令脚本也不同&#xff0c;支持CentOS、Alibaba Cloud Linux、Ubuntu/Deepin等Linux系统&#xff0c;阿里云服务器网分享阿里云服务器安装宝塔Linux面板命令脚本大全&#xff1a; 云服务器安装宝塔Linux面板命令 …...

zed2i相机中imu内参的标定及外参标定

zed2i中imu内参的标定 参考&#xff1a; https://blog.csdn.net/weixin_42681311/article/details/126109617 https://blog.csdn.net/weixin_43135184/article/details/123444090 值得注意&#xff0c;imu内参的标定其实不是那么重要&#xff0c;大致上给一个值应该影响不大…...

国防科技大学计算机基础课程笔记02信息编码

1.机内码和国标码 国标码就是我们非常熟悉的这个GB2312,但是因为都是16进制&#xff0c;因此这个了16进制的数据既可以翻译成为这个机器码&#xff0c;也可以翻译成为这个国标码&#xff0c;所以这个时候很容易会出现这个歧义的情况&#xff1b; 因此&#xff0c;我们的这个国…...

19c补丁后oracle属主变化,导致不能识别磁盘组

补丁后服务器重启&#xff0c;数据库再次无法启动 ORA01017: invalid username/password; logon denied Oracle 19c 在打上 19.23 或以上补丁版本后&#xff0c;存在与用户组权限相关的问题。具体表现为&#xff0c;Oracle 实例的运行用户&#xff08;oracle&#xff09;和集…...

渗透实战PortSwigger靶场-XSS Lab 14:大多数标签和属性被阻止

<script>标签被拦截 我们需要把全部可用的 tag 和 event 进行暴力破解 XSS cheat sheet&#xff1a; https://portswigger.net/web-security/cross-site-scripting/cheat-sheet 通过爆破发现body可以用 再把全部 events 放进去爆破 这些 event 全部可用 <body onres…...

聊一聊接口测试的意义有哪些?

目录 一、隔离性 & 早期测试 二、保障系统集成质量 三、验证业务逻辑的核心层 四、提升测试效率与覆盖度 五、系统稳定性的守护者 六、驱动团队协作与契约管理 七、性能与扩展性的前置评估 八、持续交付的核心支撑 接口测试的意义可以从四个维度展开&#xff0c;首…...

Linux --进程控制

本文从以下五个方面来初步认识进程控制&#xff1a; 目录 进程创建 进程终止 进程等待 进程替换 模拟实现一个微型shell 进程创建 在Linux系统中我们可以在一个进程使用系统调用fork()来创建子进程&#xff0c;创建出来的进程就是子进程&#xff0c;原来的进程为父进程。…...

SAP学习笔记 - 开发26 - 前端Fiori开发 OData V2 和 V4 的差异 (Deepseek整理)

上一章用到了V2 的概念&#xff0c;其实 Fiori当中还有 V4&#xff0c;咱们这一章来总结一下 V2 和 V4。 SAP学习笔记 - 开发25 - 前端Fiori开发 Remote OData Service(使用远端Odata服务)&#xff0c;代理中间件&#xff08;ui5-middleware-simpleproxy&#xff09;-CSDN博客…...

【JVM面试篇】高频八股汇总——类加载和类加载器

目录 1. 讲一下类加载过程&#xff1f; 2. Java创建对象的过程&#xff1f; 3. 对象的生命周期&#xff1f; 4. 类加载器有哪些&#xff1f; 5. 双亲委派模型的作用&#xff08;好处&#xff09;&#xff1f; 6. 讲一下类的加载和双亲委派原则&#xff1f; 7. 双亲委派模…...

MySQL 部分重点知识篇

一、数据库对象 1. 主键 定义 &#xff1a;主键是用于唯一标识表中每一行记录的字段或字段组合。它具有唯一性和非空性特点。 作用 &#xff1a;确保数据的完整性&#xff0c;便于数据的查询和管理。 示例 &#xff1a;在学生信息表中&#xff0c;学号可以作为主键&#xff…...

基于PHP的连锁酒店管理系统

有需要请加文章底部Q哦 可远程调试 基于PHP的连锁酒店管理系统 一 介绍 连锁酒店管理系统基于原生PHP开发&#xff0c;数据库mysql&#xff0c;前端bootstrap。系统角色分为用户和管理员。 技术栈 phpmysqlbootstrapphpstudyvscode 二 功能 用户 1 注册/登录/注销 2 个人中…...

Proxmox Mail Gateway安装指南:从零开始配置高效邮件过滤系统

&#x1f49d;&#x1f49d;&#x1f49d;欢迎莅临我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐&#xff1a;「storms…...