为什么使用线程池

在实际使用中,线程是很占用系统资源的,如果对线程管理不善很容易导致系统问题。因此在大多数并发框架中都会使用线程池来对线程进行管理。

  1. 降低系统消耗:通过复用已存在的线程可以减少线程创建的消耗。避免频繁的线程关闭减少线程释放的消耗。
  2. 提升系统响应速度:任务到达时可以不用等待线程创建,直接执行。
  3. 提高线程的可管理性:使用线程池能够统一分配、监控和调优。

[!QUESTION] 线程池中的线程创建时需要哪些资源?一个最简单的线程创建时需要哪些资源?
程序计数器、寄存器、线程栈、本地方法栈、内核线程(每个Java线程在内核中都有一个对应的内核线程,这个内核线程由操作系统进行分配和管理)、内存(栈内存:在创建线程时分配并固定死。堆内存:可以在线程运行时动态分配)

[!QUESTION] 线程的本质是什么?为什么能够执行各种不同的任务?
线程的本质是操作系统调度执行的最小单位,一个线程属于进程内的一个执行路径,它共享进程的内存空间和资源,但拥有自己的执行堆栈、程序计数器和局部变量。
操作系统会给每一个线程分配时间片,线程在拿到时间片后就开始执行,时间片用完就保存当前的上下文,然后CPU加载另一个线程的上下文,运行另一个线程。
线程真的是一个全能的员工,让做什么就做什么,什么都能做。想让一个线程做什么的关键在于run()方法内部调用了什么方法。线程专心负责调用资源执行方法。

线程池的执行流程

image-20240319234539030
没到核心线程数就先使用核心线程,超过核心线程数的就保存到队列中,队列满了就创建线程直到最大线程数,此时再有线程过来就执行拒绝策略。

线程池的核心参数

核心线程数
队列
最大线程数
#question 拒绝策略

线程管理不善可能导致的系统问题

线程管理不善可以导致多种系统问题,影响程序的性能、稳定性和可用性。这些问题通常包括但不限于:

线程资源释放慢或不释放

死锁(Deadlock)

当两个或多个线程在等待对方持有的资源释放时,就会发生死锁。每个线程持有一部分资源,并等待其他线程释放其他部分资源,导致它们都停止执行并无限期地等待,从而使程序无法继续运行。

饥饿(Starvation)

线程饥饿发生在某些线程无法获得必要的资源来执行任务时,通常是因为其他“贪婪”的线程不断地占用这些资源。这会导致受影响的线程无法进展,甚至可能永远不会完成其任务。

资源泄露(Resource Leak)

如果线程创建后没有正确管理(例如,没有回收或重用线程资源),可能会导致资源泄露。随着时间的推移,可用资源(如内存和文件句柄)会逐渐减少,最终可能导致系统崩溃。

性能瓶颈

不正确的线程同步机制(例如,过度使用锁)可能导致性能瓶颈,使得并发执行的优势大打折扣。

线程过多

过度切换(Thread Thrashing)

当线程数量过多,以至于操作系统花费大部分时间在线程上下文切换上,而不是执行线程本身时,会发生过度切换。这会导致显著的性能下降。

执行顺序

竞态条件(Race Condition)

当两个或多个线程同时访问和修改同一资源,而最终结果依赖于线程执行顺序时,就会发生竞态条件。这可能导致数据不一致性和难以预测的行为。

解决策略

为了避免这些问题,开发者应该遵循最佳实践,包括但不限于:

  • 使用最小锁定范围或尝试无锁编程技术。
  • 合理分配资源,避免饥饿和死锁。
  • 使用线程池以减少创建和销毁线程的开销,并重用线程。
  • 采用适当的并发数据结构和同步工具(如 java.util.concurrent 包中的工具)。
  • 定期对系统进行性能和并发性测试,以便及时发现并解决问题。

[!QUESTION] 线程在什么时候获取需要执行的资源?在什么时候释放这些资源?

  1. 在获取到CPU时间片之前就需要将必要的资源给锁定【程序计数器、寄存器、线程栈,本地方法栈,内核线程、初始参数中已知的静态资源】(非竞态资源可以不锁定),然后在CPU执行线程的过程中,线程亦可以获取更多的资源。
  2. 任务完成后或者完成与资源有关的操作后,线程会释放或解锁它之前锁定的资源。此时其他线程可以使用这些资源。

[!QUESTION] 线程在执行完一个时间片后会立刻放弃自己的资源吗 ?还是等待这个资源在该线程中使用完成或者整个线程的任务执行完之后再释放资源?
现成不会在执行完一个时间片后放弃全部的资源,但是互斥资源和共享资源如果使用完成了,或者线程结束了就会立刻释放。