美文网首页Java漫谈程序员
正确打开ThreadLocal的姿势

正确打开ThreadLocal的姿势

作者: 聊聊其他 | 来源:发表于2018-06-22 12:04 被阅读37次

作者:Nikita Salnikov-Tarnovski

不知道各位同学之前有没有使用过ThreadLocal。使用得当,ThreadLocal对于程序的设计还是很有帮助的。

1.ThreadLocal简介

我们先来介绍一下ThreadLocal,以下来自Oracle官方文档:

public class ThreadLocal<T>extends Object

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

例如,下面的类为每个线程生成本地唯一的标识符。 线程的ID在第一次调用ThreadId.get()时被分配,并在随后的调用中保持不变。

import java.util.concurrent.atomic.AtomicInteger;

 public class ThreadId {
     // Atomic integer containing the next thread ID to be assigned
     private static final AtomicInteger nextId = new AtomicInteger(0);

 // Thread local variable containing each thread's ID
 private static final ThreadLocal<Integer> threadId =
     new ThreadLocal<Integer>() {
         @Override protected Integer initialValue() {
             return nextId.getAndIncrement();
     }
 };

 // Returns the current thread's unique ID, assigning it if necessary
 public static int get() {
     return threadId.get();
 }
}

只要线程处于活动状态并且ThreadLocal实例可以访问,每个线程就拥有对其线程局部变量副本的隐式引用; 在线程消失后,线程本地实例的所有副本都将进行垃圾回收(除非存在对这些副本的其他引用)。

2.正确的打开姿势

下面跟大家分享一个故事,作者因为OOM的频繁出现而注意到ThreadLocal的正确使用的重要性。

以下为译文:

由于我们的读者可能已经猜到了,我每天都会处理内存泄漏问题。 OutOfMemoryError消息的特定类型最近开始引起我的注意 - 由误用的ThreadLocals触发的问题变得越来越频繁。 看看造成这种泄漏的原因,我开始相信,其中一半以上是由开发人员造成的,他们不知道他们在做什么,或者是谁试图将解决方案应用于无意解决的问题。

我决定通过发表两篇文章来开放这个话题,其中第一篇是你正在阅读的文章。 在这篇文章中,我解释了ThreadLocal使用背后的动机。 在目前正在进行的第二篇文章中,我将打开ThreadLocal引擎盖并查看实现。

让我们从一个假想的场景开始,在这个场景中,ThreadLocal的用法确实是合理的。 为此,向我们假设的开发人员打招呼,名为Tim。 Tim正在开发一个web应用程序,其中有很多本地化的内容。 例如,来自加利福尼亚州的用户预计会使用熟悉的MM / dd / yy格式来格式化日期,而爱沙尼亚的另一方则希望看到根据dd.MM.yyyy格式化的日期。 所以Tim开始编写这样的代码:

public String formatCurrentDate() {
    DateFormat df = new SimpleDateFormat("MM/dd/yy");
    return df.format(new Date());
}

public String formatFirstOfJanyary1970() {
    DateFormat df = new SimpleDateFormat("MM/dd/yy");
    return df.format(new Date(0));
}

过了一段时间,Tim发现这是无聊的,并且不是很好的设计 - 应用程序代码被这种初始化污染。 所以他通过将DateFormat提取为实例变量来做出看似合理的举动。 移动后,他的代码如下所示:

private DateFormat df = new SimpleDateFormat("MM/dd/yy");

public String formatCurrentDate() {
    return df.format(new Date());
}

public String formatFirstOfJanyary1970() {
    return df.format(new Date(0));
}

对重构结果感到满意,将更改推送到存储库并走回家。 几天后,用户开始抱怨 - 其中一些似乎完全乱码的字符串,而不是以前的格式良好的日期。

调查问题Tim发现DateFormat实现不是线程安全的。 这意味着在上面的场景中,如果两个线程同时使用formatCurrentDate()formatFirstOfJanyary1970()方法,那么状态可能会变形并且显示的结果可能会混乱。 所以Tim通过限制对方法的访问来确定问题,以确保一次一个线程进入格式化功能。 现在他的代码如下所示:

 private DateFormat df = new SimpleDateFormat("MM/dd/yy");

