产品战略专家梁宁确认出席AICon北京站,分享AI时代下的商业逻辑与产品需求 了解详情
写点什么

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

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

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

关注

评论

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

2023-02-12:给定正数N,表示用户数量,用户编号从0~N-1, 给定正数M,表示实验数量,实验编号从0~M-1, 给定长度为N的二维数组A, A[i] = { a, b, c }表示,用户i报

福大大架构师每日一题

算法 rust 福大大

架构实战营-模块一作业

🐢先生

架构实战营

1行Python代码去除图片水印,网友:干干净净!

程序员晚枫

Python GitHub 开源 去水印 自动化办公

一个容器,但是一整个k8s集群

newbe36524

C# Docker Kubernetes

Python 发展趋势:与 Rust 深度融合、更易于编写 Web 应用

Python猫

Python

ChatGPT十问十答 | 图解ChatGPT

涛哥 数字产品和业务架构

人工智能 ChatGPT

极客时间架构训练营模块七-王者荣耀商城异地多活架构设计

张Dave

New Bing 内测,革搜索的命

冯骐

openai GPT ChatGPT New Bing 对话模型

C++ 智能指针(一) std::auto_ptr

orbitgw

c++ C++ STL

KMP算法详解

javaadu

数据结构 字符串 KMP

为什么用元空间替代永久代?

王磊

java面试

springboot自动配置原理

喝水不抬头

软件测试/测试开发 | Web测试方法与技术之CSS讲解

测试人

软件测试 自动化测试 测试开发 Web自动化测试 web测试

NodeJS 实战系列:DevOps 尚未解决的问题

光毅

DevOps nodejs

架构训练营 - 模块四作业

Sam

架构实战营

如何做总架构师

agnostic

总架构

Shell分支语句

圆弧

分支 条件 shell脚本

软件测试/测试开发 | Web自动化之Selenium安装

测试人

软件测试 自动化测试 测试开发 Web自动化测试 selenium

软件测试/测试开发 | Web自动化之显式等待与隐式等待

测试人

软件测试 自动化测试 测试开发 web测试 web自动化

2023我的前端面试小结

loveX001

JavaScript

promise执行顺序面试题令我头秃,你能作对几道

loveX001

JavaScript

软件测试/测试开发 | SeleniumIDE用例录制

测试人

软件测试 自动化测试 测试开发 Web自动化测试 selenium

FL Studio2023最新版本音乐编曲制作软件

茶色酒

FL Studio2023

为什么补码是取反加1?

Dinfan

软件测试/测试开发 | Web测试方法与技术实战演练

测试人

软件测试 自动化测试 测试开发 Web自动化测试

写给go开发者的gRPC教程-拦截器

凉凉的知识库

Go 微服务 gRPC http2

ThreadLocal源码分析及避坑指南

喝水不抬头

大家心心念念的RocketMQ5.x入门手册来喽

中间件兴趣圈

RocketMQ rocketmq5

软件测试/测试开发 | Selenium 测试用例编写

测试人

软件测试 自动化测试 测试开发 Web自动化测试 selenium

被流量和热度裹挟,自媒体行业必须坚守职业道德

石头IT视角

三次握手与四次挥的问题,怎么回答?

loveX001

JavaScript

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