Bootstrap

一张图看懂Java中的ThreadLocal原理

--- JDK1.8 ---

一.什么是ThreadLocal?

它更确切的名字应该是“thread-local variable manager”——线程本地变量管理者。它用于实现线程间【数据隔离】,在线程中使用它存储的变量或数据,仅可在当前线程中访问使用,在其他线程中不可见。
其实,它仅仅是一个管理者,真正存储数据的不是它,而是一个叫做ThreadLocalMap的对象,每个线程都有一个自己的hreadLocalMap对象(在Thread中对象名为:threadLocals或者inheritableThreadLocals)。
 

二、结构原理示意图【划重点】

 

 图中,请注意颜色的对应关系。在主线程中创建了3个线程,和3个不同类型的ThreadLocal对象。分别在3个线程中对K1、K2、K3对象读取和设置数据后,3个线程的ThreadLocalMap中的数据如上图所示。代码如下:

        //主线程中
		ThreadLocal<String> k1 = new ThreadLocal<>();
		ThreadLocal<Integer> k2 = new ThreadLocal<>();
		ThreadLocal<Object> k3 = new ThreadLocal<>();
		k1.set("main thread");
		k2.set(1000);
		k3.set("hallo~");
		
		Thread thread1 = new Thread(new Runnable() {
			@Override
			public void run() {
				System.out.println("thread1:"+k1.get());
				System.out.println("thread1:"+k2.get());
				System.out.println("thread1:"+k3.get());

				k1.set("123");
				k2.set(1);
				k3.set("sss");

				System.out.println("thread1:"+k1.get());
				System.out.println("thread1:"+k2.get());
				System.out.println("thread1:"+k3.get());
				System.out.println();
			}
		});

		Thread thread2 = new Thread(new Runnable() {
			@Override
			public void run() {
				try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}

				System.out.println("thread2:"+k1.get());
				System.out.println("thread2:"+k2.get());
				System.out.println("thread2:"+k3.get());

				k1.set("abc");
				k2.set(2);
				k3.set(k3);

				System.out.println("thread2:"+k1.get());
				System.out.println("thread2:"+k2.get());
				System.out.println("thread2:"+k3.get());
				System.out.println();
			}
		});

		Thread thread3 = new Thread(new Runnable() {
			@Override
			public void run() {
				try {
					Thread.sleep(2000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}

				System.out.println("thread3:"+k1.get());
				System.out.println("thread3:"+k2.get());
				System.out.println("thread3:"+k3.get());

				k1.set("haha");
				k2.set(10);
				k3.set(new Object());

				System.out.println("thread3:"+k1.get());
				System.out.println("thread3:"+k2.get());
				System.out.println("thread3:"+k3.get());
				System.out.println();
			}
		});

		thread1.start();
		thread2.start();
		thread3.start();
		try {
			Thread.sleep(3000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("main:"+k1.get());
		System.out.println("main:"+k2.get());
		System.out.println("main:"+k3.get());

运行结果如下: 

thread1:null
thread1:null
thread1:null
thread1:123
thread1:1
thread1:sss

thread2:null
thread2:null
thread2:null
thread2:abc
thread2:2
thread2:java.lang.ThreadLocal@fb096c4

thread3:null
thread3:null
thread3:null
thread3:haha
thread3:10
thread3:java.lang.Object@68119265

main:main thread
main:1000
main:hallo~

Process finished with exit code 0

 

三、相关源码

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);
    }


    /**
     * Create the map associated with a ThreadLocal. Overridden in
     * InheritableThreadLocal.
     *
     * @param t the current thread
     * @param firstValue value for the initial entry of the map
     */
    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }


    /**
     * Construct a new map initially containing (firstKey, firstValue).
     * ThreadLocalMaps are constructed lazily, so we only create
     * one when we have at least one entry to put in it.
     */
    ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
        table = new Entry[INITIAL_CAPACITY]; //【初始值为16】
        int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
        table[i] = new Entry(firstKey, firstValue);
        size = 1;
        setThreshold(INITIAL_CAPACITY);
    }

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) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

 

四、注意:

1、存取数据时,ThreadLocal先按当前Thread对象查找到对应的hreadLocalMap,这就是线程之间的根本安全保障。
2、每个线程都有一个自己的hreadLocalMap对象(初始值为null),hreadLocalMap实际上就是一个Entry数组,初始化大小为16个空间,如果存满则2倍扩容。
3、Entry中的K值(this)是当前ThreadLocal对象,V值是要存储的对象(数据)。所以一个ThreadLocal对象在一个线程中最多存一个对象(数据)。
4、一个ThreadLocal对象可以在多个Thread中使用。
5、如果考虑父子线程的继承问题,需要使用InheritableThreadLocal,这里暂不讨论。

;