Java面试之ThreadLocal

在Java多线程中访问同一个共享变量时特别容易出现并发问题,特别是在多个线程需要对一个共享变量进行写入时。为了保证线程安全,一般使用者在访问共享变量时需要进行适当的同步。但是,同步的措施一般是加锁,而这显然加重了使用者的负担。那么有没有一种方式,当多个线程访问共享变量时不会出现并发问题,就像每个线程访问自己的共享变量。这时,ThreadLocal类就出现了。

ThreadLocal是JDK包提供的,它提供了线程本地变量,也就是如果创建了一个ThreadLocal变量,那么访问这个变量的每个线程都会有这个变量的一个本地副本。当多个线程操作这个变量时,实际操作的是自己本地内存里面的变量,从而避免了多线程安全问题。

简单来说就是,ThreadLocal是一种线程隔离机制,它提供了多线程环境下对于共享变量访问的安全性。

在多线程访问共享变量的场景中,一般的解决办法是对共享变量加锁,从而保证在同一时刻只有一个线程能够对共享变量进行更新,并且基于Happens-Before规则里面的监视器锁规则,又保证了数据修改后对其他线程的可见性。

但是,加锁会带来性能的下降,所以ThredLocal用了一种空间换时间的设计思想,也就是说在每个线程里面,都有一个容器来存储共享变量的副本,然后每个线程只对自己的变量副本来做更新操作,这样既解决了线程安全问题,又避免了多线程竞争加锁的开销。

ThreadLocal的实现原理是,在ThreadLocal类里面有一个成员变量ThreadLocalMap,专门用来存储当前线程的共享变量副本,后续线程对于共享变量的操作,都是在这个成员变量里面进行变更,不会影响全局共享变量的值。

ThreadLocal

“水能载舟,亦能覆舟。”ThreadLocal初衷是在线程并发时,解决变量共享问题,但由于过度设计,比如弱引用和哈希碰撞,导致理解难度大、使用成本高,反而成为故障高发点,容易出现内存泄漏、脏数据、共享对象更新等问题。

ThreadLocal内存泄漏

不恰当的使用ThreadLocal,会造成内存泄露问题。主要原因是,线程的私有变量ThreadLocalMap里面的key是一个弱引用。

弱引用的特性,就是不管是否存在直接引用的关系,当成员ThreadLocal没有其他的强引用关系的时候,这个对象会被GC回收掉。

从而导致key可能变成null,造成这块内存永远无法访问,出现内存泄露的问题。

规避内存泄露:

通过扩大成员变量ThreadLocal的作用域,如定义成static final,避免被GC回收。

每次使用完ThreadLocal以后,调用remove方法移除对应的数据。

第一种方法虽然不会造成key为null的现象,但是如果后续线程不再继续访问这个key,这也会导致内存一直占用不释放,最后造成内存溢出的问题。所以我认为最好是在使用完以后调用remove()方法移除。

ThreadLocal弱引用

ThreadLocal的应用场景:

解决线程安全问题。ThreadLocal可以在一定程度上解决多线程之间的数据安全问题。通过将数据与线程绑定,确保每个线程之间都可以独立访问自己的数据,不会发生冲突。

传递上下文信息。在多线程的场景下,有些场景需要在多个方法中传递上下文信息,如用户信息、请求信息等,如果每个方法都需要传递这些信息,则代码会变得非常复杂。ThreadLocal可以将这些信息存储在ThreadLocal中,每个方法直接从ThreadLocal中获取即可,避免了参数传递的复杂性。

优化性能。在一些需要频繁创建对象的场景下,通过将对象存储在ThreadLocal中,可以减少对象的创建次数,提高性能。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容