ThreadLocal的实现原理和使用场景

ThreadLocal的实现原理和使用场景

jdk和一些框架的很多的工具和实现类,如果没有分析过实现原理和理解过源码,很难在开发中有好的实际使用,或者是很难发挥这些工具或实现类的能力,不能灵活地在某些场景时马上想到这些工具和源码实现,或者使用出现问题时再去探究原因再去跟踪源码已为时已晚。 这里说的ThreadLocal就是需要分析源码才能灵活地实际使用。
ThreadLocal简介

ThreadLocal的主要作用是提供线程的局部变量,访问某个ThreadLocal变量的每个线程都有自己的局部变量,它独立于变量的初始化副本。实际上是Thread类里面维护一个ThreadLocalMap,ThreadLocalMap以该ThreadLocal对象变量为key,去取对应的value的实现过程,达到某个ThreadLocal变量的每个线程都有自己的局部值(每一线程维护变量的副本),线程间独立变化独立维护,下面会分析源码来详细介绍ThreadLocal的实现原理。
ThreadLocal实现的主要要点是:泛型、模板方法(initialValue)、Thread维护ThreadLocalMap、弱引用为key(避免内存泄漏)、线性探测解决hash冲突等。

最常用的使用场景举例:
定义了一个单实例的java bean,它有若干属性,但是有一个属性不是线程安全的,比如说HashMap。并且碰巧你并不需要在不同的线程中共享这个属性,也就是说这个属性不存在跨线程的意义。这种场景下可以直接使用ThreadLocal 。原话出处
例如Web服务器请求(线程封闭,把请求封闭在一条线程中处理)中有用到某个对象实例bean,但该对象实例不是线程安全的(有状态的),可以把这个bean定义为ThreadLocal。
例如代码中用到SimpleDateFormat作为日期格式化的工具类,但由于SimpleDateFormat不是线程安全的,可以用ThreadLocal实现为每个线程创建独立的SimpleDateFormat实例对象。SimpleDateFormat结合ThreadLocal的使用举例:ConcurrentDateUtil

ThreadLocal 使用举例
public class TestThreadLocal
{
    public static ThreadLocal<ThreadLocalObject> threadLocal = new ThreadLocal<ThreadLocalObject>()
    {
        @Override
        protected ThreadLocalObject initialValue()
        {
            System.out.println(Thread.currentThread().getName() + " initial begin");
            return new ThreadLocalObject(10);
        }
    };

    public static void main(String[] args)
    {
        for (int i = 0; i < 5; i++)
        {
            new MyThread(i).start();
        }
    }
}

class MyThread extends Thread
{
    private int threadIndex;

    public MyThread(int threadIndex)
    {
        this.threadIndex = threadIndex;
    }

    @Override
    public void run()
    {
        Thread.currentThread().setName("ThreadName" + threadIndex);
        ThreadLocalObject threadLocalObject = TestThreadLocal.threadLocal.get();
        System.out.println(Thread.currentThread().getName() + " initialValue: " + threadLocalObject.getNum());
        threadLocalObject.setNum(threadIndex);
        System.out.println(Thread.currentThread().getName() + " finish change value");
        try
        {
            Thread.sleep(1000);
        }
        catch (Exception ex)
        {
        }
        System.out.println(Thread.currentThread().getName() + " after change value: "
                + TestThreadLocal.threadLocal.get().getNum());
        TestThreadLocal.threadLocal.remove();
    }
}

class ThreadLocalObject
{
    int num = 3;

    public ThreadLocalObject(int num)
    {
        this.num = num;
    }

    void setNum(int num)
    {
        this.num = num;
    }

    int getNum()
    {
        return num;
    }
}

运行输出:

ThreadName0 initial begin
ThreadName2 initial begin
ThreadName1 initial begin
ThreadName0 initialValue: 10
ThreadName1 initialValue: 10
ThreadName1 finish change value
ThreadName2 initialValue: 10
ThreadName2 finish change value
ThreadName0 finish change value
ThreadName4 initial begin
ThreadName4 initialValue: 10
ThreadName4 finish change value
ThreadName3 initial begin
ThreadName3 initialValue: 10
ThreadName3 finish change value
ThreadName3 after change value: 3
ThreadName2 after change value: 2
ThreadName1 after change value: 1
ThreadName0 after change value: 0
ThreadName4 after change value: 4

可以看出threadLocal虽然是个静态变量,但我们表象看到的就是每一线程维护threadLocal变量的副本,线程间独立变化独立维护。但实际上threadLocal只是作为key,去Thread维护的ThreadLocalMap中查找对应value。threadLocal只是一个唯一对象,也不会去创建任何threadLocal对象的拷贝或副本。
通过下面的源码分析可能对上面的运行结果和分析描述有更清晰的理解。

ThreadLocal源码实现

下面说明ThreadLocal的源码实现。
首先看Thread,ThreadLocal,ThreadLocalMap,Entry之间的关系:

在这里插入图片描述

可以看到ThreadLocalMap是ThreadLocal实现的主要数据结构,是ThreadLocal中的静态内部类。是一个Entry[] table数组结构实现的Map,Entry(继承弱引用)类似HashMap中的Node(Map.Entry)的键值结构,以ThreadLocal作为键值。在下面代码分析中可以看到ThreadLocalMap实际上是通过线性探测再散列来解决hash冲突,而不是像HashMap中通过拉链法(链表和红黑树)来解决hash冲突。

理解ThreadLocal最主要的关键点是理解到Thread类维护了ThreadLocalMap结构,实现以特定ThreadLocal对象为key去取对应的线程独立的value的过程。

