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 构造函数中的可覆盖方法调用逸出 发布(publishing)一个对象的意思是:使对象能够在当前作用域之外的代码中使用。例如,将一个指向该对象的引用保存到其他代…...
Python学习之Image模块图片滤镜效果操作示例
前言 滤镜效果是图像处理中常用的一种技术,可以用来增强图像的视觉效果,实现不同的效果,比如增强对比度、饱和度、色彩等。滤镜效果可以帮助用户快速地调整图像的特性,从而使图像更加适合用户的需求。 Image模块对于图像处理的…...
Grafana 系列-统一展示-5-AWS Cloudwatch 仪表板
系列文章 Grafana 系列文章 👍️强烈推荐 强烈推荐使用 GitHub 上的 monitoringartist/grafana-aws-cloudwatch-dashboards 仪表板。该 repo 有一系列 AWS 资源的仪表板,包括但不限于: EC2EBSAPI GWAutoscalingBillingEKSLambdaLogsRDSS3…...
MySQL---控制流函数、窗口函数(序号函数、开窗聚合函数、分布函数、前后函数、头尾函数、其他函数)
1. 控制流函数 格式 解释 案例 IF(expr,v1,v2) 如果表达式 expr 成立,返回结果 v1;否则,返回结果 v2。 SELECT IF(1 > 0,正确,错误) ->正确 IFNULL(v1,v2) 如果 v1 的值不为 NULL,则返回 v1ÿ…...
一心报国的西工大网安人走出新手村
大二下学期5月5日晚上,西工大长安校区教学西楼,作为一名网安专业本科生,从大一便立志学好网安知识,报效祖国,却苦于没有优秀学习资源,就把这事儿拖到了大二,最近上了一门专业课,如同…...
如何安装oracle的sample schema
首先从如下的地址选择合适的版本进行下载 https://github.com/oracle-samples/db-sample-schemas/releases 如果是rac环境,最好是将这个数据库停掉,然后只启动一个instance,然后再开始安装 [Tue May 09 20:26:34][377951][oraclenshqae01adm…...
ChatGPT :国内免费可用 ChatGPT +Midjourney绘图
前言 ChatGPT(全名:Chat Generative Pre-trained Transformer),美国OpenAI 研发的聊天机器人程序 ,于2022年11月30日发布 。ChatGPT是人工智能技术驱动的自然语言处理工具,它能够通过理解和学习人类的语言来…...
女孩子转数据分析难吗?难在哪里?
对于数据分析,很多人乍一听会觉得没啥技术难度,是个适合女孩子的专业。我们面对很多零基础小白也是用通俗的语言来形容这个专业:一般是通过Excel或者power BI工具对数据进行分析,制作成可视化的报表给领导层,为公司业务…...
基于常用设计模式的业务框架
前言 做开发也有好几年时间了,最近总结和梳理自己在工作中遇到的一些问题,工作中最容易写出BUG的需求就是改造需求了。一个成熟的业务系统是需要经过无数次迭代而成的,也意味着经过很多开发人员之手,最后到你这里,大部…...
ubuntu重启ssh服务
一、开启ssh服务首先需要安装打开ssh服务的库: sudo apt-get install openssh-server 二、检查当前的ssh开启情况: ps -e |grep ssh 三、如果有sshd,则ssh-server已经启动;若仅有agent,则尚未启动; 开启ssh…...
【19】SCI易中期刊推荐——计算机 | 人工智能领域(中科院2区)
💖💖>>>加勒比海带,QQ2479200884<<<💖💖 🍀🍀>>>【YOLO魔法搭配&论文投稿咨询】<<<🍀🍀 ✨✨>>>学习交流 | 温澜潮生 | 合作共赢 | 共同进步<<<✨✨ 📚📚>>>人工智能 | 计算机视觉…...
Vue.js条件、循环语句
文章目录 条件语句v-ifv-elsev-else-ifv-show 循环语句v-for 指令v-for 迭代对象valuevalue ,keyvalue ,key,index v-for 迭代整数 条件语句 v-if 在元素 和 template 中使用 v-if 指令 <div id"app"><p v-if"seen">现在你看到我…...
Go语言学习查缺补漏ing Day4
Go语言学习查缺补漏ing Day4 一、掌握iota的使用 请看下面这段代码: package mainimport "fmt"const (a iota_bc "ReganYue"dd1e iotaf iota )func main() {fmt.Println(a, b, c, d, d1, e, f) }思考一下输出结果会是什么? …...
说服审稿人,只需牢记这 8 大返修套路!
本文作者:雁门飞雪 如果说科研是一场修炼,那么学术界就是江湖,投稿就是作者与审稿人或编辑之间的高手博弈。 在这一轮轮的对决中,有时靠的是实力,有时靠的是技巧,然而只有实力和技巧双加持的作者才能长久立…...
Java 责任链模式详解
责任链模式(Chain of Responsibility Pattern)是一种行为型设计模式,它用于将请求的发送者和接收者解耦,使得多个对象都有机会处理这个请求。在责任链模式中,有一个请求处理链条,每个处理请求的对象都是一个…...
使用MASA全家桶从零开始搭建IoT平台(三)管理设备的连接状态
文章目录 前言分析方案1:遗嘱消息演示遗嘱消息的使用实施流程 方案2:使用WebHook开启WebHook演示Webhook编写代码 前言 获取一个设备的在线和离线状态,是一个很关键的功能。我们对设备下发的控制指令,设备处于在线状态才能及时给我们反馈。这里的在线和…...
我的新书上架了!
talk is cheap,show you my book! 新书《从0开始学ARM》终于在各大平台上架了!! 一、关于本书 1. 本书主要内容 ARM体系架构是目前市面上的主流处理器体系架构,在手机芯片和嵌入式芯片领域,ARM体系架构…...
语言与专业的奇迹:如何利用ChatGPT优化跨国贸易
贸易公司,在进行跨国贸易时,往往需要面对不同国家的甲方或者乙方,在与之沟通的过程中,语言和专业是必须要过的一关,顺畅的交流,往往会带来更好的收益。 今天以“茶”为例,给大家介绍一“知否AI…...
云服务器安装宝塔Linux面板命令脚本大全
阿里云服务器安装宝塔Linux面板,操作系统不同安装命令脚本也不同,支持CentOS、Alibaba Cloud Linux、Ubuntu/Deepin等Linux系统,阿里云服务器网分享阿里云服务器安装宝塔Linux面板命令脚本大全: 云服务器安装宝塔Linux面板命令 …...
zed2i相机中imu内参的标定及外参标定
zed2i中imu内参的标定 参考: https://blog.csdn.net/weixin_42681311/article/details/126109617 https://blog.csdn.net/weixin_43135184/article/details/123444090 值得注意,imu内参的标定其实不是那么重要,大致上给一个值应该影响不大…...
Leetcode 3576. Transform Array to All Equal Elements
Leetcode 3576. Transform Array to All Equal Elements 1. 解题思路2. 代码实现 题目链接:3576. Transform Array to All Equal Elements 1. 解题思路 这一题思路上就是分别考察一下是否能将其转化为全1或者全-1数组即可。 至于每一种情况是否可以达到…...
基于服务器使用 apt 安装、配置 Nginx
🧾 一、查看可安装的 Nginx 版本 首先,你可以运行以下命令查看可用版本: apt-cache madison nginx-core输出示例: nginx-core | 1.18.0-6ubuntu14.6 | http://archive.ubuntu.com/ubuntu focal-updates/main amd64 Packages ng…...
【项目实战】通过多模态+LangGraph实现PPT生成助手
PPT自动生成系统 基于LangGraph的PPT自动生成系统,可以将Markdown文档自动转换为PPT演示文稿。 功能特点 Markdown解析:自动解析Markdown文档结构PPT模板分析:分析PPT模板的布局和风格智能布局决策:匹配内容与合适的PPT布局自动…...
【Web 进阶篇】优雅的接口设计:统一响应、全局异常处理与参数校验
系列回顾: 在上一篇中,我们成功地为应用集成了数据库,并使用 Spring Data JPA 实现了基本的 CRUD API。我们的应用现在能“记忆”数据了!但是,如果你仔细审视那些 API,会发现它们还很“粗糙”:有…...
Linux-07 ubuntu 的 chrome 启动不了
文章目录 问题原因解决步骤一、卸载旧版chrome二、重新安装chorme三、启动不了,报错如下四、启动不了,解决如下 总结 问题原因 在应用中可以看到chrome,但是打不开(说明:原来的ubuntu系统出问题了,这个是备用的硬盘&a…...
【HTTP三个基础问题】
面试官您好!HTTP是超文本传输协议,是互联网上客户端和服务器之间传输超文本数据(比如文字、图片、音频、视频等)的核心协议,当前互联网应用最广泛的版本是HTTP1.1,它基于经典的C/S模型,也就是客…...
[Java恶补day16] 238.除自身以外数组的乘积
给你一个整数数组 nums,返回 数组 answer ,其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。 题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。 请 不要使用除法,且在 O(n) 时间复杂度…...
10-Oracle 23 ai Vector Search 概述和参数
一、Oracle AI Vector Search 概述 企业和个人都在尝试各种AI,使用客户端或是内部自己搭建集成大模型的终端,加速与大型语言模型(LLM)的结合,同时使用检索增强生成(Retrieval Augmented Generation &#…...
Qemu arm操作系统开发环境
使用qemu虚拟arm硬件比较合适。 步骤如下: 安装qemu apt install qemu-system安装aarch64-none-elf-gcc 需要手动下载,下载地址:https://developer.arm.com/-/media/Files/downloads/gnu/13.2.rel1/binrel/arm-gnu-toolchain-13.2.rel1-x…...
解析奥地利 XARION激光超声检测系统:无膜光学麦克风 + 无耦合剂的技术协同优势及多元应用
在工业制造领域,无损检测(NDT)的精度与效率直接影响产品质量与生产安全。奥地利 XARION开发的激光超声精密检测系统,以非接触式光学麦克风技术为核心,打破传统检测瓶颈,为半导体、航空航天、汽车制造等行业提供了高灵敏…...
