1.为什么要用线程池
线程池:管理线程的池子。
降低资源损耗和提高响应速度,因为线程也是对象,不断的创建和销毁对象损耗性能,我先准备几个线程对象,不用需要一个线程创建一个线程,提高响应速度。
重复利用,线程用完,还给池子,达到重复利用,节约资源。
线程执行流程
判断核心线程池是否已满,没满,创建核心线程去执行任务,
核心线程数满了,再看任务队列是否已满,没满,线程加入到任务队列中等待执行
任务队列满了,判断线程数是否达到最大线程数量,没达到,则创建一个非核心线程执行提交的任务。
达到最大线程数量,采用拒绝策略。
四种拒绝策略
抛异常(默认)
直接丢弃任务
丢弃队列里最老的任务,将当前任务继续提交给线程池。
交给线程池所在的线程进行处理
线程池参数,各个参数的作用,如何进行的
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize,long keepAliveTime,TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
-
核心线程池数 corePoolSize
默认情况下,线程池中没有线程,只有等待任务到来才创建线程,当线程数达到核心线程数时,就会将线程加入到任务队列中 -
最大线程池数 maximumPoolSize
允许创建的最大线程数,如果任务队列满了,并且已创建的线程数小于最大线程数,则线程池会创建新的线程去执行任务,当已达到最大线程数,就会拒绝。 -
线程保持时间:keepAliveTime
线程池的工作线程空闲后,保存存活的时间。如果任务较多,每个任务执行的时间比较短,可以让线程的保存时间长一点。 -
unit: 参数keepAliveTime的时间单位。天,小时,分钟,还秒,微秒等
-
任务队列 workQueue
保存等待执行任务的阻塞队列。 -
ArrayBlockingQueue:数组结构的有界阻塞队列,先进先出
-
LinkedBlockingQueue:链表结构的有界阻塞队列,先进先出,吞吐量高于ArrayBlockingQueue。- Executors.newFixedThreadPool()使用了这个队列。
-
SynchronousQueue:一个不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则一直处于阻塞状态。
-PriorityBlockingQueue:一个具有优先级的无界阻塞队列。 -
threadFactory:创建线程的工厂。可以通过线程工厂给每个创建出来的线程设置更有意义的名字
-
handler:拒绝策略。策略默认情况下是 AbortPolicy,表示无法处理新任务时抛出异常。
抛异常(默认)
直接丢弃任务
丢弃队列里最老的任务,将当前任务继续提交给线程池。
交给线程池所在的线程进行处理
结合执行流程。
哪几种任务对列
ArrayBlockingQueue:数组结构的有界阻塞队列,先进先出
LinkedBlockingQueue:链表结构的有界阻塞队列,先进先出,容量可设,不设的话,是一个无边界的阻塞队列。newFixedThreadPool线程池使用了这个队列
PriorityBlockingQueue:具有优先级的无界阻塞队列
DelayQueue(延迟队列)是一个任务定时周期的延迟执行的队列。根据指定的执行时间从小到大排序,否则根据插入到队列的先后排序。newScheduledThreadPool线程池使用了这个队列。
SynchronousQueue:一个不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则一直处于阻塞状态。newCachedThreadPool线程池使用了这个队列。
重要方法
ThreadPoolExecutor 类中有几个非常重要的方法:
excute() 覆写了 Executor 中声明的方法,向线程池提交任务,由线程池去执行
submit()向线程池中提交任务,能够返回任务执行的结果,内部调用的excute()方法,利用了future来获取任务执行结果。
shutdown()和shutNow()关闭线程池。
excute()和submit()区别
excute()方法用于提交不需要返回值的任务,无法判断任务是否被线程成功执行与否、
submit()方法用于提交需要返回值的任务,线程池会返回一个future类型的对象,通过future对象可以判断任务是否执行成功。并且通过get()方法可以获取返回值。get()方法会阻塞当前线程直到任务结束,或get(Long timeout, TimeUnit unit)方法会阻塞当前线程一段时间后立即返回,这时候任务可能没有执行完。
threadsPool.execute(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
}
});
Future<Object> future = executor.submit(harReturnValuetask);
try {
Object s = future.get();
} catch (InterruptedException e) {
// 处理中断异常
} catch (ExecutionException e) {
// 处理无法执行任务异常
} finally {
// 关闭线程池
executor.shutdown();
}
线程池的关闭
shutdown或shutdownNow,原理都是遍历线程池中工作线程,调用线程的intrupt()方法中断线程,线程有可能无法响应中断,永远无法停止。通常用shutdown关闭线程池,如果任务不一定要执行完,则调用shutdownNow.
区别:shutdownNow首先将线程池状态设为STOP,然后尝试停止所有的正在执行或暂停任务的线程,返回等待执行任务的列表。而shutdown只是将线程池的状态设置为SHUTDOWN 状态,然后中断所有没有正在执行任务的线程。
只要调用了这两个关闭方法的其中一个,isShutdown 方法就会返回 true。当所有的任务都已关闭后,才表示线程池关闭成功,这时调用 isTerminaed 方法会返回 true。
创建线程
阿里规范守则,不允许使用Executors创建线程,因为创建的线程没有大小限制,很容易造成资源耗尽,应该使用ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则
Executors弊端
FixedThreadPool 和 SingleThreadExecutor : 允许请求的队列长度为 Integer.MAX_VALUE,可能堆积
大量的请求,从而导致OOM。
CachedThreadPool 和 ScheduledThreadPool : 允许创建的线程数量为 Integer.MAX_VALUE ,可能
会创建大量线程,从而导致OOM。
方法一 构造方法实现
方式二:通过Executor 框架的工具类Executors来实现 我们可以创建三种类型的ThreadPoolExecutor:
FixedThreadPool :线程池数量固定,有空闲的线程则让空闲线程去执行,若没有,则加入到任务对列,等待有空闲的线程。CPU密集型的任务,CPU在长期被工作线程使用的情况下,使用长期任务。
SingleThreadExecutor: 只有一个线程的线程池,若有空闲线层,让空闲线程执行任务,若没有,则加入任务队列等待执行。串行执行的任务,一个一个的执行
CachedThreadPool:根据实际情况调整线程池数量,有空闲则用空闲,没有则创建线程,空闲线程达到时间会自动停止。执行并发大量短期的小任务
newScheduledThreadPool:最大线程数为Integer.MAX_VALUE,线程从DelayQueue中获取time大于等于当前之间的任务, 执行完这个task后设置下次执行时间并放回到DelayQueue队列中。 周期性执行任务的场景,需要限制线程数量的场景
newFixedThreadPool (固定数目线程的线程池) 队列长度无限 CPU密集型的任务,CPU在长期被工作线程使用的情况下,使用长期任务
newCachedThreadPool(可缓存线程的线程池) 队列长度为1 执行并发大量短期的小任务
newSingleThreadExecutor(单线程的线程池) 队列长度无限 串行执行的任务,一个一个的执行
newScheduledThreadPool(定时及周期执行的线程池) 周期性执行任务的场景,需要限制线程数量的场景
使用无界队列的线程池会导致内存飙升吗?
会,newFixedThreadPool使用了无界的阻塞队列LinkedBlockingQueue,如果线程获取一个任务后,任务的执行执行比较长,会导致队列的任务越积越多。导致即内存使用不停的飙升,最终导致OOM
线程池状态
RUNNING,SHUTDOWN,STOP,TIDYING,TERMINATED。
//线程池状态
private static final int RUNNING = -1 << COUNT_BITS;
private static final int SHUTDOWN = 0 << COUNT_BITS;
private static final int STOP = 1 << COUNT_BITS;
private static final int TIDYING = 2 << COUNT_BITS;
private static final int TERMINATED = 3 << COUNT_BITS;
RUNNING
接收新任务,处理阻塞队列中的任务
调用线程池的shutdown()方法,可以切换到SHUTDOWN状态
调用线程池的shutdownNow()方法,可以切换到STOP状态
SHUTDOWN 不管要管正在执行的任务,还要管队列中的任务,只是不接受新任务,很负责
线程池不会接收新任务,但会处理阻塞队列中的任务
队列为空,且线程池中的任务也为空,进入TIDYING状态
STOP 巴不得快点结束,尝试中断正在执行的任务,不管队列中的任务,正在执行的任务搞完后就结束
不会接收新任务,也不会处理阻塞队列中ed任务,而且会中断正在执行的任务
线程中执行任务为空,进入TIDYING状态
TIDYING
所有的任务已经运行终止,记录的任务数量为0
terminated()执行完毕,进入TERMINATED状态
TERMINATED
线程池彻底终止
线程池异常处理
当使用线程池处理任务的时候,任务代码里面可能会抛出RuntimeException,抛出异常后,线程池可能捕获他,也可能创建一个新的线程处理它,这样我们不知道我们创建的任务出现了一样,所以我们要考虑线程池异常的情况。
例如:我们的任务中会抛出异常
ExecutorService threadPool = Executors.newFixedThreadPool(5);
for (int i = 0; i < 5; i++) {
threadPool.submit(() -> {
System.out.println("current thread name" + Thread.currentThread().getName());
Object object = null;
System.out.print("result## "+object.toString());
});
}
虽然没有结果,但是没有抛出异常,所以我们没办法知道我们的代码是否会抛出异常。需要添加try/catch
线程处理了异常,我们可以直接用try/catch捕获。
线层池exec.submit(runnable)执行流程
1.try…catch捕获异常
2.submit执行,Future.get接收异常
作者:老刘
链接:https://zhuanlan.zhihu.com/p/73990200
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。