BT

如何利用碎片时间提升技术认知与能力? 点击获取答案

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

| 作者 郑晔 关注 2 他的粉丝 发布于 2012年6月18日. 估计阅读时间: 5 分钟 | Google、Facebook、Pinterest、阿里、腾讯 等顶尖技术团队的上百个可供参考的架构实例!

诸位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)关注我们,并与我们的编辑和其他读者朋友交流。

评价本文

专业度
风格

您好,朋友!

您需要 注册一个InfoQ账号 或者 才能进行评论。在您完成注册后还需要进行一些设置。

获得来自InfoQ的更多体验。

告诉我们您的想法

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我

JDK的API里面白纸黑字写得清清楚楚不是线程安全的 by Zeng Abrams

这个是设计者有意为之。
如果说丑,如果同步就不丑了?设计者把最终的控制权叫给了使用者。
试想,如果设计成同步的,你要不同步,如何做?如果不是同步的,你还可以自己通过外部同步来实现。

Re: JDK的API里面白纸黑字写得清清楚楚不是线程安全的 by zz zz

丑不丑要看真正调用者是个什么逻辑,什么地方都同步就是过度同步,那才是真正的丑

无状态的终极形式就是过程式或函数式。 by monser corp

由于过程式更类似日常思维,所以会出现更多。这其实是违背oo思想的。很多东西都需要平衡,这里也不例外。

Re: JDK的API里面白纸黑字写得清清楚楚不是线程安全的 by q q

这个是设计者有意为之。
如果说丑,如果同步就不丑了?设计者把最终的控制权叫给了使用者。
试想,如果设计成同步的,你要不同步,如何做?如果不是同步的,你还可以自己通过外部同步来实现。


“无状态”压根就不需要同步,通过方法传递参数,非常适合这种类似工具的方法,SUN也有很多初级工程师啊。。。

缩小变量的作用域 by 苏 龙

说到底还是要缩小变量的作用域

静态方法应保持纯洁性,即无副作用! by 高 翌翔

在函数式编程语言中,如Haskell等,必须保持函数的纯洁性,否则天知道会发生些什么!

在OO中,不仅需要用对象来保持状态,而且还需要一些静态的加工方法,而那些静态加工方法就应保持函数的纯洁性,即无状态、无副作用。

没啥不好 by ge shizan

也没啥不好的,关键看你怎么用了。ThreadLocal就可以解决这种问题。

Re: 没啥不好 by Wong Peter

软件世界充满了需要去权衡的地方,尤其是空间和时间。没有放之四海皆准的原则。一个设计好不好需要看上下文。第一段代码放在线程安全的上下文中就是优雅的代码。

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我

8 讨论

登陆InfoQ,与你最关心的话题互动。


找回密码....

Follow

关注你最喜爱的话题和作者

快速浏览网站内你所感兴趣话题的精选内容。

Like

内容自由定制

选择想要阅读的主题和喜爱的作者定制自己的新闻源。

Notifications

获取更新

设置通知机制以获取内容更新对您而言是否重要

BT