ThreadLocal使用场景分析,实现原理
作者:binThreadLocal和Synchonized都是为了解决线程并发的问题,但是ThreadLocal和Synchonized是完全相反的
ThreadLocal是为每个线程提供变量的副本,自己私有的变量
Synchonized是控制共享的变量只能在某个时间被一个线程用到
1.使用场景
例如给线程设置一个threadID:
import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; public class DemoTask implements Runnable { // 包含要分配的下一个线程ID的原子整数 private static final AtomicInteger nextId = new AtomicInteger(0); // 包含每个线程的ID的ThreadLocal变量 private static final ThreadLocal threadId = new ThreadLocal() { @Override protected Integer initialValue() { return nextId.getAndIncrement();// 获取值,并递增 } }; public void run() { System.out.printf("Starting Thread: %s : %s\n", threadId.get()); try { TimeUnit.SECONDS.sleep((int) Math.rint(Math.random() * 10)); } catch (InterruptedException e) { e.printStackTrace(); } System.out.printf("Thread Finished: %s : %s\n", threadId.get()); } }
输出的内容如下:
Starting Thread: 0 Starting Thread: 1 Starting Thread: 2 Thread Finished: 0 Thread Finished: 1 Thread Finished: 2
为了对比,我们不使用,ThreadLocal变量创建线程id试试
import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; public class DemoTask implements Runnable { // 包含要分配的下一个线程ID的原子整数 private static final AtomicInteger nextId = new AtomicInteger(0); private static final Integer threadId = nextId.getAndIncrement(); public void run() { System.out.printf("Starting Thread: %s\n", threadId); try { TimeUnit.SECONDS.sleep((int) Math.rint(Math.random() * 10)); // startDate.remove(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.printf("Thread Finished: %s\n", threadId); } }
输出的内容如下:
Starting Thread: 0 Starting Thread: 0 Thread Finished: 0 Starting Thread: 0 Thread Finished: 0 Thread Finished: 0
2.实现原理
实际上ThreadLocal的所有变量,是保存在当前线程实例中,一个类似map的属性中
ThreadLocal.ThreadLocalMap threadLocals = null;
ThreadLocalMap跟当前线程绑定
Thread t = Thread.currentThread();
而这个ThreadLocalMap实际上是一个数组
Entry[] tab = table;
数组中存了Entry对象,Entry对象的key就是ThreadLocal,value是我们设置的值
static class Entry extends WeakReference<ThreadLocal<?>> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } }
ThreadLocalMap.set中通过对ThreadLocal的hash确定槽位
int i = key.threadLocalHashCode & (len-1);
ThreadLocalMap.set(ThreadLocal> key, Object value)代码如下:
public void set(T value) { //获取当前线程 Thread t = Thread.currentThread(); //从Thread.threadLocals中获取值 ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); } //map.set(this, value); private void set(ThreadLocal<?> key, Object value) { Entry[] tab = table; int len = tab.length; //确定hash槽位 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(); }
ThreadLocalMap.get()方法
public T get() { //获取当前线程 Thread t = Thread.currentThread(); //从Thread.threadLocals中获取值 ThreadLocalMap map = getMap(t); if (map != null) { //this指private static final ThreadLocal threadId = new ThreadLocal()对象 ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } return setInitialValue(); }
3.ThreadLocal中的弱引用
其中ThreadLocalMap中的Entry使用的是弱引用,这个很好理解,如果Entry强引用ThredLocal,那么他在线程执行完当前任务后,不会被回收,只会在线程被回时才会被回收。
static class Entry extends WeakReference<ThreadLocal<?>> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } }
4.ThreadLocal 内存泄漏的原因分析
我们来看下图内存模型currentThread强引用了threadLocalMap,threadLocalMap中Entry的,key弱引用了threadLoacl,Entry中value强引用了threadLocal对应的值
如果程序代码执行完毕,Entry的key-threadLocal因为是弱引用,就会被GC回收,这时threadLocalMap这个key-null,变成了null,但是对应的value因为还是强引用在threadLocalMap中,所以不会被GC回收,就会造成内存泄漏
ThreadLocalMap如何解决这个问题的呢?
在get、set、remove方法中都有将key为null的情况清理掉。
其中get调用expungeStaleEntry
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); else i = nextIndex(i, len); e = tab[i]; } return null; }
set中如果发现key是null,就直接调用replaceStaleEntry方法覆盖
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) { //值为null,将其覆盖,老的值被GC replaceStaleEntry(key, value, i); return; } } tab[i] = new Entry(key, value); int sz = ++size; if (!cleanSomeSlots(i, sz) && sz >= threshold) rehash(); }
remove中调用clear()方法清楚关联,同时调用expungeStaleEntry清理
/** * 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; } } }
如何避免泄露:
我们在使用ThreadLocal时,尽量在使用完后,手动去remove(),就可以有效的避免内存泄漏