浅谈 ThreadLocal

ThreadLocal


1. 概述

  • 通常情况下,我们创建的变量是可以被任何一个线程访问并修改的。如果想实现每一个线程都有自己的专属本地变量该如何解决呢? JDK 中提供的ThreadLocal类正是为了解决这样的问题。ThreadLocal类主要解决的就是让每个线程绑定自己的值,可以将ThreadLocal类形象的比喻成存放数据的盒子,盒子中可以存储每个线程的私有数据。
  • ThreadLocal并非是一个线程的本地实现版本,它并不是一个Thread,而是threadlocalvariable(线程局部变量)。也许把它命名为ThreadLocalVar更加合适。线程局部变量(ThreadLocal)其实的功用非常简单,就是为每一个使用该变量的线程都提供一个变量值的副本,是Java中一种较为特殊的线程绑定机制,是每一个线程都可以独立地改变自己的副本,而不会和其它线程的副本冲突。

2. 作用

  • ThreadLocal解决线程安全问题一个很好的思路,它通过为每个线程提供一个独立的变量副本解决了变量并发访问的冲突问题。在很多情况下,ThreadLocal比直接使用synchronized同步机制解决线程安全问题更简单,更方便,且结果程序拥有更高的并发性

3. 源码分析

Thread.java入手

public class Thread implements Runnable {
	/* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. 
     * 属于这个线程的ThreadLocal值,这个映射由ThreadLocal类维护
     * */
    ThreadLocal.ThreadLocalMap threadLocals = null;

    /*
     * InheritableThreadLocal values pertaining to this thread. This map is
     * maintained by the InheritableThreadLocal class.
     * InheritableThreadLocal值属于这个线程。这个映射由InheritableThreadLocal类维护。
     */
    ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
}
  • 从上述源代码可以看出Thread类中有一个threadLocals和一个inheritableThreadLocals变量,它们都是ThreadLocalMap类型的变量,我们可以把ThreadLocalMap理解为ThreadLocal类实现的定制化的HashMap。默认情况下这两个变量都是null,只有当前线程调用ThreadLocal类的setget方法时才创建它们,实际上调用这两个方法的时候,我们调用的是ThreadLocalMap类对应的get()set()方法。

ThreadLocal.java入手

	/**
	* 返回此线程局部变量的当前线程副本中的值。
	* 如果变量对当前线程没有值,它首先被初始化为调用{@link #initialValue}方法返回的值。
	*/
	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();
    }
	
	/**
     * 将此线程局部变量的当前线程副本设置为指定的值。
     * 大多数子类不需要覆盖这个方法,只依赖于{@link #initialValue}方法来设置线程局部变量的值。
     * @param value 存储在当前线程本地副本中的值。
     */
    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

	/**
     * set()的变体来建立initialValue。
     * 当用户覆盖了set()方法时,用来代替set()。
     */
    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;
    }

	/**
	* 获取与ThreadLocal关联的映射,
	* InheritableThreadLocal覆盖。
	*/
	ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
	
	/**
     * 为这个线程局部变量删除当前线程的值。
     * 如果这个线程局部变量随后被当前线程{@linkplain #get read}调用,它的值将通过调用其{@link #initialValue}方法重新初始化,除非它的值在此期间被当前线程的{@linkplain #set set}初始化。
     * 这可能导致在当前线程中多次调用{@code initialValue}方法。
     */
     public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             m.remove(this);
     }
  • 通过其中,可得:最终的变量是放在了当前线程的ThreadLocalMap中,并不是存在ThreadLocal上,ThreadLocal可以认为只是ThreadLocalMap的封装,传递了变量值。ThrealLocal类中可以通过Thread.currentThread()获取到当前线程对象后,直接通过getMap(Thread t)可以访问到该线程的ThreadLocalMap对象。
  • ThreadLocal内部维护的是一个类似Map的ThreadLocalMap数据结构,key为当前对象的Thread对象,值为Object对象。
		/**
		* 构造一个初始包含(firstKey, firstValue)的新映射。
		* ThreadLocalMaps是惰性构造的,所以我们只在至少有一个条目要放入时才创建一个。
		*/
		ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
            table = new Entry[INITIAL_CAPACITY];
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
            table[i] = new Entry(firstKey, firstValue);
            size = 1;
            setThreshold(INITIAL_CAPACITY);
        }
  • 若在同一个线程中声明了两个ThreadLocal对象的话,会使用Thread内部都是使用仅有 那个ThreadLocalMap存放数据的,ThreadLocalMapkey就是ThreadLocal对象,value就是ThreadLocal对象调用set方法设置的值。

