Thread的生命周期
指线程从创建到销毁的整个过程。在线程的生命周期中,可能会经历不同的状态变化。
线程的运行状态:
-
NEW:线程对象被创建,未启动线程
-
READY:start()启动
-
RUNNABLE(可以运行的线程状态):线程已被加载到线程调度器的就绪队列中,等待CPU的调度执行。
-
RUNNING:线程正在执行,执行过程中可能会因为线程间的切换、等待某个条件满足等原因进入阻塞状态。
-
BLOCKED:线程被阻塞,因为等待某种资源(如等待I/O操作完成、锁竞争如:sync)
-
Dead:线程执行完毕或者因异常而终止。
ThreadLocal了解吗
- ThreadLocal提供线程局部变量。在多线程环境下,每个线程都拥有自己的 ThreadLocal 变量副本,各线程之间的变量互不影响,这样可以有效避免数据同步问题,提高程序效率。
- 基本原理:每个 Thread 都有一个 ThreadLocalMap 类型的成员变量,用于存储当前线程的 ThreadLocal 值,
ThreadLocalMap 的键是 ThreadLocal 对象,而值是我们真正存储的数据,通常需要子类重写initialValue()方法以提供初始值。 - hreadLocalMap键是弱引用,这意味着当没有强引用指向 ThreadLocal 对象时,它会被垃圾回收器回收。如果 ThreadLocal 被回收了,而其对应的值还在 ThreadLocalMap 中,则会出现内存泄漏。因此,在使用完 ThreadLocal 后,最好手动调用 remove() 方法清理。
- ThreadLocal 的使用场景主要是在多线程环境下需要保持线程安全的数据隔离。
使用场景:
- 日志跟踪:在分布式系统中,可以使用 ThreadLocal 来存储请求的唯一标识,比如请求 ID。这样,在不同的线程中都可以方便地获取到请求 ID,方便进行日志跟踪和排查问题。
- 用户上下文:在 Web 应用程序中,可以使用 ThreadLocal 存储用户的上下文信息,比如用户的登录状态、权限等。这样,在不同的线程中都可以方便地获取到用户的上下文信息,避免频繁地传递参数。
RequestContextHolder中ThreadLocal使用ThreadLocal 保存请求属性值,(token等认证信息) - 线程池:在使用线程池的情况下,如果需要在线程之间传递一些上下文信息或者状态,可以使用 ThreadLocal 来存储这些信息。比如,在 Web 应用程序中,可以将用户信息存储在 ThreadLocal 中,以便在不同的线程中访问。
线程池的意义
- 线程池是 Java 中的一个重要线程管理工具,它可以有效地管理多个线程,提高程序的性能。
- 线程池的意义和作用如下:
- 提高程序性能:线程池可以有效地减少线程创建和销毁的开销,提高程序的性能。
- 降低资源消耗:线程池可以有效地控制线程数量,避免创建过多的线程导致资源消耗过大。
- 提高线程的可管理性:线程池可以对线程进行统一管理,方便进行线程的调度和控制。
- 实现线程的复用:线程池可以将线程进行复用,避免频繁创建和销毁线程。
线程池原理、线程池由什么组成
- 线程池是一种多线程处理形式,它通过维护一组可复用的线程资源来高效地执行多个任务。线程池的核心原理在于减少线程创建和销毁的开销,同时通过合理的调度策略来管理和控制并发执行的任务队列,以提高系统整体性能。
线程池管理器(ThreadPool Manager):
- 负责创建和销毁线程池。
- 控制线程池的大小,即线程池中工作线程的最大数量。
- 提供添加新任务、拒绝任务、调整线程池参数等操作。
工作线程(Worker Threads 或 Pool Workers):
- 线程池中的线程实体,它们是真正的任务执行者。
- 当没有任务时,工作线程会处于等待状态或者循环检查任务队列。
- 当有新的任务到达时,线程池管理器会选择一个空闲的工作线程来执行任务。
任务接口(Task Interface):
- 任务通常需要实现特定的接口(如 Java 中的 Runnable 或 Callable 接口),以便可以被工作线程调度执行。
- 任务接口定义了任务的行为,包括任务的具体执行逻辑以及可能的返回值(对于 Callable 接口而言)。
任务队列(Task Queue 或 Work Queue):
- 一种线程安全的数据结构(如 Java 中的 BlockingQueue),用于存储待执行的任务。
- 工作线程从该队列中取出任务进行执行,当队列为空时,工作线程可以采取不同的策略,如等待、休眠或终止。
线程工厂(Thread Factory)(可选组成部分):
- 用于创建新线程实例的工厂类,可以根据需要定制线程的属性,比如设置名称、优先级等。
拒绝策略(Rejected Execution Handler):
- 当线程池饱和(例如:所有工作线程都在忙碌,并且任务队列已满)时,拒绝策略将决定如何处理无法放入队列的新提交的任务,常见的拒绝策略包括直接丢弃、抛出异常、调用自定义回调函数等。
线程池7个参数、线程池执行流程
线程池的7个参数如下:
- corePoolSize:核心线程池大小。核心线程池中的线程会一直存活,即使没有任务需要执行。
- maximumPoolSize:最大线程池大小。最大线程池大小是线程池可以创建的最大线程数。
- keepAliveTime:线程空闲时间。线程空闲时间超过 keepAliveTime 时,线程会被销毁。
- unit:keepAliveTime 的单位。
- workQueue:任务队列。线程池会将任务放入任务队列中,等待线程执行。
- threadFactory:线程工厂。线程池会使用线程工厂创建线程。
- handler:饱和策略。当任务队列满了,并且线程池中的线程都处于工作状态时,线程池会使用饱和策略来处理新提交的任务。
线程池的执行流程如下:
- 当有任务提交到线程池时,线程池会先检查核心线程池是否有空闲线程。如果有空闲线程,则会直接将任务分配给空闲线程执行。
- 如果核心线程池中没有空闲线程,则会检查线程池的最大线程池大小。如果线程池的最大线程池大小没有达到,则会创建一个新的线程来执行任务。
- 如果线程池的最大线程池大小已经达到,则会将任务放入任务队列中。
- 如果任务队列满了,则会根据饱和策略来处理新提交的任务。常见的饱和策略包括抛出异常、丢弃任务、阻塞任务提交线程或者执行调用者的线程来执行任务。
线程池、核心线程数、最大线程数怎么设置的?
- 线程池按业务进行分类、执行
-
CPU密集型:
- 核心线程数不超过 CPU 核心数
- 最大线程数 CPU 核心数 + 1
-
IO密集型:
- 核心线程数量 CPU 核心数 2 倍
- 最大线程数量 CPU 核心数 2 倍 + 1
介绍一下知道有哪些阻塞队列
- ArrayBlockingQueue:
- 它是一个由数组支持的有界FIFO(先进先出)队列。
- 创建时需要指定容量大小,并且这个容量是固定的,无法扩容。
- 插入操作(offer、put)和移除操作(poll、take)在队列满或空时会阻塞线程,直到空间可用或者有元素可取。
- LinkedBlockingQueue:
- 一个基于链表结构的阻塞队列,默认情况下可以动态扩展到Integer.MAX_VALUE,但也可以在创建时指定容量上限使其变为有界队列。
- 同样遵循FIFO原则,但在内部实现上更灵活,适合大容量存储。
- 具有与ArrayBlockingQueue相似的阻塞特性。
- PriorityBlockingQueue:
- 这是一个无界的优先级队列,根据元素的自然排序(通过Comparable接口)或自定义Comparator排序决定元素的优先级。
- 当队列为空时,take操作会阻塞等待;当队列非空时,总是取出并返回优先级最高的元素。
- 注意,插入到此队列中的元素必须实现Comparable接口或者提供Comparator,否则将抛出异常。
- SynchronousQueue:
- 这是一种特殊的无缓冲队列,它不存储元素,而是在生产者放入元素的同时匹配一个消费者取出元素。
- 如果没有正在等待的消费者,则试图放入元素的操作将会阻塞,同样地,如果没有可用的元素,那么消费者尝试获取元素也会被阻塞。
- SynchronousQueue通常用于传递任务而不是存储任务,它可以强制执行“工作窃取”策略,即每个插入的任务都会立即交给另一个线程处理。
- DelayQueue:
- 这是一个无界队列,其中包含实现了Delayed接口的对象,只有当对象的延迟时间到期后才能从队列中获取到。
- 能够按照延迟时间顺序进行处理,例如定时任务调度器。
- LinkedTransferQueue:
- 基于链表的无界并发队列,除了基础的插入和删除功能外,还提供了transfer方法,该方法可以使生产者线程直接将元素传输给消费者线程,如果当前没有消费者等待,则生产者线程会被阻塞。
常见的阻塞队列有哪些、自己有实现过吗
实现 BlockingQueue 接口可以提供以下能力:
- 阻塞生产者线程:当队列已满时, add() 方法会阻塞生产者线程,直到队列有空闲位置。
- 阻塞消费者线程:当队列为空时, remove() 方法会阻塞消费者线程,直到队列有元素可供消费。
- 非阻塞添加和删除: offer() 方法可以向队列中添加元素,如果队列已满则返回 false ,不会阻塞生产者线程。 poll() 方法可以从队列中删除元素,如果队列为空则返回 null ,不会阻塞消费者线程。
- 获取队列大小: size() 方法可以获取队列中当前的元素数量。
- 判断队列是否为空: isEmpty() 方法可以判断队列是否为空。
- 判断元素是否存在: contains() 方法可以判断队列中是否存在指定元素。
- 遍历队列: iterator() 方法可以获取队列的迭代器,用于遍历队列中的元素。
- 清空队列: clear() 方法可以清空队列中的所有元素。
阻塞队列的使用场景如下:
- 生产者-消费者模型:阻塞队列常用于实现生产者-消费者模型,生产者向队列中添加任务,消费者从队列中获取任务并执行。阻塞队列可以确保生产者和消费者之间的协调和平衡。
- 线程池:线程池中的任务队列通常使用阻塞队列来存储待执行的任务。当线程池中的线程都在执行任务时,新提交的任务会被阻塞,直到有空闲线程可用。
- 消息传递:阻塞队列可以用于不同线程之间的消息传递。一个线程将消息放入队列,另一个线程从队列中获取消息进行处理。阻塞队列可以确保线程间的同步和顺序执行。
- 并发编程:在并发编程中,阻塞队列可以用于线程间的同步和通信。多个线程可以通过阻塞队列进行数据交换和协作。
- 总之, BlockingQueue 接口的实现提供了一种线程安全的、具有阻塞能力的队列,适用于多线程环境下的任务调度、消息传递和并发编程等场景。
对BlockQueue的理解
- 阻塞队列可以用于实现生产者-消费者模型,提供线程安全的数据传递和协调机制。
主要特点:
- 阻塞操作:当队列已满时,生产者线程尝试向队列中添加元素时会被阻塞,直到队列有空闲位置。当队列为空时,消费者线程尝试从队列中获取元素时会被阻塞,直到队列有可用元素。
- 线程安全:阻塞队列提供了线程安全的操作,多个线程可以同时操作队列而不会导致数据不一致或竞态条件。
ArrayBlockQueue与LinkedBolckQueue区别,底层怎么实现的
LinkedBlockQueue用了ReentrantLock的condition,condition是什么?condition是怎么实现的?
介绍CAS、解析cas底层原理
- CAS(Compare and Swap)是一种并发编程中常用的原子操作,用于实现多线程环境下的线程安全。
- CAS操作是一种乐观锁的实现方式,它通过比较预期值和实际值来判断是否存在竞争条件,从而避免了传统锁的开销。
- Unsafe类提供了一些方法,可以直接进行CAS操作。
- concurrnet.atomic包下AtomicLong等实现CAS乐观锁
ABA问题
-
ABA问题是指在并发环境下,一个值从A变为B,然后再变回A的过程,而某些并发操作可能会错误地认为该值没有发生变化。这种情况可能导致并发操作的逻辑错误。
-
AtomicStampedReference通过增加expectedStamp、newStamp标记值(版本号)判断是否需要将期望的引用值更新为新引用
看过JUC源码吗,讲讲CountDownLatch、CyclicBarrier、Semphore底层原理
ConcurrentHashMap由什么组成?
介绍一下volatile、synchronized、ReentrantLock实现原理
什么情况下使用synchronized
volatile为什么不能保证原子性
synchronized与ReentrantLock的区别
数据库怎么实现乐观锁、怎么实现悲观锁
ReentrantLock默认是公平还是非公平,公平非公平怎么理解
ReentrantLock的node是什么
ReentrantLock的第一个node为什么是一个空node
ReentrantLock的condition
标签:ThreadLocal,队列,元素,阻塞,学习,面试,任务,线程 From: https://www.cnblogs.com/JMrLi/p/17959529