Java并发编程系列之六:深入理解ThreadLocal

引言

无论实际项目实战还是面试,ThreadLocal都是一个绕不开的话题,本文主要从源码角度和大家一起探讨下ThreadLocal的神秘面纱。

  • ThreadLocal是什么?它能干什么?
  • ThreadLocal源码分析
  • 总结

一、ThreadLocal是什么?它能干什么?

ThreadLocal 是一个线程的本地变量, 也就意味着这个变量是线程独有的,是不能与其他线程共享的,它并不是解决多线程共享变量的问题。

所以ThreadLocal与线程同步机制不同,线程同步机制是多个线程共享同一个变量,而ThreadLocal是为每一个线程创建一个单独的变量副本,故而每个线程都可以独立地改变自己所拥有的变量副本,而不会影响其他线程所对应的副本。可以说ThreadLocal为多线程环境下变量问题提供了另外一种解决思路。

ThreadLocal的思想就是用空间换时间,使各线程都能访问属于自己这一份的变量副本,变量值不互相干扰,减少同一个线程内的多个函数或者组件之间一些公共变量传递的复杂度。

在这里插入图片描述


二、ThreadLocal源码分析

1、ThreadLocalMap解析
ThreadLocal内部定义了一个ThreadLocalMap的内部类,ThreadLocalMap实际利用Entry来实现key-value的存储,如下所示:

static class ThreadLocalMap {
	static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }
        ...
}

ThreadLocalMap是实现线程隔离机制的关键,从以上代码可以看出Entrykey就是ThreadLocal,而value就是值。同时,Entry也继承WeakReference,所以说Entry所对应keyThreadLocal实例)的引用为一个弱引用。

我们主要来看下核心的getEntry()set(ThreadLocal> key, Object value)方法

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 void set(ThreadLocal<?> key, Object value) {

            // We don't use a fast path as with get() because it is at
            // least as common to use set() to create new entries as
            // it is to replace existing ones, in which case, a fast
            // path would fail more often than not.

            Entry[] tab = table;
            int len = tab.length;
            // 根据 ThreadLocal 的散列值,查找对应元素在数组中的位置
            int i = key.threadLocalHashCode & (len-1);

			// 采用“线性探测法”,寻找合适位置
            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                ThreadLocal<?> k = e.get();
				// 若key 存在,直接覆盖
                if (k == key) {
                    e.value = value;
                    return;
                }
				// key == null,但是存在值(因为此处的e != null),说明之前的ThreadLocal对象已经被回收了
                if (k == null) {
                	// 用新元素替换陈旧的元素
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }
			//创建新元素
            tab[i] = new Entry(key, value);
            int sz = ++size;
            // 如果没有清理陈旧的 Entry 并且数组中的元素大于了阈值,则进行 rehash
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }

2、核心方法解析
(1) get()
返回此线程局部变量的当前线程副本中的值

public T get() {
		//获取当前线程
        Thread t = Thread.currentThread();
        //获取当前线程的成员变量 threadLocalMap
        ThreadLocalMap map = getMap(t);
        if (map != null) {
        	// 从当前线程的ThreadLocalMap获取相对应的Entry
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                // 获取目标值
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

首先通过当前线程获取所对应的成员变量ThreadLocalMap,然后通过ThreadLocalMap获取当前ThreadLocalEntry,最后通过所获取的Entry获取目标值result

(2) set(T 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);
    }

获取当前线程所对应的ThreadLocalMap,如果不为空,则调用ThreadLocalMapset()方法,key就是当前ThreadLocal,如果不存在,则调用createMap()方法新建一个。

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

(3) initialValue()
返回此线程局部变量的当前线程的初始值。

protected T initialValue() {
        return null;
    }

该方法定义为protected级别且返回为null,需要其子类实现其功能,所以我们在使用ThreadLocal的时候一般都应该覆盖该方法。该方法不能显示调用,只有在第一次调用get()或者set()方法时才会被执行,并且仅执行1次。

(4) remove()
移除此线程局部变量当前线程的值。

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

三、总结

1、多个线程去获取一个共享变量时,要求获取的是这个变量的初始值的副本。每个线程存储这个变量的副本,对这个变量副本的改变不会影响变量本身。适用于多个线程依赖不同变量值完成操作的场景。比如:

  • 多数据源的切换
  • spring声明式事务

2、将ThreadLocal设置成private static的,这样ThreadLocal会尽量和线程本身一起回收。

发布了86 篇原创文章 · 获赞 45 · 访问量 8万+

猜你喜欢

转载自blog.csdn.net/Diamond_Tao/article/details/103865434