创建和销毁线程依赖于CPU,多线程操作时,如果频繁地创建和销毁线程,就会耗费大量的CPU资源。为了避免出现像这样的资源浪费以及提高系统的性能,就出现了线程池。
线程池介绍
线程池类似于字符串常量池,如果需要用到某个字符串,JVM会先在字符串常量池中查找,如果有则直接调用,省去了创建的过程。线程池也一样,从线程池获取线程也省去了创建和销毁的过程。
使用线程池的优点
重新利用而不是创建线程,降低了资源的损耗;
当获取新任务时,可以直接响应,不需要等待线程的创建,提高了响应速度;
线程池可以对线程进行统一管理、调控。
线程池的参数介绍
线程池的类型是ThreadPollExecutor,其参数如下:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
corePoolSize -> 核心线程数,线程池维护的最小线程数量,核心线程创建后不会被回收
maximumPoolSize -> 池中允许的最大线程数
keepAliveTime -> 当线程数大于内核时,这是多余的空闲线程在终止前等待新任务的最大时间
unit -> keepAliveTime参数的时间单位
workQueue -> 工作队列,新任务添加到队列中,执行任务时弹出
threadFactory -> 创建新线程时使用的工厂,可以设置线程名称和编号等
handler -> 拒绝策略,当线程池中的线程满了,工作队列已满,新添加任务时的拒绝方法。
标准库共提供了四种拒绝策略:
Modifier and Type | Class and Description |
static class | ThreadPoolExecutor.AbortPolicy 抛出异常,系统停止运行 |
static class | ThreadPoolExecutor.CallerRunsPolicy 谁添加的任务谁执行 |
static class | ThreadPoolExecutor.DiscardOldestPolicy 丢弃最老的任务——即将要执行的任务 |
static class | ThreadPoolExecutor.DiscardPolicy 直接丢弃任务,不处理 |
标准库中的线程池
通过使用Executors类进行线程池的构造:
其中部分构造:
newFixedThreadPool: 创建固定线程数的线程池
newCachedThreadPool: 创建线程数目动态增长的线程池
newSingleThreadExecutor: 创建只包含单个线程的线程池
newScheduledThreadPool: 设定延迟时间后执行命令,或者定期执行命令。
通过submit方法添加任务到线程池的任务队列:
pool.submit(new Runnable() {
@Override
public void run() {
//要执行的任务
}
});
线程池的工作流程
当线程数小于corePoolSize时,每提交一个任务,创建一个新线程;
线程数达到corePoolSize,提交的新任务会放到workQueue中进行阻塞等待。
workQueue也满以后,继续创建新线程来处理,直到线程数达到maximumPoolSize。
当前线程数达到maximumPoolSize后并且workQueue也满,则根据handler处理。
线程数大于corePoolSize时,空闲线程最多等待keepAliveTime unit,若一直处于空闲状态则回收。
线程池的使用案例
public static void main(String[] args) {
//获取有10个线程的线程池
ExecutorService pool = Executors.newFixedThreadPool(10);
//执行100个任务
for (int i = 0; i < 100; i++) {
int num = i;
pool.submit(new Runnable() {
@Override
public void run() {
System.out.println("hello" + num);
}
});
}
System.out.println("任务结束!");
}
我们发现任务并不是按照顺序执行的,同时任务虽然完成了,线程却没有结束。
这是因为线程是并发执行的,同时工作队列为阻塞队列,因此不会自主结束。
模拟实现线程池
//水平有限,这里只实现前两个过程
public class MyThreadPool {
private int maxWorkerCount = 10;//最大线程数
private int size = 0;//当前线程数
private LinkedBlockingQueue<Runnable> queue = new LinkedBlockingQueue<>();
public void submit(Runnable runnable) throws InterruptedException {
//当线程小于核心线程数时,每添加一个新任务创建一个线程
if (size < maxWorkerCount) {
Thread t = new Thread(runnable);
t.start();
size++;
} else {
//线程数大于核心线程数时,不再创建新线程,而是放到阻塞队列中等待
queue.put(runnable);
Thread t1 = new Thread(() -> {
try {
while (!Thread.interrupted()) {
Runnable runnable1 = queue.take();
runnable1.run();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
});
t1.start();
}
}
public static void main(String[] args) throws InterruptedException {
MyThreadPool myThreadPool = new MyThreadPool();
for (int i = 0; i < 100; i++) {
int num = i;
myThreadPool.submit(new Runnable() {
@Override
public void run() {
System.out.println("hello" + num);
}
});
}
}
}
运行得到和之前差不多的结果: