ThreadLocal 简述

时间:2021-12-02 19:15:28

ThreadLocal的理解

Java中的ThreadLocal类允许我们创建只能被同一个线程读写的变量。因此,如果一段代码含有一个ThreadLocal变量的引用,即使两个线程同时执行这段代码,它们也无法访问到对方的ThreadLocal变量。

The ThreadLocal class in Java enables you to create variables that can only be read and written by the same thread. Thus, even if two threads are executing the same code, and the code has a reference to a ThreadLocal variable, then the two threads cannot see each other's ThreadLocal variables.

解析ThreadLocal类

  • ThreadLocal中 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();
}

判断当前线程,通过getMap 方法返回一个ThreadLocalMap 。

判断map 是否为null ,如果存在的话,就从ThreadLocalMap中获取对应的value,不存在调用setInitialValue方法。

来看看getMap方法的实现。

    /**
* Get the map associated with a ThreadLocal. Overridden in
* InheritableThreadLocal.
*
* @param t the current thread
* @return the map
*/
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}

getMap传入当前的线程返回一个threadLocals,成员变量threadLocals是什么?

 /* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;

再来看看 ThreadLocalMap ,通过map来实现key-value 的存储。

static class ThreadLocalMap {

        /**
* The entries in this hash map extend WeakReference, using
* its main ref field as the key (which is always a
* ThreadLocal object). Note that null keys (i.e. entry.get()
* == null) mean that the key is no longer referenced, so the
* entry can be expunged from table. Such entries are referred to
* as "stale entries" in the code that follows.
*/
static class Entry extends WeakReference<ThreadLocal> {
/** The value associated with this ThreadLocal. */
Object value; Entry(ThreadLocal k, Object v) {
super(k);
value = v;
}
} /**
* Set the value associated with key.
*
* @param key the thread local object
* @param value the value to be set
*/
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;
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();
}
  • ThreadLocal中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);
}

拷贝一个当前的线程,和上面的get方法对应起来看。这里也是get来进行判断。

代码测试


public class ThreadLocalTest implements Runnable { private ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>(); @Override
public void run() {
threadLocal.set((int) (Math.random() * 100D)); try {
Thread.sleep(2000);
} catch (InterruptedException e) {
} System.out.println(threadLocal.get());
} public static void main(String[] args) throws Exception {
ThreadLocalTest sharedRunnableInstance = new ThreadLocalTest(); Thread thread1 = new Thread(sharedRunnableInstance);
Thread thread2 = new Thread(sharedRunnableInstance); thread1.start();
thread2.start(); thread1.join(); // wait for thread 1 to terminate
thread2.join(); // wait for thread 2 to terminate
}
}

总结

上面的例子,将ThreadLocalTest 实例传递給两个线程,执行run 方法这两个线程分别保存了不同的数据。如果不是访问ThreadLocal对象,第2个线程会覆盖掉第1个线程的值。ThreadLocal不是用来解决共享对象的多线程访问问题的,一般情况下,通过ThreadLocal.set() 到线程中的对象是该线程自己使用的对象,其他线程是不需要访问的,也访问不到的。各个线程中访问的是不同的对象。

ThreadLocal使用场景,多线程实例(每个线程对应一个实例)的对象的访问,并且这个对象很多地方都要用到。 ThreadLocal 也常用数据库连接操作。

参考