ThreadLocal变量浅析

     在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。



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

如有转载,请指明出处。

转载于:https://my.oschina.net/ainilife/blog/261297

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值