美文网首页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的姿势

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