对ThreadLocal的理解

个人理解: 用一个ThreadLocal可以用来为每个线程独立存储一个对象,让每个线程都有唯一的副本。每一个线程读取的变量是对应的互相独立的。通过get和set方法就可以得到当前线程对应的值。

如果单单从使用的角度可以理解为(实际不是这样):
ThreadLocal相当于维护了一个map,key就是当前的线程,value就是需要存储的对象。

实际存储结构是:
线程Thread中维护了一个ThreadLocalMap对象,ThreadLocalMap对象是以ThreadLocal作为key,存储数据。一个Thread中可能会定义多个ThreadLocal对象。


使用ThreadLocal

代码如下:

package com.study.threadLocal使用;

import java.util.Random;

import org.apache.commons.lang3.StringUtils;

public class TestThreadLocal2 implements Runnable
{
	
    private static final ThreadLocal<TestObject> formatter = ThreadLocal.withInitial(() -> new TestObject());
    /*
     *                          ||
     */
    private static final ThreadLocal<TestObject> formatter2 = new ThreadLocal<TestObject>(){
        @Override
        protected TestObject initialValue()
        {
        	TestObject obj = new TestObject();
            return obj;
        }
    };
    
    public static void main(String[] args) throws InterruptedException {
        TestThreadLocal2 obj = new TestThreadLocal2();
        // 启动3个线程,线程名称分别为 0,1 ,2
        for(int i=0 ; i<3; i++){
            Thread t = new Thread(obj, ""+i);
            Thread.sleep(new Random().nextInt(1000));
            t.start();
        }
    }

    @Override
    public void run() {
    	String name = Thread.currentThread().getName();
    	// 给线程名称为0和1的线程的ThreadLocal中设置一个对象
    	if(StringUtils.equals(name, "0")){
    		TestObject obj = formatter.get();
    		obj.setName("小白");
            formatter.set(obj);
    	}
    	if(StringUtils.equals(name, "1")){
            TestObject obj = formatter.get();
    		obj.setName("小红");
            formatter.set(obj);
    	}
        System.out.println("ThreadLocal对象:"+formatter);
        System.out.println("Thread Name= "+name+" formatter = "+formatter.get());
        System.out.println();
    }
    
    
    static class TestObject {
    	private String name;

    	public String getName() {
    		return name;
    	}

    	public void setName(String name) {
    		this.name = name;
    	}
    }
}

运行结果如下:

java里Thread的内容不执行了 java中threadlocal_System


同运行结果可以看出:ThreadLocal用的是static final 修饰,所以3个线程打印的ThreadLocal对象是同一个。而ThreadLocal中存储的的TestObject对象,每个线程都是不同的对象。


简单了解ThreadLocal源码

通过set方法简单看下源码:

先通过当前线程获取ThreadLocalMap对象。如果没有,则创建ThreadLocalMap,将value存入。

java里Thread的内容不执行了 java中threadlocal_赋值_02


观察创建ThreadLocalMap对象,createMap方法会创建一个ThreadLocalMap对象,并把该对象赋值给当前线程的成员变量threadLocals。可以想得到,这个地方每个线程调用set时,t都是不同的。

java里Thread的内容不执行了 java中threadlocal_赋值_03


ThreadLocalMap的构造方法:会将ThreadLocal经过hash得到一个下标作为key,将value存储到ThreadLocalMap中的table中。这个地方需要注意的是,ThreadLocal<?> firstKey针对于所有线程,都是同一个对象。但是看上一步赋值的时候,每个线程调用的t都是不同的。

java里Thread的内容不执行了 java中threadlocal_java_04


每个线程持有一个ThreadLocalMap对象。每一个新的线程Thread都会实例化一个ThreadLocalMap并赋值给成员变量threadLocals,使用时若已经存在threadLocals则直接使用已经存在的对象。


借用ingxin大佬的总结:

1、对于某一ThreadLocal来讲,他的索引值i是确定的,在不同线程之间访问时访问的是不同的table数组的同一位置即都为table[i],只不过这个不同线程之间的table是独立的。
2、对于同一线程的不同ThreadLocal来讲,这些ThreadLocal实例共享一个table数组,然后每个ThreadLocal实例在table中的索引i是不同的。