转载https://www.jianshu.com/p/ee8c9dccc953
T get() 返回此线程局部变量的当前线程副本中的值。
protected T initialValue()
返回此线程局部变量的当前线程的“初始值”,该方法不能显示调用,只有在第一次调用get()或者set()方法时才会被执行,并且仅执行1次。
void remove() 移除此线程局部变量当前线程的值。
void set(T value) 将此线程局部变量的当前线程副本中的值设置为指定值。
1.1ThreadLocal使用案例
案例
public class MyThread implements Runnable {
//创建任意锁对象
private Object obj = new Object();
private static final ThreadLocal dateFormat = new ThreadLocal() {
@Override
protected SimpleDateFormat initialValue() {
return new SimpleDateFormat("yyyy-MM-dd HH-mm-ss");
}
};
@Override
public void run() {
synchronized (obj){//
try {
System.out.println(Thread.currentThread().getName() + ":" + dateFormat.get().format(new Date()));
Thread.sleep(2000); //3个线程同时进来,都停在这里,线程1执行完,这时max=2
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
MyThread st = new MyThread();
// 创建三个线程对象
Thread t1 = new Thread(st, "线程1");
Thread t2 = new Thread(st, "线程2");
Thread t3 = new Thread(st, "线程3");
t1.start();
t2.start();
t3.start();
}
}
结果:
线程2:2018-10-27 18-06-58
线程1:2018-10-27 18-07-00
线程3:2018-10-27 18-07-02
案例2
public class MyThread2 implements Runnable {
//创建任意锁对象
private Object obj = new Object();
private static final ThreadLocal score = new ThreadLocal() {
@Override
protected Integer initialValue() {
return 0;
}
};
@Override
public void run() {
String threadName=Thread.currentThread().getName();
if(threadName.equals("线程1")){
score.set(99);
}else if(threadName.equals("线程2")){
score.set(98);
}else if(threadName.equals("线程3")){
score.set(97);
}
synchronized (obj){//
try {
System.out.println(Thread.currentThread().getName() + ":" + score.get());
Thread.sleep(1000); //3个线程同时进来,都停在这里,线程1执行完,这时max=2
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
MyThread2 st = new MyThread2();
// 创建三个线程对象
Thread t1 = new Thread(st, "线程1");
Thread t2 = new Thread(st, "线程2");
Thread t3 = new Thread(st, "线程3");
t1.start();
t2.start();
t3.start();
}
}
结果:
线程1:99
线程2:98
线程3:97
ThreadLocal源码
set操作
public void set(T value) {
//用当前线程对象的引用作为key来设置map
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
你会看到,set需要首先获得当前线程对象Thread的引用;
然后取出当前线程对象的成员变量ThreadLocalMap;
如果ThreadLocalMap存在,那么进行key/value设置,key就是当前线程对象的引用;
如果ThreadLocalMap没有,那么创建一个;
get操作
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
//this是ThreadLocal类的引用
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null)
return (T)e.value;
}
return setInitialValue();
}
通过当前线程对应的引用(key),获取ThreadLocalMap ,通过ThreadLocal类的引用,获取对应的Entry,
Entry的KEY就是当前线程的引用,VALUE就是值。
如果Entry不存在,就获取初始化值
initialValue()
protected T initialValue() {
return null;
}
该方法定义为protected级别且返回为null,很明显是要子类实现它的,所以我们在使用ThreadLocal的时候一般都应该覆盖该方法。
该方法不能显示调用,只有在第一次调用get()或者set()方法时才会被执行,并且仅执行1次。
3.1ThreadLocalMap
1ThreadLocalMap其内部利用Entry来实现key-value的存储,如下:
static class Entry extends WeakReference> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal> k, Object v) {
super(k);
value = v;
}
}
Entry的key就是ThreadLocal,而value就是值。同时,Entry也继承WeakReference,所以说Entry所对应key(ThreadLocal实例)的引用为一个弱引用
(关于弱引用博客:Java 理论与实践: 用弱引用堵住内存泄漏https://www.ibm.com/developerworks/cn/java/j-jtp11225/)
2ThreadLocalMap两个最核心的方法getEntry()、set(ThreadLocal> key, Object value)方法。
set(ThreadLocal> key, Object value)方法
private void set(ThreadLocal> key, Object value) {
ThreadLocal.ThreadLocalMap.Entry[] tab = table;
int len = tab.length;
// 根据 ThreadLocal 的散列值,查找对应元素在数组中的位置
int i = key.threadLocalHashCode & (len-1);
// 采用“线性探测法”,寻找合适位置
for (ThreadLocal.ThreadLocalMap.Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal> k = e.get();
// key 存在,直接覆盖
if (k == key) {
e.value = value;
return;
}
// key == null,但是存在值(因为此处的e != null),说明之前的ThreadLocal对象已经被回收了
if (k == null) {
// 用新元素替换陈旧的元素
replaceStaleEntry(key, value, i);
return;
}
}
// ThreadLocal对应的key实例不存在也没有陈旧元素,new 一个
tab[i] = new ThreadLocal.ThreadLocalMap.Entry(key, value);
int sz = ++size;
// cleanSomeSlots 清除陈旧的Entry(key == null)
// 如果没有清理陈旧的 Entry 并且数组中的元素大于了阈值,则进行 rehash
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
set()操作除了存储元素外,还有一个很重要的作用,就是replaceStaleEntry()和cleanSomeSlots(),这两个方法可以清除掉key == null 的实例,防止内存泄漏。在set()方法中还有一个变量很重要:threadLocalHashCode,定义如下:
private final int threadLocalHashCode = nextHashCode();
threadLocalHashCode应该是ThreadLocal的散列值,定义为final,表示ThreadLocal一旦创建其散列值就已经确定了,生成过程则是调用nextHashCode():
//nextHashCode :表示分配下一个ThreadLocal实例的threadLocalHashCode的值
private static AtomicInteger nextHashCode = new AtomicInteger();
//HASH_INCREMENT :表示分配两个ThradLocal实例的threadLocalHashCode的增量
private static final int HASH_INCREMENT = 0x61c88647;
private static int nextHashCode() {
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
nextHashCode:
getEntry()方法
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);
}
由于采用了开放定址法,所以当前key的散列值和元素在数组的索引并不是完全对应的,首先取一个探测数(key的散列值),如果所对应的key就是我们所要找的元素,则返回,否则调用getEntryAfterMiss(),如下:
private Entry getEntryAfterMiss(ThreadLocal> key, int i, Entry e) {
Entry[] tab = table;
int len = tab.length;
while (e != null) {
ThreadLocal> k = e.get();
if (k == key)
return e;
if (k == null)
expungeStaleEntry(i);//该方法用于处理key == null,有利于GC回收,能够有效地避免内存泄漏。
else
i = nextIndex(i, len);
e = tab[i];
}
return null;
}