网上有很多关于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优先处理掉,根本无需担心内存泄露问题。

posted on 2018-12-11 09:25  程序诗人  阅读(277)  评论(0编辑  收藏  举报