前言
多线程编程是java的一个难点,为了提升自己,最近在啃编程大师写的 java concurrency in practice。就在今天,同事在调用我服务时出现了空指针异常...
分析
我刚接收到这个噩耗时第一个想法是:我靠,你会不会用啊,我的服务已经平稳运行几年了,你给我说有问题...当然,大家都是同事的关系,我也就看了一下我的代码。发现出现问题的业务是:
在打印每个交易的消耗时间时,出现了异常。
继续探查。我们把计算消耗时间写成了一个服务,这个服务的职责时,客户通知计算服务类开始时间和结束时间,然后计算服务类负责把时间差算出来。我仔细研究了我在主业务流程中的调用:当接受到请求数据的时候,立马调用了计算服务的设置开始时间的方法,当完成这个请求的时候,立马调用了计算服务的设置结束时间方法,最后调用计算服务的计算时间差的方法。这没一点问题啊。
然后我就探查这个计费服务。业务逻辑很简单,没有问题。这个服务是单例,懒汉加载...我看到在暴露给外部的getInstance方法时没有加同步锁...
我就问我同事你是怎么调用的,他说他是多线程..
bingo,问题找到了。单例模式下,在程序刚开始运行,多线程同时调用服务的getInstance方法,如果不加同步锁,有可能获得多个不同的实例。在一笔交易中,有多次调用计费服务的地方,也就是很有可能多次调用的计费服务压根就不是一个服务。所以这个服务就是线程不安全的,他提供的功能也是不可靠的。
解决方案
很简单,如果是懒汉的话,在getInstance方法加上同步锁。如果不想使用锁,那就使用饿汉模式:即将服务的初始化放在jvm加载类的时候。
感受
多看书是没有错的,这里再次附上大师对线程安全的定义:
A class is thread safe if it behaves correctly when accessed from multiple threads, regardless of the scheduling or
interleaving of the execution of those threads by the runtime environment, and with no additional synchronization or
other coordination on the part of the calling code
我来简陋的翻译一下:
如果一个类在被多线程访问时,保持正确,不管在运行环境中被多个线程交错执行和处理器调度,并且不需要多余的同步或者和其他代码相互配合。