public synchronized String formatCurrentDate() {
    return df.format(new Date());
}

public synchronized String formatFirstOfJanyary1970() {
    return df.format(new Date(0));
}

做完修改好,Tim把代码提交了上去,并进入一个长期的假期。 仅在第二天才开始接听电话,抱怨应用程序的吞吐量急剧下降。 深入研究这个问题,他发现同步访问在应用程序中造成了意想不到的瓶颈。 线程现在随便的进入格式化部分,而必须等待在另一个线程完成之后才可以。

进一步了解这个问题,Tim发现了一个名为ThreadLocal的不同类型的变量。 这些变量与它们的正常副本不同,因为每个访问一个线程的线程(通过ThreadLocalgetset方法)都有其自己的,独立初始化的变量副本。 对于新发现的概念感到满意,Tim再次重写了代码:

public static ThreadLocal df = new ThreadLocal() {
    protected DateFormat initialValue() {
        return new SimpleDateFormat("MM/dd/yy");
    }
};

public String formatCurrentDate() {
    return df.get().format(new Date());
}

public String formatFirstOfJanyary1970() {
    return df.get().format(new Date(0));
}

通过这样的过程,Tim通过痛苦的经验教训了一个强大的概念。 就像在最后一个例子中一样,结果就是一个很好的例子。

但新发现的概念是一个危险的概念。 如果Tim使用其中一个应用程序类而不是由引导类加载程序加载的JDK捆绑的DateFormat类,那么我们已经处于危险区域。 在完成任务后忘记将其删除,该对象的副本将保留在线程中,线程往往属于线程池。 由于线程池的使用寿命超过了应用程序的使用寿命,因此它将阻止该对象,因此ClassLoader负责加载应用程序以进行垃圾收集。 我们创建了一个泄漏,它有机会表现出一个java.lang.OutOfMemoryError:PermGen space form

我希望故事的第一部分已经给你对ThreadLocal一个好的认知。

相关文章

  • 正确打开ThreadLocal的姿势

    作者:Nikita Salnikov-Tarnovski 不知道各位同学之前有没有使用过ThreadLocal。使...

  • CocoaPods 正确打开姿势

    1、首先查看当前pod 的版本: pod --version截止当前2019年2月26号最新版本号:1.6.1 2...

  • 读书的正确打开姿势

    在时下读书被热捧的时代,我们的到底该怎么抉择呢? 很多人拿着书却怎么也读不进去,那到底如何是好呢? 对于年轻人来说...

  • 打开护肤的正确姿势

    这里给大家介绍的是九个护肤误区,以及正确做法,希望能够帮助到大家! 感兴趣就请往下看吧

  • nvm的正确打开姿势

    昨天在运行npm install的时候突然报错,就是下面的这些话,说是是npm不支持当前版本的node.js,版本...

  • 投资的正确打开姿势

    38/99 大多数人正在计划变穷!是的,你没有听错,很多投资人都是伪投资者,他们其实利用投资加快了自己返贫的进程。...

  • 打开生日的正确姿势

    还记得你小时候,爸爸妈妈为过你第一个生日吗? 可能家庭条件好的有蛋糕, 但是很多都像我一样是一颗水煮蛋吧,沾上白砂...

  • 读书的正确打开姿势

    你一定有过以下类似的经历: (一) 中午和朋友吃饭,聊到某个话题时朋友语出惊人,用新奇的语言,透彻的分析彻底征服了...

  • 创新的正确打开姿势

    创新者并非天赋异禀,他们用行动改变了自己的创新基因。本文主要论述了个人创新的方法论。 个人创新 大多数人以为,创新...

  • 学习的正确打开姿势

    听秋水老师这节课时,想起曾经有人问我:你是不是认为只有拿着书读才算是学习? 学习,当然有很多种方法。自己喜欢又适合...

网友评论

    本文标题:正确打开ThreadLocal的姿势

    本文链接:https://www.haomeiwen.com/subject/ibkkyftx.html