老套路,先列举下关于ThreadLocal常见的疑问,但愿能够经过这篇学习笔记来解决这几个问题:html
ThreadLocal是线程局部变量,和普通变量的不一样在于:每一个线程持有这个变量的一个副本,能够独立修改(set方法)和访问(get方法)这个变量,而且线程之间不会发生冲突。java
类中定义的ThreadLocal实例通常会被private static
修饰,这样可让ThreadLocal实例的状态和Thread绑定在一块儿,业务上,通常用ThreadLocal包装一些业务ID(user ID或事务ID)——不一样的线程使用的ID是不相同的。面试
从某个角度来看,ThreadLocal为Java并发编程提供了额外的思路——避免并发,若是某个对象自己是非线程安全的,可是你想实现多线程同步访问的效果,例如SimpleDateFormat,你可使用ThreadLocal变量。apache
public class Foo { // SimpleDateFormat is not thread-safe, so give one to each thread private static final ThreadLocal<SimpleDateFormat> formatter = new ThreadLocal<SimpleDateFormat>(){ @Override protected SimpleDateFormat initialValue() { return new SimpleDateFormat("yyyyMMdd HHmm"); } }; public String formatIt(Date date) { return formatter.get().format(date); } }
注意,这里针对每一个线程只须要初始化一次SimpleDateFormat对象,其实跟在自定义线程中定义一个SimpleDateFormat成员变量,并在线程初始化的时候new这个对象,效果是同样的,只是这样看起来代码更规整。编程
以前在yunos作酷盘项目的数据迁移时,咱们须要按照用户维度去加锁,每一个线程在处理迁移以前,都须要先获取当前用户的锁,每一个锁的key是带着用户信息的,所以也可使用ThreadLocal变量实现:后端
下面这个例子,咱们定义了一个MyRunnable对象,这个MyRunnable对象会被线程1和线程2使用,可是经过内部的ThreadLocal变量,每一个线程访问到的整数都是本身单独的一份。缓存
package org.java.learn.concurrent.threadlocal; /** * @author duqi * @createTime 2018-12-29 23:25 **/ public class ThreadLocalExample { public static class MyRunnable implements Runnable { private ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>(); @Override public void run() { threadLocal.set((int) (Math.random() * 100D)); try { Thread.sleep(2000); } catch (InterruptedException e) { } System.out.println(threadLocal.get()); } } public static void main(String[] args) throws InterruptedException { MyRunnable sharedRunnableInstance = new MyRunnable(); Thread thread1 = new Thread(sharedRunnableInstance); Thread thread2 = new Thread(sharedRunnableInstance); thread1.start(); thread2.start(); thread1.join(); //wait for thread 1 to terminate thread2.join(); //wait for thread 2 to terminate } }
ThreadLocal是如何被线程使用的?原理以下图所示:Thread引用和ThreadLocal引用都在栈上,Thread引用会引用一个ThreadLocalMap对象,这个map中的key是ThreadLocal对象(使用WeakReference包装),value是业务上变量的值。安全
首先看java.lang.Thread
中的代码:数据结构
public class Thread implements Runnable { //......其余源码 /* ThreadLocal values pertaining to this thread. This map is maintained by the ThreadLocal class. */ ThreadLocal.ThreadLocalMap threadLocals = null; /* * InheritableThreadLocal values pertaining to this thread. This map is maintained by the InheritableThreadLocal class. */ ThreadLocal.ThreadLocalMap inheritableThreadLocals = null; //......其余源码
Thread中的threadLocals变量指向的是一个map,这个map就是ThreadLocal.ThreadLocalMap,里面存放的是跟当前线程绑定的ThreadLocal变量;inheritableThreadLocals的做用相同,里面也是存放的ThreadLocal变量,可是存放的是从当前线程的父线程继承过来的ThreadLocal变量。多线程
在看java.lang.ThreadLocal
类,主要的成员和接口以下:
withInitial方法,Java 8之后用于初始化ThreadLocal的一种方法,在外部调用get()方法的时候,会经过Supplier肯定变量的初始值;
public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) { return new SuppliedThreadLocal<>(supplier); }
get方法,获取当前线程的变量副本,若是当前线程尚未建立该变量的副本,则须要经过调用initialValue
方法来设置初始值;get方法的源代码以下,首先经过当前线程获取当前线程对应的map,若是map不为空,则从map中取出对应的Entry,而后取出对应的值;若是map为空,则调用setInitialValue设置初始值;若是map不为空,当前ThreadLocal实例对应的Entry为空,则也须要设置初始值。
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(); }
set方法,跟get方法同样,先获取当前线程对应的map,若是map为空,则调用createMap建立map,不然将变量的值放入map——key为当前这个ThreadLocal对象,value为变量的值。
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方法,删除当前线程绑定的这个副本
public void remove() { ThreadLocalMap m = getMap(Thread.currentThread()); if (m != null) m.remove(this); }
/** * The difference between successively generated hash codes - turns * implicit sequential thread-local IDs into near-optimally spread * multiplicative hash values for power-of-two-sized tables. */ private static final int HASH_INCREMENT = 0x61c88647;
InheritableThreadLocal主要用于子线程建立时,须要自动继承父线程的ThreadLocal变量,实现子线程访问父线程的threadlocal变量。InheritableThreadLocal继承了ThreadLocal,并重写了childValue、getMap、createMap三个方法。
public class InheritableThreadLocal<T> extends ThreadLocal<T> { /** * 建立线程的时候,若是须要继承且父线程中Thread-Local变量,则须要将父线程中的ThreadLocal变量一次拷贝过来。 */ protected T childValue(T parentValue) { return parentValue; } /** * 因为重写了getMap,因此在操做InheritableThreadLocal变量的时候,将只操做Thread类中的inheritableThreadLocals变量,与threadLocals变量没有关系 **/ ThreadLocalMap getMap(Thread t) { return t.inheritableThreadLocals; } /** * 跟getMap相似,set或getInheritableThreadLocal变量的时候,将只操做Thread类中的inheritableThreadLocals变量 */ void createMap(Thread t, T firstValue) { t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue); } }
关于childValue多说两句,拷贝是如何发生的?
首先看Thread.init方法,
private void init(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc, boolean inheritThreadLocals) { //其余源码 if (inheritThreadLocals && parent.inheritableThreadLocals != null) this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals); /* Stash the specified stack size in case the VM cares */ this.stackSize = stackSize; /* Set thread ID */ tid = nextThreadID(); }
而后看ThreadLocal.createInheritedMap方法,最终会调用到newThreadLocalMap方法,这里InheritableThreadLocal对childValue作了重写,能够看出,这里确实是将父线程关联的ThreadLocalMap中的内容依次拷贝到子线程的ThreadLocalMap中了。
private ThreadLocalMap(ThreadLocalMap parentMap) { Entry[] parentTable = parentMap.table; int len = parentTable.length; setThreshold(len); table = new Entry[len]; for (int j = 0; j < len; j++) { Entry e = parentTable[j]; if (e != null) { @SuppressWarnings("unchecked") ThreadLocal<Object> key = (ThreadLocal<Object>) e.get(); if (key != null) { Object value = key.childValue(e.value); Entry c = new Entry(key, value); int h = key.threadLocalHashCode & (len - 1); while (table[h] != null) h = nextIndex(h, len); table[h] = c; size++; } } } }
ThreadLocalMap中的key是ThreadLocal对象,而后ThreadLocal对象时被WeakReference包装的,这样当没有强引用指向该ThreadLocal对象以后,或者说Map中的ThreadLocal对象被断定为弱引用可达时,就会在垃圾收集中被回收掉。看下Entry的定义:
static class Entry extends WeakReference<ThreadLocal<?>> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } }
ThreadLocal对象的生命周期跟线程的生命周期同样长,那么若是将ThreadLocal对象和线程池一块儿使用,就可能会遇到这种状况:一个线程的ThreadLocal对象会和其余线程的ThreadLocal对象串掉,通常不建议将二者一块儿使用。
我从Dubbo中找到了ThreadLocal的例子,它主要是用在请求缓存的场景,具体代码以下:
@Activate(group = {Constants.CONSUMER, Constants.PROVIDER}, value = Constants.CACHE_KEY) public class CacheFilter implements Filter { private CacheFactory cacheFactory; public void setCacheFactory(CacheFactory cacheFactory) { this.cacheFactory = cacheFactory; } @Override public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException { if (cacheFactory != null && ConfigUtils.isNotEmpty(invoker.getUrl().getMethodParameter(invocation.getMethodName(), Constants.CACHE_KEY))) { Cache cache = cacheFactory.getCache(invoker.getUrl(), invocation); if (cache != null) { String key = StringUtils.toArgumentString(invocation.getArguments()); Object value = cache.get(key); if (value != null) { if (value instanceof ValueWrapper) { return new RpcResult(((ValueWrapper)value).get()); } else { return new RpcResult(value); } } Result result = invoker.invoke(invocation); if (!result.hasException()) { cache.put(key, new ValueWrapper(result.getValue())); } return result; } } return invoker.invoke(invocation); }
能够看出,在RPC调用(invoke)的链路上,会先使用请求参数判断当前线程是否刚刚发起过一样参数的调用——这个调用会使用ThreadLocalCache保存起来。具体的看,ThreadLocalCache的实现以下:
package org.apache.dubbo.cache.support.threadlocal; import org.apache.dubbo.cache.Cache; import org.apache.dubbo.common.URL; import java.util.HashMap; import java.util.Map; /** * ThreadLocalCache */ public class ThreadLocalCache implements Cache { //ThreadLocal里存放的是参数到结果的映射 private final ThreadLocal<Map<Object, Object>> store; public ThreadLocalCache(URL url) { this.store = new ThreadLocal<Map<Object, Object>>() { @Override protected Map<Object, Object> initialValue() { return new HashMap<Object, Object>(); } }; } @Override public void put(Object key, Object value) { store.get().put(key, value); } @Override public Object get(Object key) { return store.get().get(key); } }
在RocketMQ中,我也找到了ThreadLocal的身影,它是用在消息发送的场景,MQClientAPIImpl是RMQ中负责将消息发送到服务端的实现,其中有一个步骤须要选择一个具体的队列,选择具体的队列的时候,不一样的线程有本身负责的index值,这里使用了ThreadLocal的机制,能够看下ThreadLocalIndex的实现:
package org.apache.rocketmq.client.common; import java.util.Random; public class ThreadLocalIndex { private final ThreadLocal<Integer> threadLocalIndex = new ThreadLocal<Integer>(); private final Random random = new Random(); public int getAndIncrement() { Integer index = this.threadLocalIndex.get(); if (null == index) { index = Math.abs(random.nextInt()); if (index < 0) index = 0; this.threadLocalIndex.set(index); } index = Math.abs(index + 1); if (index < 0) index = 0; this.threadLocalIndex.set(index); return index; } @Override public String toString() { return "ThreadLocalIndex{" + "threadLocalIndex=" + threadLocalIndex.get() + '}'; } }
这篇文章主要是解决了关于ThreadLocal的几个问题:(1)具体的概念是啥?(2)在Java开发中的什么场景下使用?(3)ThreadLocal的实现原理是怎样的?(4)开源项目中有哪些案例能够参考?不知道你是否对这几个问题有了必定的了解呢?若是还有疑问,欢迎交流。
本号专一于后端技术、JVM问题排查和优化、Java面试题、我的成长和自我管理等主题,为读者提供一线开发者的工做和成长经验,期待你能在这里有所收获。