在处理多线程并发安全的方法中,最常用的方法就是使用锁来控制多个不同线程对临界区的访问。但是无论是哪种锁,乐观锁还是悲观锁,在并发冲突时都会对性能产生一定的影响。有没有办法完全避免竞争?答案是肯定的,这就是ThreadLocal。从字面上理解,ThreadLocal可以理解为一个线程的局部变量,也就是说一个ThreadLocal变量只能被自己的线程访问,不能被其他线程访问,自然就避免了线程竞争。因此,ThreadLocal提供了一种独特的线程安全方法。而不是在线程发生冲突时试图去解决冲突,而是完全避免了冲突。ThreadLocal的基本使用是创建一个ThreadLocal对象:privateThreadLocallocalInt=newThreadLocal<>();上面的代码创建了一个localInt变量。由于ThreadLocal是泛型类,所以这里指定localInt的类型为整数。下面展示了如何设置和获取这个变量的值:publicintsetAndGet(){localInt.set(8);returnlocalInt.get();}上面的代码把变量的值设置为8,然后获取这个值。由于ThreadLocal中设置的值只能被当前线程自身看到,这意味着你不能通过其他线程为它初始化该值。为了弥补这一点,ThreadLocal提供了一个withInitial()方法来统一初始化所有线程的ThreadLocal值:privateThreadLocallocalInt=ThreadLocal.withInitial(()->6);上面的代码设置了ThreadLocal的初始值为6,这个对所有线程都是可见的。ThreadLocal的实现原理ThreadLocal变量只在单个线程可见,那么它是怎么做到的呢?先从最基本的get()方法开始:publicTget(){//获取当前线程Threadt=Thread.currentThread();//每个线程都有自己的ThreadLocalMap,//ThreadLocalMap存储了所有的ThreadLocal变量ThreadLocalMapmap=getMap(t);if(map!=null){//ThreadLocalMap的key是当前ThreadLocal对象实例,//多个ThreadLocal变量放在这个map中ThreadLocalMap.Entrye=map.getEntry(this);if(e!=null){@SuppressWarnings("unchecked")//从map中取的值这是我们需要的ThreadLocal变量Tresult=(T)e.value;returnresult;}}//如果map没有初始化,则初始化returnsetInitialValue()这里;}你可以看到所谓的ThreadLocal变量保存在线程的映射中的每个。该映射是Thread对象中的threadLocals字段。如下:ThreadLocal.ThreadLocalMapthreadLocals=null;ThreadLocal.ThreadLocalMap是一个特殊的Map,其中每个Entry的key都是弱引用:staticclassEntryextendsWeakReference>{/**ThevalueassociatedwiththisThreadLocal.*/Objectvalue;//key是弱引用Entry(ThreadLocal>k,Objectv){super(k);value=v;}}这样设计的好处是如果这个变量不再被其他对象使用,ThreadLocal对象可以自动回收,避免可能的内存泄漏(注意Entry中的值仍然是强引用,如何回收,看下面的分解)。理解ThreadLocal中的内存泄漏问题虽然ThreadLocalMap中的key是弱引用,当没有外部强引用时会自动回收,但是Entry中的value仍然是强引用。这个值的引用链如下:可以看出,只有在Thread被回收的时候,这个值才有机会被回收,否则只要线程不退出,这个值就一直有强引用.但是,要求每个Thread退出是一个极其苛刻的要求。对于线程池来说,大部分线程会一直存在于系统的整个生命周期中。在这种情况下,值对象可能会泄漏。处理的方法是在ThreadLocalMap执行set()、get()、remove()时进行清理:以getEntry()为例:privateEntrygetEntry(ThreadLocal>key){inti=key.threadLocalHashCode&(table.length-1);Entrye=table[i];if(e!=null&&e.get()==key)//如果找到key,直接返回;else//如果没有找到,它会尝试清理,如果你总是访问已有的key,那么这个清理永远不会进来=table;intlen=tab.length;while(e!=null){//整个e为entry,为弱引用ThreadLocal>k=e.get();//找到则返回if(k==key)return;if(k==null)//如果key为null,说明弱引用已经被回收了//那么这里必须回收里面的值expungeStaleEntry(i);else//如果key不是你要找的,说明存在hash冲突,这里就是处理冲突,找到下一个entryi=nextIndex(i,len);e=tab[i];}returnnull;}真正用来回收值的是expungeStaleEntry()方法,在remove()和set()方法,会直接或间接调用该方法来清理值:从这里我们可以看出ThreadLocal在避免内存泄漏方面花费了很多精力。不仅使用弱引用来维护key,还会在每次操作时检查key是否被回收,然后回收value。但是也可以看出,ThreadLocal并不能保证100%不会发生内存泄露。例如,不幸的是,您的get()方法总是访问几个一直存在的ThreadLocal,因此不会执行清理操作。如果你没有机会调用set()和remove(),那么这个内存泄漏仍然会发生。发生。所以,一个好习惯还是:当你不需要这个ThreadLocal变量的时候,主动调用remove(),这样对整个系统都有好处。ThreadLocalMap中的哈希冲突处理ThreadLocalMap作为HashMap的实现与java.util.HashMap是不同的。对于java.util.HashMap,采用链表的方式来处理冲突:而对于ThreadLocalMap,则采用简单的线性检测方式。如果发生元素冲突,则使用下一个槽来存放:具体来说,整个set()的过程如下:可继承的ThreadLocal——InheritableThreadLocal在实际开发过程中,我们可能会遇到这样的场景。主线程开了一个子线程,但是我们希望主线程中的ThreadLocal对象可以在子线程中访问,也就是说需要在父子线程之间传递一些数据。比如像这样:publicstaticvoidmain(String[]args){ThreadLocalthreadLocal=newThreadLocal();IntStream.range(0,10).forEach(i->{//每个线程的序号,希望能拿到里面thesub-threadthreadLocal.set(i);//这里来了一个子线程,我们希望访问上面的threadLocalnewThread(()->{System.out.println(Thread.currentThread().getName()+":"+threadLocal.get());}).start();try{Thread.sleep(1000);}catch(InterruptedExceptione){e.printStackTrace();}});}执行上面的代码,你会看到:Thread-0:nullThread-1:nullThread-2:nullThread-3:null因为子线程中没有threadLocal。如果我们想让子线程看到父线程的ThreadLocal,那么可以使用InheritableThreadLocal。顾名思义,这是一个支持线程间父子继承的ThreadLocal。使用上面代码中的threadLocal来使用InheritableThreadLocal:InheritableThreadLocalthreadLocal=newInheritableThreadLocal();然后执行,可以看到:Thread-0:0Thread-1:1Thread-2:2Thread-3:3Thread-4:4可以看到每个线程都可以访问父进程传来的一段数据。InheritableThreadLocal虽然看起来很方便,但是还是需要注意以下几点:变量的传递发生在线程创建的时候,如果不是新建线程,而是使用了线程池中的线程,是不行的.变量赋值是从主线程的mapCopying到子线程,它们的值是同一个对象,如果对象本身不是线程安全的,那么就会有线程安全问题写在最后,今天介绍一下ThreadLocal,ThreadLocal在很多Java线程开发中起着非常重要的作用。在这里,我们介绍一下ThreadLocal的基本使用和实现原理,特别是根据目前的实现原理,重点介绍可能存在的内存泄漏问题。最后还介绍了一个专门用于父子线程之间传递数据的ThreadLocal实现,希望对大家有所帮助。