什么是线程池?
线程池是一个可以复用线程的技术。简单来说,线程池是一种基于池化技术的思想来管理线程的技术,旨在减少线程的创建和销毁次数,提高系统的响应速度和吞吐量。它预先创建了一定数量的线程,并将这些线程放入到一个池中(即线程池),当需要执行新的任务时,不是直接创建新的线程,而是从线程池中取出一个空闲的线程来执行该任务,执行完毕后线程并不被销毁,而是放回线程池中供后续任务再次使用。
不使用线程池的问题
-
开销大:如果用户每发起一个请求,后台都需要创建一个新线程来处理,那么每次创建新线程都会带来较大的开销,包括系统资源的分配、初始化等过程。这些开销在请求量较大时会非常显著,严重影响系统性能。
-
资源浪费:当任务执行完毕后,如果直接销毁线程,那么在下一个任务到来时又需要重新创建线程,这会造成资源的频繁分配和回收,不仅开销大,而且浪费了宝贵的系统资源。
-
系统不稳定:如果请求过多,系统可能会创建大量的线程来处理这些请求。然而,系统的线程资源是有限的,过多的线程会导致线程之间竞争系统资源,引发线程切换、上下文切换等开销,进而可能导致系统响应变慢甚至崩溃。
线程池的工作原理
- 初始状态:线程池中有几个初始化的线程(数量等于核心线程数),这些线程都处于空闲状态,等待任务的到来。
- 任务提交:当有任务提交给线程池时,如果有空闲的线程,则直接由该线程执行任务;如果没有空闲的线程且任务队列未满,则任务被放入任务队列中等待。
- 线程执行:线程从任务队列中取出任务并执行,执行完毕后线程再次回到空闲状态等待新的任务。
- 动态调整:随着任务的增加,如果任务队列满了且线程数还没有达到最大线程数,则线程池会创建新的线程来执行任务。反之,如果一段时间内没有新的任务提交,线程池中的线程数量会逐渐减少到核心线程数(这取决于配置的线程池类型和策略)。
- 关闭状态:当线程池被关闭时,它不再接受新的任务,并且会等待所有已经提交的任务执行完成后才销毁线程和释放资源。
线程池的创建
方式一
通过创建ThreadPoolExecutor
实例来管理线程池,ThreadPoolExecutor
类提供了多个构造函数,允许开发者详细地配置线程池的参数,以满足不同的并发执行需求。
主要参数
- corePoolSize(核心线程数):线程池中核心线程的数量。即使这些线程处于空闲状态,它们也不会被销毁,除非设置了
allowCoreThreadTimeOut
为true
。 - maximumPoolSize(最大线程数):线程池中允许的最大线程数。当工作队列已满时,如果线程数小于最大线程数,则会创建新线程来处理任务。
- keepAliveTime(线程空闲时间):当线程数大于核心线程数时,这是多余空闲线程在终止前等待新任务的最长时间。
- unit(时间单位):
keepAliveTime
参数的时间单位,如TimeUnit.SECONDS
。 - workQueue(工作队列):用于保存等待执行的任务的阻塞队列。
- threadFactory(线程工厂):用于创建新线程的工厂。如果不指定,则使用
Executors.defaultThreadFactory()
来创建线程。这个固定的代码记住就行了。 - handler(拒绝处理策略):当任务队列和线程池都满了时,用于处理新任务的拒绝策略。
重点参数详解
任务队列的实现
ThreadPoolExecutor
的构造函数允许你指定一个BlockingQueue<Runnable>
类型的任务队列。BlockingQueue
是一个支持两个附加操作的队列,这两个操作是:在元素可用之前阻塞的检索操作,以及在没有额外空间时阻塞的插入操作。BlockingQueue
接口有多种实现,每种实现都有其特定的用途和性能特性。
常见的BlockingQueue
实现包括:
-
ArrayBlockingQueue
:一个由数组结构组成的有界阻塞队列。此队列按照先进先出(FIFO)的原则对元素进行排序。 -
LinkedBlockingQueue
:一个由链表结构组成的可选有界阻塞队列。如果创建时没有指定容量,则默认为Integer.MAX_VALUE
,即无界队列。 -
SynchronousQueue
:一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,反之亦然。 -
PriorityBlockingQueue
:一个支持优先级排序的无界阻塞队列。
如何选择任务队列
- 如果你需要限制线程池中任务的数量,可以使用有界的
ArrayBlockingQueue
或LinkedBlockingQueue
。 - 如果你想要线程池在提交新任务时立即创建新线程(只要不超过
maximumPoolSize
),可以使用SynchronousQueue
。但是,请注意,这可能会导致线程频繁地创建和销毁,从而增加开销。 - 如果你需要按照任务的优先级来执行,可以使用
PriorityBlockingQueue
。
线程池的注意事项
线程池处理Runnable任务
package demo12;
//任务类
public class MyRunnable implements Runnable{@Overridepublic void run() {//描述你的任务System.out.println(Thread.currentThread().getName()+"-->666");try {//为了方便观察,我们写一个延迟。Thread.sleep(Integer.MAX_VALUE);//Alt键加回车键捕捉一下} catch (InterruptedException e) {e.printStackTrace();}}
}
import java.util.concurrent.*;public class ThreadPool {public static void main(String[] args) {//担心忘了,你可以按着ctrl键,然后点击就能看到它的那个提示
// public ThreadPoolExecutor(int corePoolSize,
// int maximumPoolSize,
// long keepAliveTime,
// TimeUnit unit,
// BlockingQueue<Runnable> workQueue,
// ThreadFactory threadFactory,
// RejectedExecutionHandler handler) {ExecutorService pool=//多态写法//下面就创建好了一个线程池对象new ThreadPoolExecutor(3,5,8,TimeUnit.SECONDS,new ArrayBlockingQueue<>(4),Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());Runnable target=new MyRunnable();//怎么把这个任务对象交给现场时来处理呢pool.execute(target);//线程池会自动创建一个新线程,自动处理这个任务,自动执行的。pool.execute(target);pool.execute(target);pool.execute(target);//三个核心线程已经有了,此时复用前面的的核心线程。pool.execute(target);pool.execute(target);pool.execute(target);//到了临时线程的创建时机了pool.execute(target);pool.execute(target);//三个任务在核心线程执行,四个任务在队列里面排着。两个任务在临时线程执行。//到了新任务拒绝的时候了pool.execute(target);}
}
线程池处理Callable任务