写点什么

代码之丑(十二)-- 无状态方法

  • 2012-06-17
  • 本文字数:1303 字

    阅读完需:约 4 分钟

诸位 Java 程序员,想必大家对 SimpleDateFormat 并不陌生。不过,你是否知道,SimpleDateFormat 不是线程安全的(thread safe)。这意味着,下面的代码是错误的:

复制代码
class Sample {
private static final DateFormat format = new SimpleDateFormat("yyyy.MM.dd");
public String getCurrentDateText() {
return format.format(new Date());
}
}

从功能的角度上看,单独执行这段代码是没有问题的,但放到多线程环境下,因为 SimpleDateFormat 不是线程安全的,这段代码就会出错。所以,要想让这段代码正确,我们只要稍做微调:

复制代码
public class Sample {
public String getCurrentDateText() {
return new SimpleDateFormat("yyyy.MM.dd").format(new Date());
}
}

不知你是否注意到,这里的调整只是由原来的共享 format 这个变量,变成了每次调用这个方法时创建出一个新的 SimpleDateFormat 变量。

作为一个专业程序员,我们当然知道,相比于共享一个变量的开销要比每次创建小。之所以我们必须这么做,是因为 SimpleDateFormat 不是线程安全的。但从 SimpleDateFormat 提供给我们的接口上来看,实在让人看不出它与线程安全有和相干。那接下来,我们就要打开 JDK 的源码,看一下其中的代码之丑。

如果你手头没有 JDK 的源码,这里是个不错的参考。

在 format 方法里,有这样一段代码:

复制代码
calendar.setTime(date);

其中,calendar 是 DateFormat 的 protected 字段。这条语句改变了 calendar,稍后,calendar 还会用到(在 subFormat 方法里),而这就是引发问题的根源。

想象一下,在一个多线程环境下,有两个线程持有了同一个 SimpleDateFormat 的实例,分别调用 format 方法:

  1. 线程 1 调用 format 方法,改变了 calendar 这个字段。
  2. 中断来了。
  3. 线程 2 开始执行,它也改变了 calendar。
  4. 又中断了。
  5. 线程 1 回来了,此时,calendar 已然不是它所设的值,而是走上了线程 2 设计的道路。
  6. BANG!!! 稍微花点时间分析一下 format 的实现,我们便不难发现,用到 calendar,唯一的好处,就是在调用 subFormat 时,少了一个参数,却带来了这许多的问题。其实,只要在这里用一个局部变量,一路传递下去,所有问题都将迎刃而解。

这个问题背后隐藏着一个更为重要的问题:无状态。

无状态方法的好处之一,就是它在各种环境下,都可以安全的调用。衡量一个方法是否是有状态的,就看它是否改动了其它的东西,比如全局变量,比如实例的字段。format 方法在运行过程中改动了 SimpleDateFormat 的 calendar 字段,所以,它是有状态的。

写程序,我们要尽量编写无状态方法。

作者简介

郑晔,ThoughtWorks 公司首席咨询师,拥有十多年企业级软件开发经验,热衷于探索各种程序设计语言在真实软件开发中所能发挥的威力,致力于探寻合理的软件开发方式,加入 ThoughtWorks 公司后,投入到敏捷开发方法的实践之中,为其他公司提供敏捷开发方法方面的咨询服务。他的 blog 是梦想风暴,其微博是 @dreamhead

查看原文:代码之丑(十二)


感谢张凯峰对本文的审校。

给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ )或者腾讯微博( @InfoQ )关注我们,并与我们的编辑和其他读者朋友交流。

2012-06-17 21:307405
用户头像

发布了 22 篇内容, 共 14.8 次阅读, 收获喜欢 49 次。

关注

评论

发布
暂无评论
发现更多内容

微前端之如何拆解React巨石应用 qiankun

SugarTurboS

项目管理 架构 大前端 React

图解 K8S 源码 - Informer 篇

郭旭东

Kubernetes Kubernetes源码

LeetCode题解:94. 二叉树的中序遍历,使用栈,JavaScript,详细注释

Lee Chen

大前端 LeetCode

华为云专家讲述知识图谱构建流程及方法

华为云开发者联盟

华为 数据 知识图谱

OpLog4j

Geek_746da6

第三周作业一

dll

百度人工智能OCR调用调试过程

tuuezzy

WSDM Cup 2020大赛金牌参赛方案全解析

华为云开发者联盟

大数据 搜索 信息

JVM-技术专题-对象的实例化过程

码界西柚

Java JVM

从哲学源头思考自动驾驶网络架构设计

华为云开发者联盟

自动驾驶 架构

Programmatic Navigation using SwiftUI| 使用SwiftUI进行程序化导航

Daniel

医疗AI系统构建(1)one-hot编码

刘旭东

人工智能 学习 医疗AI one-hot

图扑软件联手阿里Lindorm数据库开启工业物联超融合存储模式

许力

IoT AIOT

The story of programmers in famous enterprises.

Marilyn

敏捷开发 快速开发

JVM-技术专题-类加载机制

码界西柚

Java JVM

高难度对话读书笔记—聆听篇

wo是一棵草

来喽,来喽,Python 3.9正式版发布了~~~

华为云开发者联盟

Python 编程

在线教育企业迎来“秋招”大考,数字用户体验成胜负关键手

博睿数据

运维 APM 在线教育 AIOPS 用户体验

苦难过后,终会团聚

hellocj

国外的公司都是如何对待大龄程序员的?在国内该如何应对?

Java架构师迁哥

翻译之深入注释俄罗斯民间故事的语料库,以实现对俄罗斯形式主义理论的机器学习

AI代笔

Lindorm云原生数据库 - 让数字时代IT运维系统“灵动”起来

许力

DevOps APM Data Lake AIOPS

有了TA,领域外企业里的小IT团队,也能轻松搞定大型项目

Marilyn

敏捷开发 快速开发

拜托,学妹,别再问我怎么自学 Java 了!和盘托出

沉默王二

Java 自学编程

个人数字人民币钱包即将亮相

CECBC

央行 数字人民币

JVM-技术专题-Java类文件结构

码界西柚

Java JVM

我和我的智能联接

脑极体

OKR-VUCA时代目标管理利器实践分享

张兆东

一线城市年轻人生活工作实录(程序员篇)

Marilyn

敏捷开发 开发者工具 快速开发

年轻人大企打拼多年,刚升迁便遇巨大阻力难以解决,到底如何才能在职场中幸存?

Marilyn

敏捷开发 快速开发

亚马逊Prime会员日火爆开启一站购全球逾3000万正品商品

爱极客侠

代码之丑(十二)--无状态方法_Java_郑晔_InfoQ精选文章