4. 总结

  • 每个线程Thread有一个ThreadLocal.ThreadLocalMap类型的成员变量threadLocals,这个threadLocals就是用来存储实际的变量副本的,键值为当前ThreadLocal变量,value变量副本(即T类型的变量)。 初始时,在Thread里面,threadLocals为空,当通过ThreadLocal变量调用get()方法或者set()方法,就会对Thread类中的threadLocals进行初始化,并且以当前ThreadLocal变量为键值,以ThreadLocal要保存的副本变量为value,存到threadLocals。 然后在当前线程里面,如果要使用副本变量,就可以通过get方法在threadLocals里面查找。

5. 实例

/**
 * 本地线程 测试
 *
 * @author murphy
 */
public class ThreadLocalTest {
    // 可以理解为一个容器:特殊点 - 只能存放一个数据
    private ThreadLocal<String> threadLocal = new ThreadLocal<>();
    private List<String> list = new ArrayList<String>();

    class MyThread1 extends Thread {
        @Override
        public void run() {
            threadLocal.set("murphy");
            list.add("List1");
            System.out.println("MyThread1 - threadLocal - " + threadLocal.get());
            System.out.println("MyThread1 - list - " + list.get(0));
        }
    }

    class MyThread2 extends Thread {
        @Override
        public void run() {
//            threadLocal.set("cici");
//            list.add("List2");
            System.out.println("MyThread2 - threadLocal - " + threadLocal.get());
            System.out.println("MyThread2 - list - " + list.get(0));
        }
    }

    /**
     * 验证:每一个线程都有一个独立的副本
     * 输出:
     * 		MyThread1 - threadLocal - murphy
     * 		MyThread2 - threadLocal - null
     * 		MyThread1 - list - List1
     * 		MyThread2 - list - List1
     */
    public static void main(String[] args) {
        ThreadLocalTest test = new ThreadLocalTest();
        MyThread1 t1 = test.new MyThread1();
        MyThread2 t2 = test.new MyThread2();
        t1.start();
        t2.start();
    }

  	/**
  	* 输出:Cici
  	*/
    public static void main1(String[] args) {
        ThreadLocalTest test = new ThreadLocalTest();
        // 添加数据
        test.threadLocal.set("murphy");
        // 再次添加会覆盖先前的值
        test.threadLocal.set("Cici");
        // 取出数据
        String s = test.threadLocal.get();
        System.out.println(s);
    }
}

6. 实战 - 优化Mybatis工具类

MybatisUtil.java

/**
 * 工具类:获取连接 - 关闭连接
 *
 * @author murphy
 */
public class MybatisUtil {

    private static ThreadLocal<SqlSession> sqlSessionThreadLocal = new ThreadLocal<>();
    private static SqlSessionFactory factory;

    static {
        Reader reader = null;
        try {
            reader = Resources.getResourceAsReader("mybatis.xml");
        } catch (IOException e) {
            e.printStackTrace();
        }
        // 创建工厂
        factory = new SqlSessionFactoryBuilder().build(reader);
    }

    /**
     * 获取连接
     * @return
     */
    public static SqlSession getSqlSession(){
        // 优化 - 从ThreadLocal中获取SqlSession
        SqlSession sqlSession = sqlSessionThreadLocal.get();
        if (sqlSession == null) {
            // 创建sqlSession
            sqlSession = factory.openSession();
            // 将sqlSession与线程进行绑定
            sqlSessionThreadLocal.set(sqlSession);
        }
        return sqlSession;
    }

    /**
     * 关闭连接
     */
    public static void closeSqlSession() {
        // 优化 - 从ThreadLocal中获取SqlSession
        SqlSession sqlSession = sqlSessionThreadLocal.get();
        if (sqlSession != null) {
            sqlSession.close();
            // 移除局部变量的值
            sqlSessionThreadLocal.remove();
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

xmurphymurphy

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值