2025/12/31 9:25:04
网站建设
项目流程
合肥做网站首选 晨飞网络,济南设计网站的公司,广告策划方案怎么做,什么平台可以做网站#x1f9d1; 博主简介#xff1a;CSDN博客专家#xff0c;历代文学网#xff08;PC端可以访问#xff1a;https://literature.sinhy.com/#/?__c1000#xff0c;移动端可微信小程序搜索“历代文学”#xff09;总架构师#xff0c;15年工作经验#xff0c;精通Java编… 博主简介CSDN博客专家历代文学网PC端可以访问https://literature.sinhy.com/#/?__c1000移动端可微信小程序搜索“历代文学”总架构师15年工作经验精通Java编程高并发设计Springboot和微服务熟悉LinuxESXI虚拟化以及云原生Docker和K8s热衷于探索科技的边界并将理论知识转化为实际应用。保持对新技术的好奇心乐于分享所学希望通过我的实践经历和见解启发他人的创新思维。在这里我希望能与志同道合的朋友交流探讨共同进步一起在技术的世界里不断学习成长。技术合作请加本人wx注明来自csdnforeast_seaJava并发编程面试题ThreadLocal1. ThreadLocal 是什么ThreadLocal是 Java 中提供的一种用于实现线程局部变量的工具类。它允许每个线程都拥有自己的独立副本从而实现线程隔离用于解决多线程中共享对象的线程安全问题。在 Web 应用中可以使用 ThreadLocal 存储用户会话信息这样每个线程在处理用户请求时都能方便地访问当前用户的会话信息。在数据库操作中可以使用 ThreadLocal 存储数据库连接对象每个线程有自己独立的数据库连接从而避免了多线程竞争同一数据库连接的问题。在格式化操作中例如日期格式化可以使用 ThreadLocal 存储 SimpleDateFormat 实例避免多线程共享同一实例导致的线程安全问题。使用 ThreadLocal 通常分为四步①、创建 ThreadLocal//创建一个ThreadLocal变量publicstaticThreadLocalStringlocalVariablenewThreadLocal();②、设置 ThreadLocal 的值//设置ThreadLocal变量的值localVariable.set(沉默王二是沙雕);③、获取 ThreadLocal 的值//获取ThreadLocal变量的值StringvaluelocalVariable.get();④、删除 ThreadLocal 的值//删除ThreadLocal变量的值localVariable.remove();ThreadLocal 有哪些优点①、线程隔离每个线程访问的变量副本都是独立的避免了共享变量引起的线程安全问题。由于 ThreadLocal 实现了变量的线程独占使得变量不需要同步处理因此能够避免资源竞争。②、数据传递方便ThreadLocal 常用于在跨方法、跨类时传递上下文数据如用户信息等而不需要在方法间传递参数。除了 ThreadLocal还有什么解决线程安全问题的方法①、Java 中的 synchronized 关键字可以用于方法和代码块确保同一时间只有一个线程可以执行特定的代码段。publicsynchronizedvoidmethod(){// 线程安全的操作}②、Java 并发包java.util.concurrent.locks中提供了 Lock 接口和一些实现类如 ReentrantLock。相比于 synchronizedReentrantLock 提供了公平锁和非公平锁。ReentrantLocklocknewReentrantLock();publicvoidmethod(){lock.lock();try{// 线程安全的操作}finally{lock.unlock();}}③、Java 并发包还提供了一组原子变量类如 AtomicIntegerAtomicLong 等它们利用 CAS比较并交换实现了无锁的原子操作适用于简单的计数器场景。AtomicIntegeratomicIntegernewAtomicInteger(0);publicvoidincrement(){atomicInteger.incrementAndGet();}④、Java 并发包提供了一些线程安全的集合类如 ConcurrentHashMapCopyOnWriteArrayList 等。这些集合类内部实现了必要的同步策略提供了更高效的并发访问。ConcurrentHashMapString,StringmapnewConcurrentHashMap();⑤、volatile 变量保证了变量的可见性修改操作是立即同步到主存的读操作从主存中读取。privatevolatilebooleanflagfalse;2. 你在工作中用到过 ThreadLocal 吗有用到过用来存储用户信息。MVC 架构登录后的用户每次访问接口都会在请求头中携带一个 token在控制层可以根据这个 token解析出用户的基本信息。假如在服务层和持久层也要用到用户信息就可以在控制层拦截请求把用户信息存入 ThreadLocal。这样我们在任何一个地方都可以取出 ThreadLocal 中存的用户信息。很多其它场景的 cookie、session 等等数据隔离都可以通过 ThreadLocal 去实现。数据库连接池也可以用 ThreadLocal将数据库连接池的连接交给 ThreadLocal 进行管理能够保证当前线程的操作都是同一个 Connnection。3. ThreadLocal 怎么实现的呢ThreadLocal 本身并不存储任何值它只是作为一个映射来映射线程的局部变量。当一个线程调用 ThreadLocal 的 set 或 get 方法时实际上是访问线程自己的 ThreadLocal.ThreadLocalMap。ThreadLocalMap 是 ThreadLocal 的静态内部类它内部维护了一个 Entry 数组key 是 ThreadLocal 对象value 是线程的局部变量本身。早期的 ThreadLocal 不是这样的它的 ThreadLocalMap 中使用 Thread 作为 key这也是最简单的实现方式。优化后的方案有两个好处一个是 Map 中存储的键值对变少了另一个是 ThreadLocalMap 的生命周期和线程一样长线程销毁的时候ThreadLocalMap 也会被销毁。Entry 继承了 WeakReference它限定了 key 是一个弱引用弱引用的好处是当内存不足时JVM 会回收 ThreadLocal 对象并且将其对应的 Entry 的 value 设置为 null这样在很大程度上可以避免内存泄漏。staticclassEntryextendsWeakReferenceThreadLocal?{/** The value associated with this ThreadLocal. */Objectvalue;//节点类Entry(ThreadLocal?k,Objectv){//key赋值super(k);//value赋值valuev;}}ThreadLocal 的实现原理就是每个线程维护一个 Mapkey 为 ThreadLocal 对象value 为想要实现线程隔离的对象。1、当需要存线程隔离的对象时通过 ThreadLocal 的 set 方法将对象存入 Map 中。2、当需要取线程隔离的对象时通过 ThreadLocal 的 get 方法从 Map 中取出对象。3、Map 的大小由 ThreadLocal 对象的多少决定。什么是弱引用什么是强引用强引用比如说User user new User(沉默王二)中user 就是一个强引用new User(沉默王二)就是一个强引用对象。当 user 被置为 null 时user nullnew User(沉默王二)将会被垃圾回收如果 user 不被置为 null即便是内存空间不足JVM 也不会回收new User(沉默王二)这个强引用对象宁愿抛出 OutOfMemoryError。弱引用比如说下面这段代码ThreadLocalUseruserThreadLocalnewThreadLocal();userThreadLocal.set(newUser(沉默王二));①、userThreadLocal 是一个强引用new ThreadLocal()是一个强引用对象②、new User(沉默王二)是一个强引用对象。③、在 ThreadLocalMap 中key new ThreadLocal()是一个弱引用对象。当 JVM 进行垃圾回收时如果发现了弱引用对象就会将其回收。其关系链就是ThreadLocal 强引用 - ThreadLocal 对象。Thread 强引用 - ThreadLocalMap。ThreadLocalMap[i]强引用了 - Entry。Entry.key 弱引用 - ThreadLocal 对象。Entry.value 强引用 - 线程的局部变量对象。4. ThreadLocal 内存泄露是怎么回事通常情况下随着线程 Thread 的结束其内部的 ThreadLocalMap 也会被回收从而避免了内存泄漏。但如果一个线程一直在运行并且其ThreadLocalMap中的 Entry.value 一直指向某个强引用对象那么这个对象就不会被回收从而导致内存泄漏。当 Entry 非常多时可能就会引发更严重的内存溢出问题。那怎么解决内存泄漏问题呢很简单使用完 ThreadLocal 后及时调用remove()方法释放内存空间。try{threadLocal.set(value);// 执行业务操作}finally{threadLocal.remove();// 确保能够执行清理}remove()方法会将当前线程的 ThreadLocalMap 中的所有 key 为 null 的 Entry 全部清除这样就能避免内存泄漏问题。privatevoidremove(ThreadLocal?key){Entry[]tabtable;intlentab.length;intikey.threadLocalHashCode(len-1);for(Entryetab[i];e!null;etab[inextIndex(i,len)]){if(e.get()key){e.clear();expungeStaleEntry(i);return;}}}publicvoidclear(){this.referentnull;}那为什么 key 要设计成弱引用弱引用的好处是当内存不足的时候JVM 会主动回收掉弱引用的对象。比如说WeakReferencekeynewWeakReference(newThreadLocal());key 是弱引用new WeakReference(new ThreadLocal())是弱引用对象当 JVM 进行垃圾回收时如果发现了弱引用对象就会将其回收。一旦 key 被回收ThreadLocalMap 在进行 set、get 的时候就会对 key 为 null 的 Entry 进行清理。总结一下在 ThreadLocal 被垃圾收集后下一次访问 ThreadLocalMap 时Java 会自动清理那些键为 null 的条目参照源码中的 replaceStaleEntry 方法这个过程会在执行 ThreadLocalMap 相关操作如get(),set(),remove()时触发。你了解哪些 ThreadLocal 的改进方案在 JDK 20 Early-Access Build 28 版本中出现了 ThreadLocal 的改进方案即ScopedValue。还有 Netty 中的 FastThreadLocal它是 Netty 对 ThreadLocal 的优化它内部维护了一个索引常量 index每次创建 FastThreadLocal 中都会自动1用来取代 hash 冲突带来的损耗用空间换时间。privatefinalintindex;publicFastThreadLocal(){indexInternalThreadLocalMap.nextVariableIndex();}publicstaticintnextVariableIndex(){intindexnextIndex.getAndIncrement();if(index0){nextIndex.decrementAndGet();}returnindex;}5. ThreadLocalMap 的源码看过吗ThreadLocalMap 虽然被叫做 Map其实它是没有实现 Map 接口的但是结构还是和 HashMap 比较类似的主要关注的是两个要素元素数组和散列方法。元素数组一个 table 数组存储 Entry 类型的元素Entry 是 ThreaLocal 弱引用作为 keyObject 作为 value 的结构。privateEntry[]table;散列方法散列方法就是怎么把对应的 key 映射到 table 数组的相应下标ThreadLocalMap 用的是哈希取余法取出 key 的 threadLocalHashCode然后和 table 数组长度减一运算相当于取余。intikey.threadLocalHashCode(table.length-1);这里的 threadLocalHashCode 计算有点东西每创建一个 ThreadLocal 对象它就会新增0x61c88647这个值很特殊它是斐波那契数也叫黄金分割数。hash增量为 这个数字带来的好处就是hash分布非常均匀。privatestaticfinalintHASH_INCREMENT0x61c88647;privatestaticintnextHashCode(){returnnextHashCode.getAndAdd(HASH_INCREMENT);}6. ThreadLocalMap 怎么解决 Hash 冲突的我们可能都知道 HashMap 使用了链表来解决冲突也就是所谓的链地址法。ThreadLocalMap 没有使用链表自然也不是用链地址法来解决冲突了它用的是另外一种方式——开放定址法。开放定址法是什么意思呢简单来说就是这个坑被人占了那就接着去找空着的坑。如上图所示如果我们插入一个 value27 的数据通过 hash 计算后应该落入第 4 个槽位中而槽位 4 已经有了 Entry 数据而且 Entry 数据的 key 和当前不相等。此时就会线性向后查找一直找到 Entry 为 null 的槽位才会停止查找把元素放到空的槽中。在 get 的时候也会根据 ThreadLocal 对象的 hash 值定位到 table 中的位置然后判断该槽位 Entry 对象中的 key 是否和 get 的 key 一致如果不一致就判断下一个位置。7. ThreadLocalMap 扩容机制了解吗在 ThreadLocalMap.set()方法的最后如果执行完启发式清理工作后未清理到任何数据且当前散列数组中Entry的数量已经达到了列表的扩容阈值(len*2/3)就开始执行rehash()逻辑if(!cleanSomeSlots(i,sz)szthreshold)rehash();再着看 rehash()具体实现这里会先去清理过期的 Entry然后还要根据条件判断size threshold - threshold / 4也就是size threshold* 3/4来决定是否需要扩容。privatevoidrehash(){//清理过期EntryexpungeStaleEntries();//扩容if(sizethreshold-threshold/4)resize();}//清理过期EntryprivatevoidexpungeStaleEntries(){Entry[]tabtable;intlentab.length;for(intj0;jlen;j){Entryetab[j];if(e!nulle.get()null)expungeStaleEntry(j);}}接着看看具体的resize()方法扩容后的newTab的大小为老数组的两倍然后遍历老的 table 数组散列方法重新计算位置开放地址解决冲突然后放到新的newTab遍历完成之后oldTab中所有的entry数据都已经放入到newTab中了然后 table 引用指向newTab具体代码8. 父子线程怎么共享数据父线程能用 ThreadLocal 来给子线程传值吗毫无疑问不能。那该怎么办这时候可以用到另外一个类——InheritableThreadLocal。使用起来很简单在主线程的 InheritableThreadLocal 实例设置值在子线程中就可以拿到了。publicclassInheritableThreadLocalTest{publicstaticvoidmain(String[]args){finalThreadLocalthreadLocalnewInheritableThreadLocal();// 主线程threadLocal.set(不擅技术);//子线程ThreadtnewThread(){Overridepublicvoidrun(){super.run();System.out.println(鄙人三某 threadLocal.get());}};t.start();}}那原理是什么呢原理很简单在 Thread 类里还有另外一个变量ThreadLocal.ThreadLocalMapinheritableThreadLocalsnull;在 Thread.init 的时候如果父线程的inheritableThreadLocals不为空就把它赋给当前线程子线程的inheritableThreadLocals。if(inheritThreadLocalsparent.inheritableThreadLocals!null)this.inheritableThreadLocalsThreadLocal.createInheritedMap(parent.inheritableThreadLocals);