一文搞定ThreadLocal、InheritableThreadLocal和ThreadLocalMap

应用场景

最近用到ThreadLocal,如下面方法调用 A -> B-> C -> … -> Z。此时可能会需要在Z获取A方法中的某个状态s,并且整个调用链中,s可能随时改变。如果通过修改接口的方式,可能改动较多,这个时候就可以尝试用ThreadLocal。

public class ThreadLocalAssistor {
	private static final ThreadLocal<Integer> STATE = new ThreadLocal<Integer>();
}
public void A(int a, int b) {
	ThreadLocalAssistor.STATE.set(1);
}
public void Z(String str) {
	Integer state = ThreadLocalAssistor.STATE.get();
}

这样是不是很简单

ThreadLocal

public class Thread implements Runnable {
	ThreadLocal.ThreadLocalMap threadLocals = null;
}
public class ThreadLocal<T> {
	// 获取线程的threadLocals变量
	ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
	
	public void set(T value) {
		// 获取当前线程并获取其threadLocals变量
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t); 
        // threadLocals不为空则设置值,否则为此线程创建threadLocals
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }
    // 这里道理和get相同
    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null)
                return (T)e.value;
        }
        return setInitialValue();
    }
}

总结:ThreadLocal本身不存储任何数据,而是去提供一个途径去获取线程的ThreadLocalMap,并对其进行值设置

ThreadLocalMap

static class ThreadLocalMap {
	private Entry[] table;
	// 其Entry并没有像HashMap那样是链表形式,只是普通的存储K,V
	static class Entry extends WeakReference<ThreadLocal> {
	    Object value;
	    Entry(ThreadLocal k, Object v) {
	        super(k);
	        value = v;
	    }
	}
	private void set(ThreadLocal key, Object value) {
       Entry[] tab = table;
       int len = tab.length;
       int i = key.threadLocalHashCode & (len-1);
       // 从第一个i开始,如果重新进入则会调用到tab中的下一个,形成一个环状的查询过程
       for (Entry e = tab[i];e != null;e = tab[i = nextIndex(i, len)]) {
           ThreadLocal k = e.get();
		   // 如果为当前entry的ThreadLocal则修改其值
           if (k == key) {
               e.value = value;
               return;
           }
           if (k == null) {
           	   // 如果k为空,表示对应的ThreadLocal已经被gc清理了,则替换掉,防止内存泄露
               replaceStaleEntry(key, value, i);
               return;
           }
       }
       // 如果没有,则在此位置新建一个Entry
       tab[i] = new Entry(key, value);
       int sz = ++size;
       if (!cleanSomeSlots(i, sz) && sz >= threshold)
           rehash();
   }
   // 如果到达table最后一个,让其返回0,从头再来
   private static int nextIndex(int i, int len) {
       return ((i + 1 < len) ? i + 1 : 0);
   }
}

总结:Entry继承WeakReference并指定ThreadLocal为弱引用,所以gc的时候会清理没有被引用的ThreadLocal,但是对应Entry则不会清理。如果不这么做,当一个Thread调用多个ThreadLocal的set方法,而将这些ThreadLocal = null后,那么上面方法中 if (k == null) 的情况是不会出现的,因为引用关系如下 ThreadLocal -> Entry -> ThreadLocalMap -> Thread,所以Entry中的value会一直保留,会导致内存泄露。

InheritableThreadLocal

InheritableThreadLocal可以获取父线程中的ThreadLocal的值

public class InheritableThreadLocal<T> extends ThreadLocal<T> {
    protected T childValue(T parentValue) {
        return parentValue;
    }
    ThreadLocalMap getMap(Thread t) {
       return t.inheritableThreadLocals;
    }
    void createMap(Thread t, T firstValue) {
        t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
    }
}

可见InheritableThreadLocal继承于ThreadLocal,并且操作线程的inheritableThreadLocals对象

ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc) {
    // ......
    Thread parent = currentThread();
    if (parent.inheritableThreadLocals != null)
        this.inheritableThreadLocals =
            ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
    // ......
}
static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
    return new ThreadLocalMap(parentMap);
}

总结:线程创建的时候会获取父线程的inheritableThreadLocals,并以此创建自己的inheritableThreadLocals,来达到数据传递的作用。并且子线程在初始化获取父线程的数据后,后续对inheritableThreadLocals的操作都是他们独立了。

public static void main(String[] args) throws InterruptedException {
    final InheritableThreadLocal<String> local = new InheritableThreadLocal<>();
    local.set("1");
    new Thread(new Runnable() {
        @Override
        public void run() {
            System.out.println("in "+ local.get());
            local.set("2");
            System.out.println("in set "+ local.get());
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("in "+ local.get());
            local.set("4");
            System.out.println("in set "+ local.get());
        }
    }).start();
    Thread.sleep(100);
    local.set("3");
    System.out.println("main set "+ local.get());
    Thread.sleep(100);
    System.out.println("main get "+ local.get());
}
// 结果如下:
in 1
in set 2
main set 3
in 2
in set 4
main get 3
// 对应的引用关系如下,只有继承的时候引用了父线程的值,后面的操作两者就断开联系了
父线程 -> 1 -> 3
		^	
		|
		子线程 - > 2 -> 4
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值