我经常阅读有关“如果对象是不可变的,则它是线程安全的”的文章。 实际上,我从未找到过一篇让我相信不变的意味着线程安全的文章。 即使是Brian Goetz的Java Concurrency in Practice一书中关于不变性的一本书也没有完全令我满意。 在这本书中,我们可以在一个框架中逐字阅读: 不可变对象始终是线程安全的 。 我认为这句话值得更多解释。

因此,我将尝试定义不变性及其与线程安全性的关系。

定义 不变性

我的定义是“不可变的对象是在构造之后状态不会改变的对象”。 我故意含糊其词,因为没有人真正同意确切的定义。

线程安全

您可以在Internet上找到许多不同的“线程安全”定义。 定义它实际上非常棘手。 我会说线程安全代码是在多线程环境中具有预期行为的代码。 我让您定义“预期行为”…

字符串示例

让我们看一下String的代码(实际上只是一部分代码……):

public class String {private final char value[];/** Cache the hash code for the string */private int hash; // Default to 0public String(char[] value) {this.value = Arrays.copyOf(value, value.length);}public int hashCode() {int h = hash;if (h == 0 && value.length > 0) {char val[] = value;for (int i = 0; i < value.length; i++) {h = 31 * h + val[i];}hash = h;}return h;}
}

String被认为是不可变的。 看一下它的实现,我们可以推断出一件事:不可变的对象可以更改其内部状态(在这种情况下,是延迟加载的哈希码),只要它在外部不可见即可。

现在,我将以一种非线程安全的方式重写hashcode方法:

public int hashCode() {if (hash == 0 && value.length > 0) {char val[] = value;for (int i = 0; i < value.length; i++) {hash = 31 * hash + val[i];}}return hash;}

如您所见,我删除了局部变量h并直接影响了变量hash 。 此实现不是线程安全的! 如果多个线程同时调用hashcode ,则每个线程的返回值可能不同。 问题是,这堂课是一成不变的吗? 由于两个不同的线程可以看到不同的哈希码,因此从外部角度来看,我们具有状态更改,因此它不是不可变的。

我们可以得出这样的结论: String是不可变的, 因为它是线程安全的,而不是相反的。 所以……说“做一些不可变的对象,它是线程安全的!”有什么意义? 但是请注意,您必须使不可变对象具有线程安全性!”

ImmutableSimpleDateFormat示例

在下面,我写了一个类似于SimpleDateFormat的类。

public class VerySimpleDateFormat {private final DateFormat formatter = SimpleDateFormat.getDateInstance(SimpleDateFormat.SHORT);public String format(Date d){return formatter.format(d);}
}

该代码不是线程安全的,因为SimpleDateFormat.format不是。

这个对象是不变的吗? 好问题! 我们已尽力使所有字段均不可修改,我们不使用任何设置方法或任何建议对象状态将改变的方法。 实际上,内部SimpleDateFormat更改其状态,这就是它不安全线程的原因。 由于对象图中的某些内容发生了变化,因此即使它看起来像它也不是不变的。问题甚至不是SimpleDateFormat更改其内部状态,而是它以一种非线程安全的方式进行操作。

总结这个例子,创建一个不可变的类并不容易。 最后一个关键字还不够,您必须确保对象的对象字段不会更改其状态,这有时是不可能的。

不可变的对象可以具有非线程安全的方法(没有魔术!)

让我们看一下下面的代码。

public class HelloAppender {private final String greeting;public HelloAppender(String name) {this.greeting = 'hello ' + name + '!\n';}public void appendTo(Appendable app) throws IOException {app.append(greeting);}
}

HelloAppender类绝对是不可变的。 方法appendTo接受Appendable 。 由于Appendable不能保证是线程安全的(例如StringBuilder ),因此追加到此Appendable会在多线程环境中引起问题。

结论

在某些情况下,创建不可变对象绝对是一个好习惯,并且对创建线程安全代码有很大帮助。 但是,当我到处阅读时,这使我感到困扰。 不可变对象是线程安全的 ,显示为公理。 我明白了这一点,但是我认为对此进行一点思考总是很有益的,以便理解导致非线程安全代码的原因。

感谢Jose的评论,在本文的结尾我得出了不同的结论。 这都是关于不可变的定义。 需要澄清!

如果满足以下条件,则对象是不可变的:

  • 它的所有字段在使用之前都已初始化(这意味着您可以进行延迟初始化)
  • 字段的状态在初始化后不会更改(不更改表示对象图不会更改,即使子级的内部状态也是如此)

除非对象必须处理非线程安全的对象,否则不可变对象将始终是线程安全的。

参考: 不变性真的意味着线程安全吗? 从我们的JCG合作伙伴 Tibo Delor在InvalidCodeException博客中获得。

翻译自: https://www.javacodegeeks.com/2012/09/does-immutability-really-means-thread.html

