深入理解ThreadLocal
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了深入理解ThreadLocal相关的知识,希望对你有一定的参考价值。
ThreadLocal是什么:
- ThreadLocal翻译过来是本地线程,但它却不是线程,只是保存线程的自己使用的变量
- ThreadLocal是线程封闭的一种实现,什么是线程封闭呢,线程封闭就是将某个对象封闭在一个线程中,使用这种方式将自动实现线程安全性,即使被封闭的对象本身不是线程安全的。假如你有5个线程都要使用变量x所表示的对象,那线程本地存储就会生成5个用于x的不同的存储块。就不会产生竞态状态了
简单例子
一个实体类,里面有一个Threadlocal类
只能通过get和set方法去访问该对象的值
public class MyNumber {
private static ThreadLocal<Integer> number = new ThreadLocal<Integer>() {
protected Integer initialValue() {
return 0;
}
};
public Integer getNext() {
number.set(number.get() + 1);
return number.get();
}
public static void main(String[] args) throws InterruptedException {
MyNumber myNumber = new MyNumber();
ExecutorService es = Executors.newCachedThreadPool();
for (int i = 0; i < 2; i++) {
es.execute(new MyThread(myNumber));
}
TimeUnit.SECONDS.sleep(3);
es.shutdownNow();
}
}
线程类:MyThread,其里面有一个属性,MyNumber,在run方法里,对LocalThread里保存的值其进行加1操作
public class MyThread implements Runnable {
private MyNumber myNumber;
public MyThread(MyNumber myNumber) {
this.myNumber = myNumber;
}
@Override
public void run() {
for(int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName()
+ "-number: " + myNumber.getNext());
}
}
}
输出结果:
pool-1-thread-2-number: 1
pool-1-thread-1-number: 1
pool-1-thread-2-number: 2
pool-1-thread-2-number: 3
pool-1-thread-2-number: 4
pool-1-thread-2-number: 5
pool-1-thread-1-number: 2
pool-1-thread-1-number: 3
pool-1-thread-1-number: 4
pool-1-thread-1-number: 5
创建了两个线程,都将调用mynumber的getNext()方法,但两个线程的操作互不影响。
从输出结果可以看出:确实为每个线程通过ThreadLocal为每个线程创建了一个Integer的存储区域,通过set和get去设置和获得这个值。
ThreadLocal是如何实现线程封闭的
源码中,ThreadLocal是泛型类的,每个线程都将会为这个T类型的值创建存储区域
构造函数:是一个默认构造函数
public ThreadLocal() {}
在MyNumber类中,初始化ThreadLocal时重写了initialValue()方法,因为该方法默认返回null
protected T initialValue() {
return null;
}
对于基本类型包装类我们肯定不想返回值是null,所以进行了重写
-
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(); }
在获取值的时候,首先要获得当前的线程,以便确定是针对哪个线程获取其中的ThreadLocal保存的值。通过getMap方法获取键值对,获得其中的value值,如果值不等于null,则进行强制转化以后返回,为空,则对其进行初始化一次,setInitialValue()的代码如下
private T setInitialValue() { T value = initialValue(); Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); return value; }
其初始化的值仍为initialValue()方法返回的值,这里如果map为空,则会创建一个集合。
getMap方法:
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
Thread类中:
ThreadLocal.ThreadLocalMap threadLocals = null;
说明每一个线程类中都有threadLocals,getMap方法获得当前线程的的threadsLocals的值,是一个ThreadLocalMap类
重点讲解一下ThreadLocal中的Map操作。
讲Map的时候先讲一下ThreadLocalMap的静态内部类Entry,继承了WeakReference,在无法使用它时,垃圾收集器会将其回收。里面有一个value的属性,用于保存线程的值。
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
}
它的super是WeakReference,最终将调用Reference这个类的构造函数。这个类主要是用来为Entry设置一个value值,并调用Refernce构造函数。
获得Entry的方法也是通过i
private Entry getEntry(ThreadLocal<?> key) {
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
if (e != null && e.get() == key)
return e;
else
return getEntryAfterMiss(key, i, e);
}
}
ThreadLocalMap是ThreadLocal的静态内部类
其中一个构造函数:
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY];
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
}
首先确定了Entry[]数组的大小,通过与运算获得value存储在数组中的第几p的实现方式有点类似
get方法中首先通过getMap方法获得对应的ThreadLocalMap,并通过getEntry()方法获得Entry对象,从而获得value的值
set方法
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
set方法里的核心方法应该是ThreadMap类中的set方法
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
if (k == key) {
e.value = value;
return;
}
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
该段的意思是首先通过计算hash并做与运算,计算出存储位置,通过存储位置获得value,如果value不为空,则重置value的值,否则table[i]的位置则为Entry
因为getMap获取的是当前线程的threadLocals,其初始时值为0,所以set时,先调用 createMap(t, value)方法:
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this,firstValue);
}
为当前线程的threadLocals进行初始化,传入了一个threadLocal对象和初始化值。
- 总结
ThreadLocal实现为使用相同变量的每个线程创建一个存储空间是通过Thread类的threadLocals实现的。因为每个线程都有一个ThreadLocal.ThreadLocalMap,所以可以做到独立存储。当调用set方法时,首先会获得当前线程类的threadLocals并将其存储到map引用中,第一次调用set时,因为threadLocals初始化值为null,所以先调用creatMap(t,value)方法初始化当前线程的threadLocals(初始化做了什么:通过一个类似于hashMap实现存储的计算获取value在Entry数组中的存储位置),所以当第二次调用set方法时,threadLocals不为空,将调用map.set(this, value)方法,如果通过计算原来Entry[i]上有值,则将替换这上面的值,否则就进行赋值。
对于get方法,首先通过获得getMap()方法获得threadLocals的值,一般之前会进行set方法的调用,map不为null,通过getEntry()方法返回保存value的Entry对象,从而获得value值,如果未为进行set方法的调用,则会返回initialValue()方法返回的初始化值。总之,保存和获取都是通过threadLocal实现的。可以将ThreadLocal视为一个Thread-T键值对象,但实际上保存在Thread对象中,当线程终止以后,这些值i将会作为垃圾回收,因为保存value的Entry类继承了WeakReference
以上是关于深入理解ThreadLocal的主要内容,如果未能解决你的问题,请参考以下文章