ThreadLocal 是JDK给我们提供的一个工具类,通过该类提供的方法可以操作变量,每个线程往ThreadLocal 中读写数据是线程隔离的,操作的都是线程本地内存里的变量,从而避免了线程安全的问题。
下面这段程序创建了一个ThreadLocal 对象countTL,重写了方法initialValue(当我们调用get方法时,如果还没有通过set方法设置值,它会返回initialValue方法的返回值)。在main方法里启动了两个线程,两个线程在循环里分别调用了increase方法,increase方法首先通过countTL.get()获取变量,然后++,在调用set方法设置新的值。
public class ThreadLocalDemo {
private static final ThreadLocal<Integer> countTL = new ThreadLocal<Integer>() {
protected Integer initialValue() {
return new Integer(0);
}
;
};
public int increase() {
//如果未执行set方法初始值为initialValue返回的值
Integer value = countTL.get();
value++;
//设置值
countTL.set(value);
return value;
}
public static void main(String[] args) {
ThreadLocalDemo d = new ThreadLocalDemo();
new Thread(new Runnable() {
@Override
public void run() {
for (; ; ) {
System.out.println(Thread.currentThread().getName() //
+ " " + d.increase());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}, "线程1").start();
new Thread(new Runnable() {
@Override
public void run() {
for (; ; ) {
System.out.println(Thread.currentThread().getName()//
+ " " + d.increase());
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}, "线程2").start();
}
}
运行结果:
通过运行结果,可以看出两个线程通过ThreadLocal 操作的变量是两个不同的变量,否则不可能出现递增这种现象。
虽然多个线程调用的是ThreadLocal 相同的方法,但是操作的却是线程自己的本地变量,ThreadLocal是如何做到的呢?
下面我们来看看ThreadLocal的内部结构
每个Thread线程内部都有一个ThreadLocalMap 变量,ThreadLocalMap 是ThreaLocal于内部实现的哈希表存储结构,和HashMap类似,但又不太相同。
ThreadLocal.ThreadLocalMap threadLocals
ThreadLocalMap 里的Entry对象的key就是ThreadLocal对象本身,value就是变量的值。当我们获取变量值得时候,首先可以通过Thread.currentThread()获取到当前线程对象,然后获取线程内部的ThreadLocalMap对象,然后调用ThreadLocalMap 的get方法就可以获取到值。同理设置值也是最后转换为对线程内部的ThreadLocalMap 的操作。
一个ThreadLocal只能存储一个变量,会产生一个Entry对象存储到ThreadLocalMap对象里,如果存储多个变量就需要多个ThreadLocal。
get方法:
public T get() {
//获取当前线程
Thread t = Thread.currentThread();
//获取当前线程的 threadLocals 变量
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;
}
}
//threadLocal为null则初始化当前线程的 threadLocals
return setInitialValue();
}
如果get方法获取map为null 或者 获取元素为变量为null,则会调用setInitialValue方法,如果map为null会创建map,并设置初始值,并返回。
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;
}
set方法:set方法也是先取得当前线程里的Map对象,然后操作map里的变量,如果map为null,同样会创建map。
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
remove方法:同样也是取得当前线程里的Map对象,然后调用map的方法操作变量。
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
ThreadLocalMap是ThreadLocal里的一个静态内部类,是采用线性探测的方式实现的哈希表,并不是像HashMap那样采用链地址法。ThreadLocal变量如果存在过多的化,极大的增加Hash冲突的可能,影响效率。
static class ThreadLocalMap {
/**
* The entries in this hash map extend WeakReference, using
* its main ref field as the key (which is always a
* ThreadLocal object). Note that null keys (i.e. entry.get()
* == null) mean that the key is no longer referenced, so the
* entry can be expunged from table. Such entries are referred to
* as "stale entries" in the code that follows.
*/
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
值得注意的是这里的Entry继承WeakReference,是一个弱引用。
弱引用:当垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。这里你可能会疑问:比如最上面的那个程序,当发生GC的时候,ThreadLocal对象会不会被回收掉,导致get的时候取不到值。实际上不会的,因为ThreadLocal对象还被countTL强引用引用着,只被弱引用关联的对象才会被回收掉。
如果ThreadLocal对象不在被强引用引用着,GC后Entry对象的key就会被回收掉,变为null,但是value是强引用,并不会被回收。所以使用完要调用一下remove方法,remove方法会调用ThreadLocalMap的remove方法,该方法里的expungeStaleEntry(i)会将key为null的值清除掉。
/**
* Remove the entry for key.
*/
private void remove(ThreadLocal<?> key) {
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)]) {
if (e.get() == key) {
e.clear();
expungeStaleEntry(i);
return;
}
}
}
来源:oschina
链接:https://my.oschina.net/u/3902132/blog/3103603