文档章节

ThreadLocal变量浅析

ainilife
 ainilife
发布于 2014/05/06 21:20
字数 1163
阅读 326
收藏 0

     在Java中,我们知道,如果需要进行线程间共享数据,可能会使用加锁或者volatile的方式;如果想要在单个线程里共享数据,可能就会使用ThreadLocal变量。本文将浅析我理解的和我看到的ThreadLocal变量,包括它的机制、使用方式和常见问题。

机制

     我们知道ThreadLocal的使用方式如下:(这段代码是在ThreadLocal变量的doc中抄过来的)

 public class ThreadId {
      // Atomic integer containing the next thread ID to be assigned
      private static final AtomicInteger nextId = new AtomicInteger(0);
 
      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.get()方法时,首先得到当前线程,然后从当前线程中取得ThreadLocalMap变量。如果存在这个map,那么以ThreadLocal为key就可以获得当前线程的value了。这种方式相当于让每个线程都拥有一个变量的拷贝。

//ThreadLocal中的get方法
    public T get() {
        Thread t = Thread.currentThread();
        //每个Thread中都有ThreadLocalMap,这个map以ThreadLocal为key,保存的值为value
        //比如ThreadLocalMap<ThreadLocal,Integer>。 所以可以通过当前线程获得这个map。
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null)
                return (T)e.value;
        }
        return setInitialValue();
    }

     在更深层次的读代码后发现,这个ThreadLocalMap后面并不是HashMap,而是它自己实现了一套map。另外,map中的entry对象很有意思,如下:

    static class ThreadLocalMap {
    /**
         * The entries in this hash map extend WeakReference, using
         * its main ref field as the key (which is always a
         * ThreadLocal object).  Note that null keys (i.e. entry.get()
         * == null) mean that the key is no longer referenced, so the
         * entry can be expunged from table.  Such entries are referred to
         * as "stale entries" in the code that follows.
         */
        static class Entry extends WeakReference<ThreadLocal> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal k, Object v) {
                super(k);
                value = v;
            }
        }

可以发现entry是一个WeakReference对象,我理解的原因是如果这个ThreadLocal变量不再使用了,那么entry会被垃圾回收掉。

常见场景

SimpleDataFormat是一个线程不安全的类,并且它的性能不是很好。在多线程情况下,一般能想到的使用方式是,

  1. 每次调用的时候都是new出来一个SimpleDataFormat对象

  2. 使用加锁的方式来使用SimpleDataFormat

加锁不是很好,每次new呢,性能也不是不好,所以在这里,就可以发挥ThreadLocal的强大功能了。

public class Foo{
    private static final ThreadLocal<SimpleDateFormat> formatter = new ThreadLocal<SimpleDateFormat>(){
        @Override
        protected SimpleDateFormat initialValue()
        {
            return new SimpleDateFormat("yyyyMMdd HHmm");
        }
    };

    public String formatIt(Date date)
    {
        return formatter.get().format(date);
    }}

这种方式非常高效、优雅。

常见问题

    在使用ThreadLocal变量的时候,在使用完毕之后,需要显示的主动的释放资源,这是一个很好的习惯

   threadLocal.remove();

   在有线程池的情况下,这种显示remove的好处在于,

  1. 可以避免因为线程池的重用而导致的threadlocal变量中get的数据是老线程的数据,而导致不正确

  2. 在一些Web容器中,比如tomcat、jboss的中,如果不主动释放资源,那么可能会导致out of memory的异常出现。

那么为什么会出现OOM呢?

   因为在tomcat或者其他web容器中,通常有线程池。线程池中的线程是复用的,Thread不会被销毁,而ThreadLocal变量是保存在Thread中的ThreadLocalMap中的,所以如果不主动清理这些变量,那么这些ThreadLocal变量一直会存在线程池中,不被清理。就算tomcat中的应用销毁了,这些threadlocal变量还在。这就造成了memory leak,但还不至于出现OOM。那么什么情况下出现OOM呢?

   当应用不停的reload的情况下会出现OOM。为什么呢?当tomcat中的应用卸载的时候,classloader会被回收,那么GC会清理在perm heap中不被使用的class(注:PermGen中可以被回收的条件是:classloader可以被回收,其下的所有加载过的没有对应实例的类信息(保存在permgen)可以被回收)。发现它无法清理ThreadLocal变量中引用的那个class,因为这个class正在被线程池中的线程使用。如果那个class又引用了其他class,那么所有被引用的class将都不能被清理。这样就导致这些类信息在PermGen中发生memory leak了。然后当应用重新load之后,相应的类信息又被新的classloader重新在permGen中加载一遍。在最坏的情况下,应用不停的reload,那么permGen终将被class信息撑爆,造成OOM。



