开发人员中鲜为人知的功能之一是线程本地存储。 这个想法很简单,并且在需要数据的情况下很有用。 如果我们有两个线程,则它们引用相同的全局变量,但我们希望它们具有彼此独立初始化的单独值。
大多数主要的编程语言都有该概念的实现。 例如,C ++ 11甚至具有thread_local关键字,Ruby选择了一种API 方法 。
从1.2版开始,Java还使用java.lang.ThreadLocal <T>及其子类java.lang.InheritableThreadLocal <T>来实现该概念,因此这里没有什么新鲜的东西。
假设由于某种原因,我们需要为线程指定一个Long特定值。 使用线程本地将很简单:
public class ThreadLocalExample {public static class SomethingToRun implements Runnable {private ThreadLocal threadLocal = new ThreadLocal();@Overridepublic void run() {System.out.println(Thread.currentThread().getName() + " " + threadLocal.get());try {Thread.sleep(2000);} catch (InterruptedException e) {}threadLocal.set(System.nanoTime());System.out.println(Thread.currentThread().getName() + " " + threadLocal.get());}}public static void main(String[] args) {SomethingToRun sharedRunnableInstance = new SomethingToRun();Thread thread1 = new Thread(sharedRunnableInstance);Thread thread2 = new Thread(sharedRunnableInstance);thread1.start();thread2.start();}}
以下代码的一个可能示例运行将导致:
Thread-0 nullThread-0 132466384576241Thread-1 nullThread-1 132466394296347
在开始时,两个线程的值都设置为null,显然每个线程都使用单独的值,因为在将Thread-0的值设置为System.nanoTime()之后 ,它不会对Thread-1的值产生任何影响如我们所愿,一个线程作用域的long变量。
一个不错的副作用是线程从各种类调用多个方法的情况。 他们都将能够使用相同的线程范围变量,而无需进行重大的API更改。 由于未明确传递价值,因此可能会难以测试且不利于设计,但这完全是一个单独的主题。
在哪些地区流行使用线程局部语言的框架?
作为Java中最受欢迎的框架之一,Spring在内部将ThreadLocals用于许多部分,可通过简单的github 搜索轻松显示。 大多数用法与当前用户的操作或信息有关。 实际上,这实际上是JavaEE世界中ThreadLocals的主要用途之一,例如在RequestContextHolder中存储当前请求的信息:
private static final ThreadLocal<RequestAttributes> requestAttributesHolder = new NamedThreadLocal<RequestAttributes>("Request attributes");
或UserCredentialsDataSourceAdapter中的当前JDBC连接用户凭据。
如果我们回到RequestContextHolder,则可以使用此类访问代码中任何位置的所有当前请求信息。
常见的用例是LocaleContextHolder ,它可以帮助我们存储当前用户的语言环境。
Mockito使用它来存储当前的“全局” 配置 ,如果我们看看那里的任何框架,那么我们也很有可能会找到它。
线程本机和内存泄漏
我们了解了这个很棒的小功能,所以让我们在各处使用它。 我们可以做到这一点,但很少有Google搜索,而且我们发现那里的大多数人都说ThreadLocal是邪恶的。 并非完全正确,它是一个很好的实用程序,但在某些情况下可能容易造成内存泄漏。
“您是否可以通过线程局部变量导致意外的对象保留? 你当然可以。 但是您也可以使用数组来执行此操作。 这并不意味着线程局部变量(或数组)是坏事。 只是您必须谨慎使用它们。 使用线程池需要格外小心。 随意使用线程池与随意使用线程本地变量相结合会导致意外的对象保留,这在许多地方都已提到。 但是,将责任归咎于线程本机是没有根据的。” –约书亚·布洛赫(Joshua Bloch)
如果它在应用程序服务器上运行,则使用ThreadLocal在服务器代码中创建内存泄漏非常容易。 ThreadLocal上下文与运行它的线程相关联,一旦线程死掉,它将被丢弃。 现代的应用服务器使用线程池,而不是在每个请求上创建新线程,这意味着您最终可能会无限期地在应用程序中保存大型对象。 由于线程池来自应用程序服务器,因此即使卸载应用程序后,我们的内存泄漏仍会保留。 修复方法很简单,可以释放不需要的资源。
另一个ThreadLocal滥用是API设计。 我经常看到到处都在使用RequestContextHolder (它持有ThreadLocal ),例如DAO层。 后来,如果有人在诸如和调度程序之类的请求之外调用相同的DAO方法,他将得到一个非常糟糕的惊喜。
这使黑魔法和许多维护开发人员最终找到了您的住所并进行了访问。 即使ThreadLocal中的变量是线程本地的,它们在代码中还是非常全局的。 使用它之前,请确保您确实需要此线程作用域。
有关该主题的更多信息
- http://en.wikipedia.org/wiki/Thread-local_storage
- http://www.appneta.com/blog/introduction-to-javas-threadlocal-storage/
- https://plumbr.eu/blog/how-to-shoot-yourself-in-foot-with-threadlocals
- http://stackoverflow.com/questions/817856/when-and-how-should-i-use-a-threadlocal-variable
- https://plumbr.eu/blog/when-and-how-to-use-a-threadlocal
- https://weblogs.java.net/blog/jjviana/archive/2010/06/09/dealing-glassfish-301-memory-leak-or-threadlocal-thread-pool-bad-ide
- https://software.intel.com/zh-CN/articles/use-thread-local-storage-to-reduce-synchronization
翻译自: https://www.javacodegeeks.com/2014/12/thread-local-storage-in-java.html