Thread:
    ThreadLocal.ThreadLocalMap threadLocals = null;
  1. get 方法的实现
    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        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;
    }

    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

这里代码比文字描述更加直观清楚,过程就是先获取到当前的线程t,再根据t获取ThreadLocalMap。

如果map不为空,则根据map.getEntry(this)获取Entry键值对,get()是ThreadLocal的局部方法,这里的this也就是当前ThreadLocal对象,可以看出Thread维护的ThreadLocalMap可以有多个ThreadLocal对象key,也就可以维护多个需要线程独立的变量的value。如果map为null或者取不到对应Entry,就调用setInitialValue()方法。

setInitialValue()里面会在Map为null时创建Map,插入initialValue()取得的值,也就对应了初始变量副本的说法。这是我们ThreadLocal使用举例代码中重写initialValue()方法的原因。 如果initialValue()方法里每次返回的是同一个对象,那么各个线程实际上还是操作同一个对象,如果initialValue()每次都new新的对象,那么各个线程操作的就是不同对象。 没有重写initialValue()的情况下,如果没有先set,直接get的话,会返回null。

下面看下Map中getEntry(this)的实现,根据查找过程可以看出是通过线性探测来解决hash冲突。

        private Entry getEntry(ThreadLocal<?> key) {
            int i = key.threadLocalHashCode & (table.length - 1);
            Entry e = table[i];
            if (e != null && e.get() == key)
                return e;
            else
                return getEntryAfterMiss(key, i, e);
        }

        private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
            Entry[] tab = table;
            int len = tab.length;

            while (e != null) {
                ThreadLocal<?> k = e.get();
                if (k == key)
                    return e;
                if (k == null)
                    expungeStaleEntry(i);
                else
                    i = nextIndex(i, len);
                e = tab[i];
            }
            return null;
        }
        
        private static int nextIndex(int i, int len) {
            return ((i + 1 < len) ? i + 1 : 0);
        }
  1. set 方法的实现

清楚get方法的实现原理,基本上就可以想到set方法实现的大概过程了,下面代码分析就比较容易理解。

ThreadLocal:
    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:
        private void set(ThreadLocal<?> key, Object value) {
            Entry[] tab = table;
            int len = tab.length;
            int i = key.threadLocalHashCode & (len-1);

            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                ThreadLocal<?> k = e.get();

                if (k == key) {
                    e.value = value;
                    return;
                }

                if (k == null) {
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }

            tab[i] = new Entry(key, value);
            int sz = ++size;
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }
  1. remove方法的实现

顺手贴下remove方法的源码

ThreadLocal:
     public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             m.remove(this);
     }

ThreadLocalMap:
        private void remove(ThreadLocal<?> key) {
            Entry[] tab = table;
            int len = tab.length;
            int i = key.threadLocalHashCode & (len-1);
            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                if (e.get() == key) {
                    e.clear();
                    expungeStaleEntry(i);
                    return;
                }
            }
        }
实现总结

可以理解为:ThreadLocal的get/set/remove方法实际上不是对当前ThreadLocal对象进行get/set/remove,而是通过增加了一个数据结构ThreadLocalMap来增加一步处理,实际上是对Thread持有的ThreadLocalMap进行get/set/remove操作。ThreadLocal对象的get/set/remove方法,会先取得当前Thread的ThreadLocalMap,再以该ThreadLocal对象为key在ThreadLocalMap中get/set/remove对应的value。value的类型由ThreadLocal中泛型指定ThreadLocal<valueType>

ThreadLocal不会造成内存泄露。ThreadLocal的类注释里面有一段话:

Each thread holds an implicit reference to its copy of a thread-local
variable as long as the thread is alive and the ThreadLocal
instance is accessible; after a thread goes away, all of its copies of
thread-local instances are subject to garbage collection (unless other
references to these copies exist).

下面描述引自 深入理解ThreadLocal

ThreadLocal对Thread的引用全部通过局部变量完成,而没有一个全局变量。而实际的资源副本则存储在Thread的自身的属性ThreadLocalMap中,这说明,其实ThreadLocal只是关联一个Thread和其资源副本的桥梁,并且实际上Thread和资源副本的生命周期是紧密相连的,的的确确如ThreadLocal所说,在线程被回收的时候,其资源副本也会被回收,虽然ThreadLocal是静态的,但是它既不引用Thread,也不引用ThreadLocalMap。真是精巧的设计!

ThreadLocal的应用场景

最常见的ThreadLocal使用场景为 用来解决 数据库连接、Session管理等。

数据库连接中ThreadLocal的使用,下面代码摘自:
https://www.cnblogs.com/WuXuanKun/p/5827060.html

private static ThreadLocal<Connection> connectionHolder
= new ThreadLocal<Connection>() {
public Connection initialValue() {
    return DriverManager.getConnection(DB_URL);
}
};
 
public static Connection getConnection() {
return connectionHolder.get();
}

Session管理中ThreadLocal的使用,下面代码摘自:
http://www.iteye.com/topic/103804

private static final ThreadLocal threadSession = new ThreadLocal();
 
public static Session getSession() throws InfrastructureException {
    Session s = (Session) threadSession.get();
    try {
        if (s == null) {
            s = getSessionFactory().openSession();
            threadSession.set(s);
        }
    } catch (HibernateException ex) {
        throw new InfrastructureException(ex);
    }
    return s;
}

参考博文:
ThreadLocal的基本原理与实现
ThreadLocal用法和实现原理
深入理解ThreadLocal
ThreadLocal的底层实现原理与应用场景
ThreadLocal原理

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值