初次发文,不足之处,多多指正。    

如有转载,请指明出处。

© 著作权归作者所有

ainilife
粉丝 7
博文 2
码字总数 2240
作品 1
高级程序员
私信 提问
我对java String的理解 及 源码浅析

摘要: 摘要: 原创出处: http://www.cnblogs.com/Alandre/ 泥沙砖瓦浆木匠 希望转载,保留摘要,谢谢! 每天起床告诉自己,自己的目标是 ”技术 + 英语 还有生活“! -泥沙砖瓦浆木匠 一.char...

泥沙砖瓦浆木匠
2014/08/17
269
0
java.io.Serializable浅析

 Java API中java.io.Serializable接口源码: 1 public interface Serializable {2 }   类通过实现java.io.Serializable接口可以启用其序列化功能。未实现次接口的类无法使其任何状态序列化...

偶尔诗文
2015/08/16
62
0
Java 内存分配全面浅析

本文将由浅入深详细介绍Java内存分配的原理,以帮助新手更轻松的学习Java。这类文章网上有很多,但大多比较零碎。本文从认知过程角度出发,将带给读者一个系统的介绍。 进入正题前首先要知道...

vieky
2014/12/10
295
3
浅析java内存模型--JMM(Java Memory Model)

在并发编程中,多个线程之间采取什么机制进行通信(信息交换),什么机制进行数据的同步? 在Java语言中,采用的是共享内存模型来实现多线程之间的信息交换和数据同步的。 线程之间通过共享程...

我最喜欢三大框架
03/12
24
2
JVM 虚拟机(对象创建,类加载器,执行引擎等),

1.揭开 Java 对象创建的奥秘? 2.class 文件结构详解? 3.详解 Java 类的加载过程? > Java 对象创建,class 文件结构 Java对象模型 。Java对象保存在堆内存中。在内存中,一个Java对象包含三...

desaco
2018/08/29
0
0

没有更多内容

加载失败,请刷新页面

加载更多

OSChina 周四乱弹 —— 当你简历注水但还是找到了工作

Osc乱弹歌单(2019)请戳(这里) 【今日歌曲】 @花间小酌 :#今日歌曲推荐# 分享成龙的单曲《男儿当自强》。 《男儿当自强》- 成龙 手机党少年们想听歌,请使劲儿戳(这里) @hxg2016 :刚在...

小小编辑
今天
2.7K
22
靠写代码赚钱的一些门路

作者 @mezod 译者 @josephchang10 如今,通过自己的代码去赚钱变得越来越简单,不过对很多人来说依然还是很难,因为他们不知道有哪些门路。 今天给大家分享一个精彩的 GitHub 库,这个库整理...

高级农民工
昨天
4
0
用好项目管理工具,人人都可以成为项目经理

现在市面上的项目管理工具越来越多了,但是大多数都是一些协同工具或轻量项目管理工具。如果是多团队、跨部门使用或者企业级的项目管理,从管理思想到工具运用,需要适应企业的业务流程体系,...

cs平台
昨天
12
0
只需一步,在Spring Boot中统一Restful API返回值格式与统一处理异常

统一返回值 在前后端分离大行其道的今天,有一个统一的返回值格式不仅能使我们的接口看起来更漂亮,而且还可以使前端可以统一处理很多东西,避免很多问题的产生。 比较通用的返回值格式如下:...

晓月寒丶
昨天
69
0
区块链应用到供应链上的好处和实际案例

区块链可以解决供应链中的很多问题,例如记录以及追踪产品。那么使用区块链应用到各产品供应链上到底有什么好处?猎头悬赏平台解优人才网小编给大家做个简单的分享: 使用区块链的最突出的优...

猎头悬赏平台
昨天
32
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部