【offer作手】java并发-threadlocal

265 阅读8分钟

面试难度:★★★★

考察概率:★★★★

#莫等闲,白了少年头#

本人从毕业开始一直在一线互联网大厂工作,现任技术TL,出版过《深入理解Java并发》一书,折腾过技术开源项目,并长期作为面试官参与面试,深谙双方的诉求与技术沟通。如今归零心态,再出发。#莫等闲,白了少年头#

技术交流+v:xxxyxsyy1234(和笔者一起努力,每日打卡) 2000+以面试官视角总结的考点,可与我共同打卡学习

公众号.jpg

面试官视角

涉及到高并发下数据线程安全的问题,总会自然而然的想到ThreadLocal变量,对于这个类也是一个高频的考点内容。最重要的还是解决数据线程安全通过线程本地变量这种解决方案的思路,“要让问题解决的最好的方式还是让问题不发生”。

面试题

  1. ThreadLocal 能否解决线程并发问题?
  2. ThreadLocal内存泄漏的原因?
  3. ThreadLocalMap为什么要使用线性探测法解决哈希冲突?
  4. Worker Thread模式与生产者-消费者模式的区别是什么?

回答要点

1. ThreadLocal 能否解决线程并发问题?

这个问题就看你是从什么角度回答的。并发问题是如何产生的:同一个变量被多个线程进行读写操作。
那ThreadLocal可以在多线程环境下实现线程之间的数据隔离,算不算解决并发问题呢。其实是这个问题不好。问题应该改为“ThreadLocal能否直接解决线程并发问题”。如果这么问,我觉得应该回答不能。

ThreadLocal并不能直接解决线程并发问题,它更多用于在多线程环境下实现线程封闭和数据隔离。ThreadLocal为每个线程提供了独立的变量副本,使得每个线程可以独立地操作自己的变量副本,从而避免了线程间的数据共享和竞争条件。

虽然ThreadLocal可以在一定程度上减少线程间的竞争条件,但它并不能完全解决线程并发问题,特别是对于共享可变状态的问题。多个线程共享的可变状态仍然需要使用其他并发控制机制,如锁(Lock)或原子类(Atomic)来保证线程安全。ThreadLocal仅提供了一种在多线程环境下隔离数据的手段,并不能保证数据的一致性和线程安全。

2. ThreadLocal内存泄漏的原因?

threadLocal是为了解决对象不能被多线程共享访问的问题,通过threadLocal.set方法将对象实例保存在每个线程自己所拥有的threadLocalMap中,这样每个线程使用自己的对象实例,彼此不会影响达到隔离的作用,从而就解决了对象在被共享访问带来线程安全问题。

如果将同步机制和threadLocal做一个横向比较的话,同步机制就是通过控制线程访问共享对象的顺序,而threadLocal就是为每一个线程分配一个该对象,各用各的互不影响。打个比方说,现在有100个同学需要填写一张表格但是只有一支笔,同步就相当于A使用完这支笔后给B,B使用后给C用,按照这种方式持续下去,而老师就控制着这支笔的使用顺序,使得同学之间不会产生冲突。而使用threadLocal就相当于老师直接准备了100支笔,这样每个同学都使用自己的,同学之间就不会产生冲突。

threadLocal、threadLocalMap以及entry之间的关系如上图所示实线代表强引用、虚线代表的是弱引用,如果threadLocal没有一条引用链路可达,很显然在进行垃圾回收的时候势必会被回收,因此entry就存在key为null的情况,无法通过一个key为null去访问到该entry所维护的数据域value。最终会导致在引用关系上就会存在了这样一条引用链:threadRef→Thread→ThreadLocalMap→entry→valueRef->valueMemory,在垃圾回收的时候进行可达性分析的时候会被判断为value可达从而不会被回收掉,但是该value永远不能被访问到这样就存在了内存泄漏。

3. ThreadLocalMap为什么要使用线性探测法解决哈希冲突?

ThreadLocalMap使用线性探测法解决哈希冲突的原因主要是出于性能和空间效率的考虑。

线性探测法是一种解决哈希冲突的方法,它在发生哈希冲突时,顺序地检查哈希表中的下一个位置,直到找到一个空闲的位置或者遍历完整个哈希表。与其他解决冲突的方法相比,线性探测法具有以下优点:

  1. 空间效率高:线性探测法不需要额外的数据结构来存储冲突的元素,只需要使用哈希表本身即可。这样可以节省额外的空间消耗。
  2. 缓存友好:线性探测法的连续内存访问性质更加符合CPU缓存的特性,提高了缓存命中率,从而提高了查询的效率。
  3. 简单高效:线性探测法的实现相对简单,不需要复杂的链表结构或二次哈希等操作,减少了实现的复杂性和开销。

在ThreadLocalMap中,每个ThreadLocal对象都对应着一个Entry对象,Entry对象作为哈希表中的存储单元。当哈希冲突发生时,使用线性探测法顺序检查下一个位置,直到找到一个空闲的位置或者遍历完整个哈希表。这样可以保证不同的ThreadLocal对象的Entry之间不会发生覆盖,从而实现了线程间的数据隔离。

总的来说,ThreadLocalMap使用线性探测法解决哈希冲突是为了在保证性能和空间效率的前提下,实现对ThreadLocal对象的高效存储和检索。

4. Worker Thread模式与生产者-消费者模式的区别是什么?

Worker Thread模式和生产者-消费者模式是两种并发编程的设计模式,它们有一些相似之处,但也有一些关键的区别。

Worker Thread模式:

  • Worker Thread模式也被称为线程池模式,它通过维护一个线程池来处理任务。
  • 在Worker Thread模式中,有一个固定数量的工作线程(Worker Thread),它们从任务队列中获取任务并执行。
  • 主线程(或其他线程)将任务提交到任务队列中,而不是直接创建新线程来执行任务。
  • Worker Thread模式适用于需要长时间运行的任务,可以复用线程,避免频繁地创建和销毁线程带来的开销。

生产者-消费者模式:

  • 生产者-消费者模式是一种经典的并发模式,其中有一个或多个生产者线程将任务(或数据)放入共享的队列中,而一个或多个消费者线程从队列中取出任务并进行处理。
  • 生产者线程负责产生任务,消费者线程负责处理任务,它们之间通过共享的队列进行通信。
  • 生产者-消费者模式通过解耦生产者和消费者之间的关系,使它们可以独立地进行工作,提高了系统的灵活性和可伸缩性。

关键区别:

  • Worker Thread模式更关注的是线程的管理和复用,通过线程池中的工作线程来执行任务。它将任务的提交和执行解耦,提供了更好的线程管理和资源利用。
  • 生产者-消费者模式更关注的是任务的生产和消费,通过共享队列来实现生产者和消费者之间的协作。它将任务的生产和消费解耦,提供了更好的任务调度和协调。

虽然Worker Thread模式和生产者-消费者模式在某些方面有相似之处,但它们的关注点和应用场景略有不同。Worker Thread模式更适用于长时间运行的任务,并对线程的管理和资源利用有较高的要求;而生产者-消费者模式更适用于任务的生产和消费解耦,提供任务调度和协调的灵活性。

代码考核

对于这部分来说,很少会有代码考核的内容,主要是要知道对应的原理。

知识点详情

这部分可以参考本人的书籍《深入理解Java并发》,或者本人博客

1、juejin.cn/post/684490…