使用ThreadLocal

之前和同事讨论过一个日期格式化的问题,程序比较简单,大体是这样:

同事并不喜欢这个代码。原因是每调用一次format方法都会创建一个SimpleDateFormat对象。虽然我一再强调SimpleDateFormat对象是方法内的,即生即灭,不会导致明显的内存或性能上的问题。但是同事还是打算尝试一下。一段时间后我看到了下面的代码:

他使用了ThreadLocal。关于ThreadLocal,官方文档的说法如下:

该类提供了线程局部 (thread-local) 变量。这些变量不同于它们的普通对应物,因为访问某个变量(通过其 get 或 set 方法)的每个线程都有自己的局部变量,它独立于变量的初始化副本。ThreadLocal 实例通常是类中的 private static 字段,它们希望将状态与某一个线程(例如,用户 ID 或事务 ID)相关联。

文档的说法有些令人费解,所以这里把英文原文也贴出来:

This class provides thread-local variables.  These variables differ from their normal counterparts in that each thread that accesses one (via its get or set method) has its own, independently initialized copy of the variable.  ThreadLocal instances are typically private static fields in classes that wish to associate state with a thread (e.g., a user ID or Transaction ID).

简而言之,ThreadLocal的作用就是为每个线程提供一个线程内的局部变量来让该线程使用。这个局部变量的状态不应该受到其他线程的影响,从而在某种程度上实现了线程隔离。

也许我们需要换一个更简单直观的程序来看一下:

在这个ThreadLocalTest类中,我们创建了一个ThreadLocal变量,通过这个变量为每个线程创建了一个值为当前时间戳的字符串。在程序中启动了3个线程来测试每个是否线程都会得到一个唯一的字符串变量。另外,在每个线程中都会重复调用线程内部的字符串变量来测试该变量是否发生了变化。

执行结果如下图:

执行结果中的第一列是线程ID,第二列则是在不同时间输出的线程内部字符串变量。可以看到不同线程对应的字符串变量是不同的,一个线程在不同时间使用的内部字符串变量是相同的。

回到一开始讨论的日期工具类上。通过使用ThreadLocal我的同事避免了每次调用format方法都要创建一个SimpleDateFormat对象的问题,减少了对象创建回收的过程。同时因为每个线程内的SimpleDateFormat实例都是线程隔离的,也就是说这个线程内部的SimpleDateFormat实例每次只会被这一个线程所使用,所以虽然SimpleDateFormat类是非线程安全的,在使用中也不会出现问题。

了解了使用ThreadLocal的优势后,又想到了另一个问题:在使用ThreadLocal时每创建一个线程就会为这个线程创建一个内部实例,那么在无限次创建线程的时候,是否会因为ThreadLocal创建的内部实例过多导致内存泄漏?或者换个角度考虑:线程运行停止后,ThreadLocal是如何回收该线程的内部实例的。

这个问题我需要想一想。想明白了换篇文儿继续说。

#####

发表评论