1

ThreadLocal是个很爽的东西,线程安全,能当全局变量来用(别!)。

上一篇末尾提到ThreadLocal的妙用,这东西确实在框架实现中很常用。不过一定要小心啊。

先告诉大家一个安全秘诀:try-finally大法,百战百胜!(一定要在finally里清空ThreadLocal)

我职业生涯遇到最棘手的并发bug都是ThreadLocal造成的,称之为ThreadLocal污染问题。

第一家公司,使用Seam框架(老技术,现代人可以理解为类似Spring MVC),Seam对每个请求都套上Filter,进入时把context写入ThreadLocal,返回或抛Exception(注意)时清理ThreadLocal。而我们用了很多的库,有的库会抛Error,用catch(Exception e)是抓不住的。这就导致有时ThreadLocal没有被清掉,而服务器用的是线程池,线程会复用啊,那下次请求是不是可能读到错误的context呢?听起来好严重啊!

倒也不会,Seam遇到下一个请求又会把新的context写入ThreadLocal,覆盖旧值,就算旧值没释放也不要紧。然而!我们的系统有的模块没有用Seam,而我们有个内部框架,为了兼容性,会检测当前线程是否存在Seam context,如果存在,就从context取对象,如果不存在,就另寻蹊径。有的Filter挂在Seam Filter前面,如果那个Filter调到内部框架,就会先检测当前线程是否存在Seam context,就读到上次的context了。

这样就会出现一些诡异的运行结果,理论上污染会逐渐扩大,直到服务器重启才恢复。更诡异的是如果被污染的线程下次遇到了Seam Filter,覆盖旧值,就又恢复正常了,让人抓不到。

最后当然是诊断并解决了这个问题啦(只能靠推理o(╯□╰)o)。我顺便看了Spring MVC的代码,是用了try-finally大法的,经得起考验。

第二家公司,某次引入一个设计,也用了ThreadLocal来传递上下文信息,有的地方没能清掉ThreadLocal。于是诡异bug冒出来,百分之一概率出现各种稀奇古怪的运行结果,看不出规律,就是怪。所以大家还是能看到一点规律的——那就是没有规律!

现在大家应该明白要怎么对待ThreadLocal了吧。


sorra
841 声望78 粉丝