对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;
}
}
}
运行结果如下:
同运行结果可以看出:ThreadLocal用的是static final 修饰,所以3个线程打印的ThreadLocal对象是同一个。而ThreadLocal中存储的的TestObject对象,每个线程都是不同的对象。
简单了解ThreadLocal源码
通过set方法简单看下源码:
先通过当前线程获取ThreadLocalMap对象。如果没有,则创建ThreadLocalMap,将value存入。
观察创建ThreadLocalMap对象,createMap方法会创建一个ThreadLocalMap对象,并把该对象赋值给当前线程的成员变量threadLocals。可以想得到,这个地方每个线程调用set时,t都是不同的。
ThreadLocalMap的构造方法:会将ThreadLocal经过hash得到一个下标作为key,将value存储到ThreadLocalMap中的table中。这个地方需要注意的是,ThreadLocal<?> firstKey
针对于所有线程,都是同一个对象。但是看上一步赋值的时候,每个线程调用的t都是不同的。
每个线程持有一个ThreadLocalMap对象。每一个新的线程Thread都会实例化一个ThreadLocalMap并赋值给成员变量threadLocals,使用时若已经存在threadLocals则直接使用已经存在的对象。
借用ingxin大佬的总结:
1、对于某一ThreadLocal来讲,他的索引值i是确定的,在不同线程之间访问时访问的是不同的table数组的同一位置即都为table[i],只不过这个不同线程之间的table是独立的。
2、对于同一线程的不同ThreadLocal来讲,这些ThreadLocal实例共享一个table数组,然后每个ThreadLocal实例在table中的索引i是不同的。