Java基础知识回顾第10篇 - 认识Java线程池
一、整体认识
一般的理解就是,当我们需要执行不同任务的时候,针对每一个任务都需要去独立创建一个线程。这样带来的一个问题就是,频繁地创建和销毁线程需要时间,很消耗资源。为了解决这个问题,Java线程池出现了,那么什么是线程池呢?
预先准备好若干个线程等待着执行任务,当任务来了,就从准备好的线程中拿一个线程来执行我们的任务(这里存放线程的容器,我们叫做线程池)。
线程池带来的好处是:在有大量需要执行的任务情况下,节约资源,避免频繁地创建和销毁线程带来的资源消耗。
二、实现线程池的核心类ThreadPoolExecutor
在Java中,实现线程池的核心类是ThreadPoolExecutor。通过查看源代码我们可以发现,它的继承结构为:
ThreadPoolExecutor extends AbstractExecutorService implements ExecutorService extends Executor
该类的主要核心方法如下:
execute():向线程池提交一个任务,交由线程池去执行;
submit():用来向线程池提交任务,可返回执行结果;
shutdown():关闭线程池,不接受新的任务,它会等待所有的任务全部执行完;
shutdownNow():关闭线程池,不接受新的任务,它会尝试去终止正在执行的任务;
三、Java线程池实现原理
1、线程池状态
RUNNING:线程池状态后的初始化状态;
SHUTDOWN:调用shutdown()方法之后线程池的状态;
STOP:调用shutdownNow()方法之后线程池的状态;
TERMINATED:当线程池处于SHUTDOWN或STOP状态,并且没有执行中的任务的时候,线程池状态会被置为TERMINATED;
2、任务的执行
当一个任务交给线程池之后,线程池会进行如下的一些处理:
如果当前线程池中的线程数目小于corePoolSize,则每来一个任务,就会创建一个线程去执行这个任务;
如果当前线程池中的线程数目>=corePoolSize,则每来一个任务,会尝试将其添加到任务缓存队列当中,若添加成功,则该任务会等待空闲线程将其取出去执行;若添加失败(一般来说是任务缓存队列已满),则会尝试创建新的线程去执行这个任务;
如果当前线程池中的线程数目达到maximumPoolSize,则会采取任务拒绝策略进行处理;
如果线程池中的线程数量大于corePoolSize时,如果某线程空闲时间超过keepAliveTime,线程将被终止,直至线程池中的线程数目不大于corePoolSize;如果允许为核心池中的线程设置存活时间,那么核心池中的线程空闲时间超过keepAliveTime,线程也会被终止。
注:其中corePoolSize表示线核心池的大小,maximumPoolSize表示线程池能容纳的最大线程数量,keepAliveTime表示线程存活时间。
3、线程池中线程的初始化
在默认情况下,创建线程池之后,线程池中是没有线程的,需要提交任务之后才会创建线程。
4、线程池涉及到的几种队列(类型BlockingQueue<Runnable>)
1)、ArrayBlockingQueue:基于数组的先进先出队列,此队列创建时必须指定大小;
2)、LinkedBlockingQueue:基于链表的先进先出队列,如果创建时没有指定此队列大小,则默认为Integer.MAX_VALUE;
3)、synchronousQueue:这个队列比较特殊,它不会保存提交的任务,而是将直接新建一个线程来执行新来的任务。
5、线程池的任务拒绝策略
当线程池的任务缓存队列已满并且线程池中的线程数目达到maximumPoolSize,如果还有任务到来就会采取任务拒绝策略,通常有以下四种策略:
ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务
6、线程池的关闭
shutdown():不会立即终止线程池,而是要等所有任务缓存队列中的任务都执行完后才终止,但再也不会接受新的任务
shutdownNow():立即终止线程池,并尝试打断正在执行的任务,并且清空任务缓存队列,返回尚未执行的任务
7、线程池容量的动态调整
ThreadPoolExecutor.setCorePoolSize:设置核心池大小
ThreadPoolExecutor.setMaximumPoolSize:设置线程池最大能创建的线程数目大小
四、Java线程池的使用
通过ThreadPoolExecutor创建线程池的方式为:ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 10, 200, TimeUnit.MILLISECONDS,new ArrayBlockingQueue<Runnable>(5));但是通常我们不这么做,而是使用Executors类中的几个静态方法来创建线程池:
Executors.newCachedThreadPool(); //创建一个缓冲池,缓冲池容量大小为Integer.MAX_VALUE
Executors.newSingleThreadExecutor(); //创建容量为1的缓冲池
Executors.newFixedThreadPool(int); //创建固定容量大小的缓冲池
具体Demo示例代码如下:
package com.tu.test;
import java.util.concurrent.ArrayBlockingQueue;
//import java.util.concurrent.ExecutorService;
//import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class ThreadPoolExecutorTest {
public static void main(String[] args) {
//创建一个线程池
ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(5, 10, 200, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(5));
//ExecutorService poolExecutor = Executors.newScheduledThreadPool(5);
//ExecutorService poolExecutor = Executors.newCachedThreadPool();
for (int i = 0;i<15;i++) {
MyPoolTask task = new MyPoolTask(i);
//向线程池提交任务
poolExecutor.execute(task);
// System.out.println("线程池中线程数目:"+poolExecutor.getPoolSize()+",队列中等待执行的任务数目:"+
// poolExecutor.getQueue().size()+",已执行完的任务数目:"+poolExecutor.getCompletedTaskCount());
}
//关闭线程池
poolExecutor.shutdown();
}
}
执行结果如下:
Task2正在执行。。。
Task1正在执行。。。
Task3正在执行。。。
Task10正在执行。。。
Task11正在执行。。。
Task14正在执行。。。
Task12正在执行。。。
Task0正在执行。。。
Task4正在执行。。。
Task13正在执行。。。
Task2执行结束。。。
Task5正在执行。。。
Task1执行结束。。。
Task3执行结束。。。
Task6正在执行。。。
Task7正在执行。。。
Task10执行结束。。。
Task8正在执行。。。
Task11执行结束。。。
Task9正在执行。。。
Task12执行结束。。。
Task14执行结束。。。
Task0执行结束。。。
Task4执行结束。。。
Task13执行结束。。。
Task5执行结束。。。
Task7执行结束。。。
Task6执行结束。。。
Task8执行结束。。。
Task9执行结束。。。