Java并发之ThreadLocal简析

综述

从ThreadLocal的源码开始的注释上可以了解到如下内容:

These variables differ fromtheir normal counterparts in that each thread that accesses one (via its{@code get} or {@code set} method) has its own, independently initialized
copy of the variable. {@code ThreadLocal} instances are typically private static fields in classes that wish to associate state with a thread (e.g.,a user ID or Transaction ID).

也即是每个线程都有自己的共享变量,他是用来提供线程内部的共享变量。在多线程的环境下以保证不同线程变量间的相互独立和互不影响。通俗点说就是ThreadLocal能够保证当前线程使用的变量(不管在哪里只要是此线程在执行)是同一个(对这句话进行下面这个测试):

public class ThreadLocalTestUtil extends Thread {
    private ThreadLocal threadLocal = new ThreadLocal();

    @Override
    public void run() {
        super.run();
        for (int i = 0; i < 3; i++){
            threadLocal.set(i);
            System.out.println(Thread.currentThread().getName() + " " + threadLocal.get());
        }
    }
}

public class ThreadLocalTest {
    public static void main(String[] args) {
        new ThreadLocalTestUtil().start();
        new ThreadLocalTestUtil().start();
        new ThreadLocalTestUtil().start();
        new ThreadLocalTestUtil().start();
        new ThreadLocalTestUtil().start();
    }
}

结果如下:

Thread-1 0
Thread-1 1
Thread-1 2
Thread-0 0
Thread-0 1
Thread-4 0
Thread-4 1
Thread-3 0
Thread-4 2
Thread-2 0
Thread-2 1
Thread-2 2
Thread-0 2
Thread-3 1
Thread-3 2

可以看到,因为ThreadLocalTestUtil的run方法没有使用同步,多个线程执行时会存在一些问题,但是因为每个线程执行时都会创建属于自己的ThreadLocal实例,所以,运行结果不会出现混乱,结果正常打印。

1、ThreadLocal的实现原理(jdk-10)

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

get()方法会返回此线程局部变量的当前线程副本中的值,如果与此线程关联的ThreadLocal未被设置过值,则会调用 initialValue() 方法,如下:

protected T initialValue() {
        return null;
}

所以,当可以重载initIalValue()函数来设置初始值,如下:

public static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>() {
		@Override
		protected Integer initialValue() {
			return Integer.valueOf(1);
		}
	};

根据ThreadLocal的get()方法看下其实现方式

  • ①、获取当前线程
  • ②、获取当前线程的ThreadLocalMap
    getMap(t)方法如下:
ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
//对threadLocals的解释如下
//ThreadLocal.ThreadLocalMap threadLocals = null;
//与此线程有关的ThreadLocal值。 该映射由ThreadLocal类维护
  • ③、如果map不为null,则获取其value并返回,否则执行返回执行setInitialValue()方法的值,对线程的ThreadLocalMap对象进行初始化操作,ThreadLocalMap对象的key为ThreadLocal对象,value为initialValue()方法的返回值。

ThreadLocalMap类

ThreadLocalMap类是ThreadLocal的静态内部类。每个Thread维护一个ThreadLocalMap映射表,这个映射表的key是ThreadLocal实例本身,value是真正需要存储的Object。这样的设计主要有以下几点优势:

  • 这样设计之后每个Map的Entry数量变小了:之前是Thread的数量,现在是ThreadLocal的数量,能提高性能;
  • 当Thread销毁之后对应的ThreadLocalMap也就随之销毁了,能减少内存使用量。

Thead为每个线程维护了一个ThreadLocalMap,而ThreadLocalMap的key是Thread本身,value是要存储的对象,ThreadLocal的set()方法其实就是向ThreadLocalMap里设置值,get()则是从中取值。

Entry类是ThreadLocalMap类的内部类,继承自WeakReference类,我们得到下面这个对象间的引用结构图(实线为强引用,虚线为弱引用)
在这里插入图片描述
考虑以下问题:
ThreadLocalMap使用ThreadLocal的弱引用作为key,如果一个ThreadLocal没有外部关联的强引用,那么在虚拟机进行垃圾回收时,这个ThreadLocal会被回收,这样,ThreadLocalMap中就会出现key为null的Entry,这些key对应的value也就再无妨访问,但是value却存在一条从Current Thread过来的强引用链。因此只有当Current Thread销毁时,value才能得到释放。

该强引用链如下:

CurrentThread Ref -> Thread -> ThreadLocalMap -> Entry -> value

因此,只要这个线程对象被gc回收,那些key为null对应的value也会被回收,这样也没什么问题,但在线程对象不被回收的情况下,比如使用线程池的时候,核心线程是一直在运行的,线程对象不会回收,若是在这样的线程中存在上述现象,就可能出现内存泄露的问题。关于内存泄漏问题参阅:
Java并发编程之ThreadLocal详解


版权声明:本文为qq_43060759原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。