50万奖金+官方证书,深圳国际金融科技大赛正式启动,点击报名 了解详情
写点什么

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

  • 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:307319
用户头像

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

关注

评论

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

和 if else说再见,SpringBoot 这样做参数校验才足够优雅!

Java你猿哥

Java spring Spring Boot ssm if-else

华为云数据灾备方案,做好企业数据的坚硬后盾

YG科技

Qz学算法-数据结构篇(稀疏数组、队列)

浅辄

数据结构 队列 稀疏矩阵 三周年连更

技术探讨 | YMatrix 如何将 TPC-H 性能提升 10 倍?

YMatrix 超融合数据库

数据库 开源数据库 性能提升 超融合数据库

全栈开发实战|​人事管理系统的设计与实现(Spring Boot + Vue 3 + MyBatis)

TiAmo

Spring Boot mybatis Vue 3 全栈开发 三周年连更

音视频开发_获取媒体文件的详细信息

DS小龙哥

三周年连更

华为云数据灾备,满足企业数据安全及业务连续性需求

YG科技

容易忽视的细节:Log4j 配置导致的零点接口严重超时

vivo互联网技术

问题排查 接口超时 线程block

企业微信接入系列-上传附件资源

六月的雨在InfoQ

企业微信 三周年连更 企业微信接入 企微上传附件

Java 依赖注入(DI)

HoneyMoose

跨平台应用开发进阶(五十二):安全合规之Android APP完整性校验机制探究

No Silver Bullet

android 安全合规 跨平台应用开发 三周年连更 APP完整性

《一文带你看懂:如何进行一次高质量CR》

后台技术汇

代码评审 三周年连更

某程序员:被裁了要求公司足额补缴全部公积金,一次补二十多万!

Java你猿哥

Java 程序员 SSM框架

Unity常用生命周期函数解析 - 超级详细,不服来辩

陈言必行

Unity 三周年连更

什么是 API 接口测试

AREX 中文社区

测试 自动化测试 接口测试 回归测试

右键助手:MouseBoost PRO mac激活版

真大的脸盆

Mac Mac 软件 鼠标增强工具 鼠标辅助软件

Spring Boot过滤器实现项目内接口过滤

Java你猿哥

Java Spring Boot SSM框架

Spring Boot如何使用Undertow容器?超级详细,建议收藏

bug菌

Spring Boot 三周年连更 Undertow

J2EE,Java EE,Jakarta EE 命名之间的恩恩怨怨

HoneyMoose

【数据中台商业化】数据中台微前端实践

京东科技开发者

数据中台 微前端 iframe 微前端框架

什么是前端开发领域的 Cumulative Layout Shift 问题

汪子熙

typescript 前端开发 angular web socket 三周年连更

从原理聊JVM(一):染色标记和垃圾回收算法

京东科技开发者

Java JVM 三色标记 垃圾回收器 企业号 4 月 PK 榜

薪资结构重铸: Zebec将业务范围扩大到Web2薪资管理领域

西柚子

[杂谈]谷歌浏览器 XSwitch 插件 - 解决本地请求转发

alexgaoyh

浏览器 插件 请求转发 本地请求 xswitch

Android事件分发-基础原理和场景分析

京东科技开发者

android Activity View 企业号 4 月 PK 榜 ViewGroup

直播预告 | 时序数据处理的云端利器:TDengine Cloud 详解与演示

TDengine

tdengine 时序数据库 云服务

从原理聊JVM(一):染色标记和垃圾回收算法

小小怪下士

Java 程序员 后端 JVM

华为云数据灾备解决方案,助力企业守好安全防线

YG科技

有没有那么一瞬间,你也曾有过“失业焦虑”呢?| 社区征文

架构精进之路

技术创新 职业发展 三周年征文 三周年连更

求你看完再去面试!涨薪必备分布式事务小抄,狂怼面试官

程序知音

Java 分布式 后端 java架构 Java进阶

行业分析| 新的学习方式——在线自习室

anyRTC开发者

音视频 在线教育 视频直播 直播连麦 在线自习室

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