不变性真的意味着线程安全吗?相关推荐

  1. 业务的可变性和不可变性分析_不可变性真的意味着线程安全吗?

    业务的可变性和不可变性分析 我经常阅读有关"如果对象是不可变的,则它是线程安全的"的文章. 实际上,我从未找到过一篇使我相信不可变意味着线程安全的文章. 即使是Brian Goet ...

  2. 2万字,看完这篇才敢说自己真的懂线程池!

    前言 线程池可以说是 Java 进阶必备的知识点了,也是面试中必备的考点,可能不少人看了一些文章后能对线程池工作原理说上一二,但这还远远不够,如果碰到比较有经验的面试官再继续追问,很可能会被吊打,考虑 ...

  3. postgres 支持的线程数_线程池被打满了怎么处理呢,你是否真的了解线程池?

    0.前言 线程池,顾名思义就是线程的池子,在每次需要取线程去执行任务的时候,没必要每次都创建新线程执行,线程池就是起着维护线程的作用,当有任务的时候就取出一个线程执行,如果任务执行完成则把线程放回到池 ...

  4. 多个线程访问统一对象的不同方法_分析| 你未必真的了解线程安全,别骗自己,来看下怎么实现线程安全...

    世界那么大,谢谢你来看我!!关注我你就是个网络.电脑.手机小达人 什么是进程? 电脑中时会有很多单独运行的程序,每个程序有一个独立的进程,而进程之间是相互独立存在的.比如下图中的QQ.酷狗播放器.电脑 ...

  5. 10问10答:你真的了解线程池吗?

    简介: <Java开发手册>中强调,线程资源必须通过线程池提供,而创建线程池必须使用ThreadPoolExecutor.手册主要强调利用线程池避免两个问题,一是线程过渡切换,二是避免请求 ...

  6. 你真的懂线程同步么?

    前言:学进程时,学习的重点应该进程间通信,而学习线程时,重点就应该是线程同步了.想过为什么?fork创建子进程之后,子进程有自己的独立地址空间和PCB,想和父进程或其它进程通信,就需要各种通信方式,例 ...

  7. ftl不存在为真_这个验证贝尔不等式的实验的真实性如何?是否真的意味着量子纠缠的发生是超光速的?...

    然而,私以为'现象'并'不支持'我们有如此好的想象力.'现象'是上帝给的,'抽象'是人的工作. 从贝尔不等式类实验的'现象',我们能比较可靠的作如下'抽象',超过这个界限的'抽象',个人以为并不保险: ...

  8. 每一次离别真的意味着重逢么

    25号晚上回到家里,女儿最为开心了,我知道的,这个女儿象极了我小时候,我依然记得我小时候也对爸爸极其依赖.每天在路上等爸爸回家,如今我总不能在家陪伴我的家人. 28号又要从家里回北京了,我看见女儿眼睛 ...

  9. 你真的会停止线程吗?

    点击上方 好好学java ,选择 星标 公众号 重磅资讯.干货,第一时间送达 今日推荐:牛人 20000 字的 Spring Cloud 总结,太硬核了~ 作者:Greta Wang 出处:http: ...

最新文章

  1. mysql 分组取最新的一条记录(整条记录)
  2. 建造者模式(Builder Pattern)简单随笔
  3. Python开发者的完美终端工具
  4. const_cast的使用:添加或去掉const、常量折叠
  5. Navigation bar - remove recent object
  6. P1160-队列安排【链表】
  7. MySQL 客户端命令
  8. 利用mitmproxy进行抓包
  9. 《Python编程快速上手——让繁琐工作自动化》——2.8 导入模块
  10. redis memcached 可视化管理及监控工具 TreeNMS
  11. jconsole使用
  12. 目前云存储,主要面临哪些问题?
  13. 用DW编写网页--个人简历
  14. Gradle's dependency cache may be corrupt (this sometimes occurs after a net错误解决
  15. 网络调试助手连接远程服务器
  16. 深度学习核心技术精讲100篇(四十五)-商业DMP数据管理平台的架构与实践
  17. [人生故事] -- 花朵静悄悄地开放
  18. IE浏览器提示无法安全地连接到此页面,这可能是因为该站点使用过期的或不安全的 TLS 安全设置.该如何解决?
  19. 机器人杆长标定_一种SCARA机器人标定方法与流程
  20. 2023最新SSM计算机毕业设计选题大全(附源码+LW)之java哈尔滨市道路信息管理系统3msu1

热门文章

  1. jvm(3)-垃圾收集器与内存分配策略
  2. javafx 调用java_Java,JavaFX的流畅设计风格拨动开关
  3. java类似sizeof_如何用Java编写类似C的Sizeof函数
  4. 设计模式 原型模式_创新设计模式:原型模式
  5. java登录界面命令_Java命令行界面(第18部分):JCLAP
  6. Java中的CopyOnWriteArrayList
  7. JDK 11上的JavaFX
  8. Java命令行界面(第30部分):观察
  9. 使用正确的垃圾收集器将Java内存使用量降至最低
  10. 使用Speedion 3.0.17或更高版本轻松从事务中返回值