ThreadLocal原理与使用场景

一、ThreadLocal原理

如果看懂了ThreadLocal的set()方法,get()、remove()方法也就好理解了,所以重点看一下set()方法。

set()方法执行流程总结:

1.获取当前线程对象

2.获取当前线程对象的成员变量ThreadLocalMap

3.1不为null,set值

3.1.1获取当前ThreadLocalMap对象的Entry数组

3.1.2获取ThreadLocal对象的i值

3.1.3遍历Entry数组,根据ThreadLocal对象的i值判断,如果ThreadLocal对象已存在数组中,更新值,退出

3.1.4不存在数组中,以key-value形式:将ThreadLocal对象与值value绑定放入数组中

3.2为null,为当前线程创建成员变量ThreadLocalMap

3.2.1 new一个Entry数组,初始容量为INITIAL_CAPACITY=16

3.2.2计算出存放在Entry数组的位置i:ThreadLocal的threadLocalHashCode与15的位运算,结果与取模相同。

3.2.3放入Entry数组

源码分析如下:

public class Thread implements Runnable {
    //每个线程对象都维护了一个成员变量ThreadLocalMap
    ThreadLocal.ThreadLocalMap threadLocals = null;
}

public class ThreadLocal<T> {
    private final int threadLocalHashCode = nextHashCode();
    public T get() {}
    public void remove() {}
    public void set(T value) {
        Thread t = Thread.currentThread();//当前线程对象
        ThreadLocal.ThreadLocalMap map = getMap(t);//获取当前线程对象的成员变量ThreadLocalMap
        if (map != null)
            map.set(this, value);//不为null,set值
        else
            createMap(t, value);//为null,为当前线程创建ThreadLocalMap
    }
    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocal.ThreadLocalMap(this, firstValue);//为线程对象创建成员变量ThreadLocalMap
    }

    static class ThreadLocalMap {
        private static final int INITIAL_CAPACITY = 16;
        static class Entry extends WeakReference<ThreadLocal<?>> {
            //线程实际存储私有数据的地方
            Object value;
            //以key-value形式,将ThreadLocal与Object对象绑定。一个ThreadLocal对象在一个线程Thread对象中只可存储一个Object。
            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }

        //ThreadLocalMap类内部维护了一个Entry数组对象table
        private ThreadLocal.ThreadLocalMap.Entry[] table;

        ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
            table = new ThreadLocal.ThreadLocalMap.Entry[INITIAL_CAPACITY];//new一个Entry数组,初始容量为INITIAL_CAPACITY=16
            /**
             * 计算出存放在table数组的位置:ThreadLocal的threadLocalHashCode与15的位运算,结果与取模相同。
             * 因为每个ThreadLocal对象创建后,threadLocalHashCode是final不变的,所以每个ThreadLocal对象的i值是不变的,
             * 这也是为什么一个ThreadLocal对象在一个线程Thread对象中只可存储一个Object的原因
             */
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
            table[i] = new ThreadLocal.ThreadLocalMap.Entry(firstKey, firstValue);//放入Entry数组
            size = 1;
            setThreshold(INITIAL_CAPACITY);
        }

        private void set(ThreadLocal<?> key, Object value) {
            ThreadLocal.ThreadLocalMap.Entry[] tab = table;//当前ThreadLocalMap对象的Entry数组
            int len = tab.length;
            int i = key.threadLocalHashCode & (len - 1);//ThreadLocal对象的i值
            //遍历Entry数组
            for (ThreadLocal.ThreadLocalMap.Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                ThreadLocal<?> k = e.get();
                //ThreadLocal对象已存在数组中,更新值,退出
                if (k == key) {
                    e.value = value;
                    return;
                }
                if (k == null) {
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }
            //不存在数组中,以key-value形式:将ThreadLocal与value绑定放入数组中
            tab[i] = new ThreadLocal.ThreadLocalMap.Entry(key, value);
            int sz = ++size;
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }
    }
}

根据上面源码总结如下:

一个Thread对象可有多个ThreadLocal对象在线程的本地内存中存储值,一个ThreadLocal对象可在多个线程的本地内存中存储值。所以Thread与ThreadLocal可简单理解为多对多关系。(线程的本地内存也称为线程的工作内存,JMM内存模型定义的概念)

ThreadLocal的本质就是一个工具类,提供了set()、get()、remove()等方法,提供对线程本地内存的值的操作。Thread的成员变量ThreadLocalMap实现线程本地内存数据存储的(重点:成员变量实现本地内存对象的存储),ThreadLocalMap是定义在ThreadLocal的静态内部类。

ThreadLocalMap维护一个Entry类型数组,Entry是定义在ThreadLocalMap的静态内部类,Entry就是用了一个Object类型的成员变量value来存储值的,而且Entry的构造函数还将本ThreadLocal对象与线程值value以key-value形式绑定了。一个ThreadLocal对应Entry[]数组的下标i是唯一不变的,因为ThreadLocal的hash值是final。

二、ThreadLocal使用场景

Synchronized和ThreadLocal都是用来解决多线程并发数据访问的,但是Synchronized用于线程间的数据共享,而ThreadLocal则用于线程间的数据隔离。所以,使用场景要根据这个变量本身是否应该是共享的!

ThreadLocal常用使用场景为解决数据库连接、Session管理等。可参考spring的管理。
例子:一个变量是成员变量(多线程共享的),但它本质上应该是线程私有的,就可用ThreadLocal将共享成员变量copy一份到线程私有变量中。如下Demo:

/**
* 问题:如何做到 MyThreadLocal单例,sendMessage()方法不加锁,但是多线程执行sendMessage()仍然是线程安全的?
* 答:共享成员变量connection设为线程私有。即使用ThreadLocal将共享成员变量变成线程私有
**/
public class MyThreadLocal {
    //成员变量,多线程共享
    private Connection connection;

    public static ThreadLocal<Connection> threadLocal = new ThreadLocal();

    public void sendMessage() throws SQLException {
        //Statement statement = connection.createStatement();
        Statement statement = getConnection().createStatement();
        //...
    }

    public static Connection getConnection() {
        Connection conn = threadLocal.get();
        if (conn == null) {
            Connection conn = ConnectionManager.getConnection();
            threadLocal.set(conn);
            return conn;
        } else {
            return conn;
        }
    }
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章