1、ThreadLocal 简介
ThreadLocal是一个全局对象,ThreadLocal是线程范围内变量共享的解决方案;ThreadLocal可以看作是一个map集合,key就是当前线程,value就是要存放的变量。
ThreadLocal与Synchronized的区别
ThreadLocal其实是与线程绑定的一个变量。ThreadLocal和Synchonized都用于解决多线程并发访问。
ThreadLocal | Synchronized |
---|---|
用于线程间的数据隔离 | 用于线程间的数据共享 |
为每一个线程都提供一个副本threadLocals | 利用锁机制使得变量和代码块在某一时刻只能被一个线程访问 |
每个线程在某一时刻访问到的并不是同一个对象,而是副本threadLocals | 多个线程间通信时能够获得数据共享 |
老版本的ThreadLocal 里面维护了一个散列表,key是Thread,value是我们要存放的东西
新版本里面没有散列表了,而是每个线程里有一个threadLocals的字段,类型是ThreadLocal.ThreadLocalMap(里面有一个tables数组,这个tables数组里面放的就是我们存的对象)。
static class ThreadLocalMap {
private Entry[] table;
}
table 是 Entry 类型的,它的每个Entry的key都是一个弱引用;这样设计的好处是,如果key这个ThreadLocal 不再被其他对象使用时,可以自动回收这个ThreadLocal对象,避免可能的内存泄露(注意,Entry中的value,依然是强引用)
staticclass Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
ThreadLocal 里面有不同线程存数据,每个线程都有一个Map,这个Map的key-value 就是ThreadLocal 和存的数据(有一点绕 )
当我们在线程t1 调用ThreadLocal 的get() 方法时,我们会调用t.getMap()方法得到线程t 的threadLocals,如果线程的threadLocals是空,我们会先初始化它 调用initialValue()方法
- ThreadLocal 的引用变量用完后会自动给你销毁,而不用考虑ThreadLocal中的变量会占用空间。
- 在ThreadLocal类中有一个Map,用于存储每一个线程的变量副本,Map中元素的key为线程对象,value为对应的线程的变量副本。
- ThreadLocal的作用:
- ThreadLocal通过为每一个线程提供一个独立的变量副本解决了变量并发访问的冲突问题。
- 传递数据:可以通过ThreadLocal在同一线程的不同组件中传递公共变量。
- 线程隔离:每个线程的变量都是独立的,不会相互影响。
2、入门案例
Demo1
ThreadLocal 的使用十分简单,在本线程放一个数,只有在本线程才能 取出,在其它线程是取不到的。
public static void main(String[] args) throws InterruptedException {
ThreadLocal<Integer> threadLocal = new ThreadLocal<>();
new Thread(new Runnable() {
@Override
public void run() {
threadLocal.set(123);
System.out.println(threadLocal.get());
}
}).start();
Thread.sleep(1000);
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(threadLocal.get());
}
}).start();
}
执行结果:
123
null
Demo2
ThreadLocal通过为每一个线程提供一个独立的变量副本解决了变量并发访问的冲突问题。
public class ThreadLocalTest {
/**
* 原子整数,一个分配给线程的 Thread ID
*/
private static final AtomicInteger nextId = new AtomicInteger(0);
/**
* 每一个线程对应一个 Thread ID
*/
private static final ThreadLocal<Integer> threadId = new ThreadLocal<Integer>() {
/**
* 初始化一个nextId的值
* @return
*/
@Override
protected Integer initialValue() {
// nextId自增
return nextId.getAndIncrement();
}
};
/**
* 返回当前线程对应的Thread ID,必要时会进行分配!
*
* @return
*/
public static int get() {
return threadId.get();
}
static class RunnableTask implements Runnable {
@Override
public void run() {
try {
System.out.println(
"当前线程名称为:" + Thread.currentThread().getName() +
",分配的Id为:" + threadId.get()
);
} finally {
threadId.remove();
}
}
}
// 测试程序
public static void main(String[] args) throws InterruptedException {
RunnableTask task = new RunnableTask();
Thread t1 = new Thread(task,"线程1");
t1.start();
TimeUnit.MILLISECONDS.sleep(100);
Thread t2 = new Thread(task,"线程2");
t2.start();
TimeUnit.MILLISECONDS.sleep(100);
Thread t3 = new Thread(task,"线程3");
t3.start();
TimeUnit.MILLISECONDS.sleep(100);
Thread t4 = new Thread(task,"线程4");
t4.start();
TimeUnit.MILLISECONDS.sleep(100);
Thread t5 = new Thread(task,"线程5");
t5.start();
TimeUnit.MILLISECONDS.sleep(100);
}
}
执行结果:
当前线程名称为:线程1,分配的Id为:0
当前线程名称为:线程2,分配的Id为:1
当前线程名称为:线程3,分配的Id为:2
当前线程名称为:线程4,分配的Id为:3
当前线程名称为:线程5,分配的Id为:4
3、源码分析
成员属性
//线程获取ThreadLocal.get() 时,如果是第一次在某个ThreadLocal对象上get时,会给当前线程分配 一个value
// 这个value 和 当前的 ThreadLocal对象被包装成为一个entry,其中key是ThreadLocal对象,value是ThreadLocal对象给当前线程生成的value
//这个entry 存放到 当前线程 ThreadLocals 这个map的哪个桶位?与当前ThreadLocal对象的threadLocalHashCode 有关系
// 使用threadLocalHashCode & (table.length-1) 的位置就是当前entry需要存放的位置
private final int threadLocalHashCode = nextHashCode();
/**
* 创建ThreadLocal 对象时 会使用到,每创建一个ThreadLocal对象 就会使用nextHashCode 分配一个hash值给这个对象
*/
private static AtomicInteger nextHashCode =
new AtomicInteger();
/**
* 每创建一个ThreadLocal 对象,这个ThreadLocal.nextHashCode 就会增长0x61c88647
* 这个值非常特殊,它是斐波那契额 也叫 黄金分割,hash增量为这个数字,带来的好处就是hash分布非常均匀
*/
private static final int HASH_INCREMENT = 0x61c88647;
/**
* 创建新的ThreadLocal 对象时,会给当前对象分配一个hash,使用这个方法
*/
private static int nextHashCode() {
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
构造方法
public ThreadLocal() {
}
get()方法
/**
* 返回当前线程与当前ThreadLocal对象相关联的 线程局部变脸,这个变量只有当前线程能够访问到。
* 如果当前线程没有分配,则给当前线程分配(使用initialValue方法)
*/
public T get() {
// 获取当前线程
Thread t = Thread.currentThread();
//获取当前线程Thread对象的 threadLocals map引用
ThreadLocalMap map = getMap(t);
//条件成立:说明当前线程已经拥有自己的 ThreadLocalMap对象了
if (map != null) {
//key 是当前ThreadLocal对象
//调用map.getEntry() 方法 获取ThreadLocalMap 中该threadLocal关联的entry,
ThreadLocalMap.Entry e = map.getEntry(this);
//条件成立:说明当前线程 初始化过 与当前threadLocal 对象相关联的线程局部变量
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
//返回value
return result;
}
}
//执行到这里有几种情况?
//1.当前线程对应的threadLocalMap是空
//2.当前线程与当前 threadLocal对象没有生成过相关联的线程局部变量
//setInitialValue 方法初始化当前线程与当前 threadLocal对象 相关联的value
//且 当前线程如果没有 threadLocalMap的话,还会初始化创建map
return setInitialValue();
}
ThreadLocalMap getMap(Thread t) {
//返回当前线程的 threadLocals
return t.threadLocals;
}
/**
* setInitialValue 方法初始化当前线程与当前 threadLocal对象 相关联的value
* 且 当前线程如果没有 threadLocalMap的话,还会初始化创建map
*/
private T setInitialValue() {
//调用的当前ThreadLocal对象的 initialValue方法,这个方法 大部分情况下咱们都会重写
//value 就是当前ThreadLocal 对象与当前线程相关联的 线程局部变量
T value = initialValue();
//获取当前线程对象
Thread t = Thread.currentThread();
//获取当前线程内部的 threadLocals ThreadLocalMap对象
ThreadLocalMap map = getMap(t);
//条件成立:说明当前线程内部已经初始化过 threadLocalMap对象了。
if (map != null)
//保存当前threadLocal 与当前线程生成的 线程局部变量
//key:当前threadLocal 对象 value:线程与当前threadLocal相关的局部变量
map.set(this, value);
else
//执行到这里,说明当前线程内部还未初始化 threadLocalMap,这里调用createMap 给当前线程创建map
//参数1:当前线程,参数2:当前线程与当前threadLocal相关的局部变量
createMap(t, value);
//返回 当前线程与当前threadLocal相关的局部变量
return value;
}
/**
* 默认返回null,一般情况下,咱们都是需要重写这个方法
*/
protected T initialValue() {
return null;
}
void createMap(Thread t, T firstValue) {
//传递t的意义就是 要访问当前线程 t.threadLocals 字段,给这字段初始化
// new ThreadLocalMap(this, firstValue)
//创建一个ThreadLocalMao对象 初始值k-v为:this:当前threadLocal对象,firstValue:当前线程与当前threadLocal相关的局部变量
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
set()方法
/**
* 修改当前线程与当前threadLocal对象相关联的 线程局部变量
*/
public void set(T value) {
//获取当前线程
Thread t = Thread.currentThread();
//获取当前线程的ThreadLocalMap对象
ThreadLocalMap map = getMap(t);
//条件成立:说明当前线程的ThreadLocalMap 已经初始化过了
if (map != null)
//调用threadLocalMap.set 方法 进行重写 或者 添加
map.set(this, value);
else
//执行到这里说明当前线程还未创建 ThreadLocalMap对象
//参数1:当前线程, 参数2:当前线程与当前threadLocal相关的局部变量
createMap(t, value);
}
ThreadLocalMap getMap(Thread t) {
//返回当前线程的 threadLocals
return t.threadLocals;
}
void createMap(Thread t, T firstValue) {
//传递t的意义就是 要访问当前线程 t.threadLocals 字段,给这字段初始化
// new ThreadLocalMap(this, firstValue)
//创建一个ThreadLocalMao对象 初始值k-v为:this:当前threadLocal对象,firstValue:当前线程与当前threadLocal相关的局部变量
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
remove() 方法
/**
* 移除当前线程与当前ThreadLocal对象相关联的 线程局部变量
*/
public void remove() {
//获取当前线程的ThreadLocalMap对象
ThreadLocalMap m = getMap(Thread.currentThread());
//条件成立:说明当前线程已经初始化过 ThreadLocalMap对象
if (m != null)
//调用ThreadLocalMap.remove(key = 当前threadLocal)
m.remove(this);
}
ThreadLocalMap getMap(Thread t) {
//返回当前线程的 threadLocals
return t.threadLocals;
}
4、总结
ThreadLocal可以做到多线程下资源的根本隔离
ThreadLocal是作为 当前线程Thread 中属性 ThreadLocalMap集合中的某个Entry的key值(Entry(threadLocal,value)),虽然不同的线程Thread的 ThreadLocalMao集合中的Entry中的key值(ThreadLocal)是一样的,但是不同线程拥有独一无二的 ThreadLocalMap集合,也就是不同线程Thread的ThreadLocalMap中相同key对应存储的value 是不一样的,从而达到了线程间变量隔离的目的,但是同一线程中这个value变量地址是一样的。