网上有很多关于ThreadLocal的文章,大部分都提到了多线程之间共享资源的问题。其实ThreadLocal和多线程之间一点关系都没有。如果有,我怕是它的名字改成ThreadShare是不是更合适呢?开个玩笑。从其名称ThreadLocal,我们就可以看出他应该是隶属于线程内部的资源。接下来就详细说说吧。
我们以前在处理一个Request请求,穿透多个业务方法的时候,如果要共享数据,一般都会搞个Context上下文对象,在多个方法之间进行传递,这样可以解决多个方法之间的数据共享问题。这是很不错的一种方案,而且这种方案在实际使用的时候效果也非常不错,因为Context内部我们想放什么,就放什么,想怎么放就怎么放,非常的自由无界。但是这种自由带来的问题在小流量的请求中是不会有问题的,但是在大流量的请求中,则存在不小的问题,主要在:
1. Context对象,每个请求进来,都会new一个,大流量下,瞬间暴增,由于空间申请操作势必引发频繁的young GC, 业务压力大的时候,full GC也是不可避免的。
2. Context对象,在一个请求终结之后,需要手动释放。
3. Context对象,存在被请求内部的多线程共享访问的情形。有线程安全性问题。
上面三个问题,是我随便列举的,但是在实际使用中,可能还有更多。但是如果Context的生产和销毁如果控制的足够好的话,上面的问题也不是什么问题。
既然Context对象的控制稍显麻烦,那么JDK有没有提供什么现成的类库供我们使用呢? 答案是肯定的,这个对象就是ThreadLocal对象。
说道ThreadLocal对象,我更认为他是在当前请求的上游和下游之间进行数据共享的。那么按照之前的例子说来,如果一个请求穿越多个业务方法,其实数据的共享可以利用ThreadLocal来进行,这样就无需专门定义一个Context。
首先来看看其内部get实现机制:
/** * Returns the value in the current thread's copy of this * thread-local variable. If the variable has no value for the * current thread, it is first initialized to the value returned * by an invocation of the {@link #initialValue} method. * * @return the current thread's value of this thread-local */public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) return (T)e.value; } return setInitialValue();}ThreadLocalMap getMap(Thread t) { return t.threadLocals;}private T setInitialValue() { T value = initialValue(); Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); return value;}protected T initialValue() { return null;}
从上面源码可以看出,get操作会从当前Thread上附加的map中进行数据获取。
再来看看set方法:
/** * Sets the current thread's copy of this thread-local variable * to the specified value. Most subclasses will have no need to * override this method, relying solely on the {@link #initialValue} * method to set the values of thread-locals. * * @param value the value to be stored in the current thread's copy of * this thread-local. */public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value);}ThreadLocalMap getMap(Thread t) { return t.threadLocals;}void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue);}
从上面源码看出,首先他会取出当前Thread,然后会将map设置到当前Thread上,用户可以在这个map上进行数据增删改查操作。非常巧妙。所以从这里可以看出,一个ThreadLocal对象,即便穿插在多个线程之间,也不会造成资源共享问题,因为他会为每个线程都设置map对象,这也从根本上避免了线程安全问题。
最后,因为ThreadLocal内部的对象为WeakReference,所以不用进行手动释放,只需要保证一个请求结束,对其内部的引用释放掉就行了,然后自动会被JVM优先处理掉,根本无需担心内存泄露问题。