网站设计公司 广州,公司注册费用计入什么科目,建立家族修仙,国外网站dns老套路#xff0c;先列举下关于ThreadLocal常见的疑问#xff0c;希望可以通过这篇学习笔记来解决这几个问题#xff1a;ThreadLocal是用来解决什么问题的#xff1f;如何使用ThreadLocal#xff1f;ThreadLocal的实现原理是什么#xff1f;可否举几个实际项目中使用Thre…老套路先列举下关于ThreadLocal常见的疑问希望可以通过这篇学习笔记来解决这几个问题ThreadLocal是用来解决什么问题的如何使用ThreadLocalThreadLocal的实现原理是什么可否举几个实际项目中使用ThreadLocal的案例基础知识ThreadLocal是线程局部变量和普通变量的不同在于每个线程持有这个变量的一个副本可以独立修改(set方法)和访问(get方法)这个变量并且线程之间不会发生冲突。类中定义的ThreadLocal实例一般会被private static修饰这样可以让ThreadLocal实例的状态和Thread绑定在一起业务上一般用ThreadLocal包装一些业务ID(user ID或事务ID)——不同的线程使用的ID是不相同的。如何使用case1从某个角度来看ThreadLocal为Java并发编程提供了额外的思路——避免并发如果某个对象本身是非线程安全的但是你想实现多线程同步访问的效果例如SimpleDateFormat你可以使用ThreadLocal变量。public class Foo{ // SimpleDateFormat is not thread-safe, so give one to each thread private static final ThreadLocal formatter new ThreadLocal(){ Override protected SimpleDateFormat initialValue() { return new SimpleDateFormat(yyyyMMdd HHmm); } }; public String formatIt(Date date) { return formatter.get().format(date); }}注意这里针对每个线程只需要初始化一次SimpleDateFormat对象其实跟在自定义线程中定义一个SimpleDateFormat成员变量并在线程初始化的时候new这个对象效果是一样的只是这样看起来代码更规整。case2之前在yunos做酷盘项目的数据迁移时我们需要按照用户维度去加锁每个线程在处理迁移之前都需要先获取当前用户的锁每个锁的key是带着用户信息的因此也可以使用ThreadLocal变量实现case3下面这个例子我们定义了一个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 threadLocal new ThreadLocal(); 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关键知识点源码分析ThreadLocal是如何被线程使用的原理如下图所示Thread引用和ThreadLocal引用都在栈上Thread引用会引用一个ThreadLocalMap对象这个map中的key是ThreadLocal对象(使用WeakReference包装)value是业务上变量的值。首先看java.lang.Thread中的代码publicclass 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 ThreadLocal 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); }数字0x61c88647这个值是HASH_INCREMENT的值普通的hashmap是使用链表来处理冲突的但是ThreadLocalMap是使用线性探测法来处理冲突的HASH_INCREMENT就是每次增加的步长根据参考资料1所说选择这个数字是为了让冲突概率最小。 /** * 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 extends ThreadLocal { /** * 创建线程的时候如果需要继承且父线程中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 key (ThreadLocal) 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; } } } }ThreadLocal对象何时被回收ThreadLocalMap中的key是ThreadLocal对象然后ThreadLocal对象时被WeakReference包装的这样当没有强引用指向该ThreadLocal对象之后或者说Map中的ThreadLocal对象被判定为弱引用可达时就会在垃圾收集中被回收掉。看下Entry的定义 static class Entry extends WeakReference { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal k, Object v) { super(k); value v; }}ThreadLocal和线程池一起使用ThreadLocal对象的生命周期跟线程的生命周期一样长那么如果将ThreadLocal对象和线程池一起使用就可能会遇到这种情况一个线程的ThreadLocal对象会和其他线程的ThreadLocal对象串掉一般不建议将两者一起使用。案例学习Dubbo中对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 store; public ThreadLocalCache(URL url) { this.store new ThreadLocal() { Override protected Map initialValue() { return new HashMap(); } }; } 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在RocketMQ中我也找到了ThreadLocal的身影它是用在消息发送的场景MQClientAPIImpl是RMQ中负责将消息发送到服务端的实现其中有一个步骤需要选择一个具体的队列选择具体的队列的时候不同的线程有自己负责的index值这里使用了ThreadLocal的机制可以看下ThreadLocalIndex的实现package org.apache.rocketmq.client.common;import java.util.Random;public class ThreadLocalIndex { private final ThreadLocal threadLocalIndex new ThreadLocal(); 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)开源项目中有哪些案例可以参考不知道你是否对这几个问题有了一定的了解呢如果还有疑问欢迎交流。参考资料Why 0x61c88647?Java ThreadLocalWhen and how should I use a ThreadLocal variable?技术小黑屋理解Java中的ThreadLocal深入分析ThreadLocal的内存泄漏问题《Java并发编程实战》InheritableThreadLocal详解ThreadLocal详解ThreadLocal的使用场景数据结构哈希表