Java 中的秘会厅 ThreadLocal 你了解吗?
1.ThreadLocal 概览
在 Java 的编程魔法世界的 java.lang 包中有个 ThreadLocal 结构,这使我们能够为当前线程单独存储数据,并将其简单地封装在一个特殊类型的对象中。
2.ThreadLocal API
TheadLocal 结构允许我们存储只能由特定线程访问的数据。 比如,我们希望将一个整数值与特定线程捆绑在一起:
接下来,当我们要使用线程中的这个值时,只需调用 get() 或 set() 方法即可。简单地说,我们可以想象 ThreadLocal 将数据存储在一个以线程为键的映射中。因此,当我们调用 threadLocalValue 上的 get() 方法时,我们将得到请求线程的整数值:
要从 ThreadLocal 中删除值,我们可以调用 remove() 方法:
要了解如何正确使用 ThreadLocal,我们先看一个不使用 ThreadLocal 的示例,然后重写我们的示例以利用该结构。
3.在 Map 中存储用户数据
让我们思考一个程序场景,它需要根据给定的用户 ID 存储特定用户的上下文数据:
我们希望每个用户 ID 有一个线程。我们将创建一个实现 Runnable 接口的 SharedMapWithUserContext 类。run() 方法中的实现会通过 UserRepository 类调用数据库,该数据库会返回给定用户 ID 的 Context 对象。
接下来,我们将该上下文存储在以 userId 为关键字的 ConcurentHashMap 中:
我们可以为两个不同的 userId 创建并启动两个线程,并断言 userContextPerUserId 映射中有两个条目,从而轻松测试我们的代码:
4.在 ThreadLocal 中存储用户数据
我们可以重写示例,使用 ThreadLocal 来存储用户上下文实例。每个线程都将拥有自己的 ThreadLocal 实例。
使用 ThreadLocal 时,我们需要非常小心,因为每个 ThreadLocal 实例都与特定的线程相关联。在我们的示例中,每个特定的 userId 都有一个专用的线程,而这个线程是由我们创建的,因此我们可以完全控制它。
run() 方法将获取用户上下文,并使用 set() 方法将其存储到 ThreadLocal 变量中:
我们可以通过启动两个线程来测试它,这两个线程将执行给定用户 ID 的操作:
运行这段代码后,我们将在标准输出中看到 ThreadLocal 已在每个给定线程中设置:
我们可以看到,每个用户都有自己的上下文。
5.ThreadLocal 和线程池
ThreadLocal 提供了一种易于使用的 API,可将某些值限制在每个线程中。这是在 Java 中实现线程安全的一种合理方式。不过,在同时使用 ThreadLocal 和线程池时,我们应该格外小心。
为了更好地理解这个可能的注意事项,让我们考虑一下下面的情况:
应用程序从线程池中借用一个线程。
它将一些线程限定值存储到当前线程的 ThreadLocal 中。
当前执行结束后,应用程序会将借用的线程归还给线程池。
一段时间后,应用程序会借用同一线程处理另一个请求。
由于应用程序上次没有执行必要的清理,它可能会在新请求中重新使用相同的 ThreadLocal 数据。
这可能会在高并发应用程序中造成令人意想不到的后果。
解决这个问题的一种方法是:在使用完每个 ThreadLocal 后,手动将其删除。由于这种方法需要严格的代码审查,因此容易出错。
6.ThreadPoolExecutor 扩展
事实证明,扩展 ThreadPoolExecutor 类并为 beforeExecute() 和 afterExecute() 方法提供自定义钩子实现是可行的。在使用借用线程运行任何程序之前,线程池会调用 beforeExecute() 方法。另一方面,它将在执行我们的逻辑后调用 afterExecute() 方法。
因此,我们可以扩展 ThreadPoolExecutor 类,并在 afterExecute() 方法中删除 ThreadLocal 数据:
如果我们将请求提交给 ExecutorService 的这个实现,就可以确保使用 ThreadLocal 和线程池不会给我们的应用程序带来安全隐患。
在本文中,我们研究了 ThreadLocal 结构。我们实现了使用线程间共享的 ConcurrentHashMap 来存储与特定用户 ID 关联的上下文的逻辑。然后,我们重写了示例,利用 ThreadLocal 来存储与特定用户 ID 和特定线程相关的数据。
版权声明: 本文为 InfoQ 作者【骨灰架构师虫哥】的原创文章。
原文链接:【http://xie.infoq.cn/article/f561ba15d94c886fd70feed85】。文章转载请联系作者。
评论