线程池:
线程池提供了一种限制和管理资源(包括执行一个任务)。每个线程池还维护了一些基本统计信息。池化技术的思想主要是为了减少每次获取资源的消耗,提高对资源的利用率。
使用线程池的好处:
- 降低资源消耗。(重复利用已创建的线程,降低了线程重复创建和销毁造成的消耗。)
- 提高响应速度(当任务到达时,不需要等到线程创建就能立即执行。)
- 提高线程的可管理性(无限制的创建线程,会消耗系统资源和降低系统的稳定性,使用线程池可以统一的分配,调优和监控)
线程池的创建
在《阿里巴巴Java开发手册中》规定不允许使用Executors工具类创建线程,而是通过ThreadPoolExecutor构造器创建,这样可以避免OOM的发生。
1、通过构造方法创建线程池
ThreadPoolExecutor类中提供了四个构造方法,其中三个都是在下面方法的基础上添加了一些默认参数。
/**
* Creates a new ThreadPoolExecutor with the given initial
* parameters.
*/
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
参数分析(七大参数):
- corePoolSize:即使在空闲状态下也要保留在池中的线程数,除非设置了allowCoreThreadTimeOut
- maximumPoolSize:池中允许的最大线程数
- keepAliveTime:当池中线程数大于内核数(corePoolSize)时,keepAliveTime是多余的空闲线程将在终止之前等待新任务的最长时间。
- unit:keepAliveTime参数的时间单位
- workQueue:在执行任务之前用于保留任务的队列。该队列将仅保存由代码execute方法提交的Runnable任务。
- threadFactory:执行程序创建新线程时要使用的工厂
- handler:(饱和策略)因为达到了线程界限和队列容量而在执行被阻止时使用的处理程序
ThreadPoolExecutor饱和策略
如果当前同时运行的线程数量达到最大线程数量,并且队列也已经被放满任务时,ThreadPoolTaskExecutor定义了一些策略:
- ThreadPoolExecutor.AbortPolicy(默认):抛出
RejectedExecutionException
来拒绝新任务的处理 - ThreadPoolExecutor.CallerRunsPolicy:调用执行自己的线程运行任务。这种策略会减低新任务的提交速度,影响程序的整体性能。另外,这个策略喜欢增加队列容量。如果你的应用程序可以承受延迟并且你不能任意丢弃任何一个任务请求时,你可以选择这个策略。
- ThreadPoolExecutor.DiscardPolicy:不处理新任务,直接丢弃掉。
- ThreadPoolExecutor.DiscardOldestPolicy:此策略将丢弃最早的为处理的任务请求。
2、通过 工具类Executors 创建线程池的四种方法:
* 第一种:创建一个单线程的线程池
ExecutorService executorService = Executors.newSingleThreadExecutor();
* 第二种:创建一个固定大小的线程池,参数为线程的数量;
ExecutorService executorService = Executors.newFixedThreadPool(2);
* 第三种:创建一个可缓存的线程池,该线程池可回收部分空闲(60秒不执行任务)的线程,
可自动增加线程.此线程池不会限制线程池的大小啊
ExecutorService executorService = Executors.newCachedThreadPool();
* 第四种:创建一个大小无限的线程池,此线程池支持定时以及周期性执行任务.
ExecutorService executorService = Executors.newScheduledThreadPool(3);
缺点:
- 第一、二方法 ,线程池大小固定,允许请求的队列长度为Integer.MAX_VALUE,可能堆积大量请求,从而导致OOM。
- 第三、四方法,线程池大小不固定,允许创建线程数量为Integer.MAX_VALUE,可能创建大量线程,从而导致OOM。
线程池的启动:
1、execute()方法用于提交不需要返回值的任务,所以无法判断任务被线程池执行与否;
2、submit()方法用于提交需要返回值的任务。线程池会返回一个future类型的对象,可以通过这个Future对象判断任务是否执行成功。在submit中调用了execute()方法。
executorService.submit(new MyRunnable5());
executorService.execute(new MyRunnable5());
结束线程池
//结束线程池
executorService.shutdown();
案例: Executors
package com.DuoXianCheng;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* 线程池
* 线程池是预先创建线程的一种技术.
*
*/
public class ThreadDemo5 {
public static void main(String[] args) {
//创建线程池(4种) 第二种运用的比较多.
//第一种:创建一个单线程的线程池
// ExecutorService executorService = Executors.newSingleThreadExecutor();
//第二种:创建一个固定大小的线程池,参数为线程的数量;
//ExecutorService executorService = Executors.newFixedThreadPool(2);
//第三种:创建一个可缓存的线程池,该线程池可回收部分空闲(60秒不执行任务)的线程,
// 可自动增加线程.此线程池不会限制线程池的大小啊
// ExecutorService executorService = Executors.newCachedThreadPool();
//第四种:创建一个大小无限的线程池,此线程池支持定时以及周期性执行任务.
ExecutorService executorService = Executors.newScheduledThreadPool(3);
//启动
executorService.execute(new MyRunnable5());
executorService.execute(new MyRunnable5());
//结束线程池
executorService.shutdown();
}
}
class MyRunnable5 implements Runnable{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+"---"+i);
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
execute()的源码分析
我们先来看看线程池执行方法 execute()的源码。
// 存放线程池的运行状态 (runState) 和线程池内有效线程数 (workerCount)
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
private static int workerCountOf(int c) {
return c & CAPACITY;
}
private final BlockingQueue<Runnable> workQueue;
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
//获取ctl中保存的线程池当前的状态信息
int c = ctl.get();
/**
*如果正在运行的线程少于corePoolSize线程,
*请尝试使用给定任务作为其第一个任务来启动新线程。
*对addWorker的调用从原子上检查runState和workerCount,
*从而通过返回false来防止在不应该添加线程的情况下发出虚假警报。
*/
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
/**
*如果一个任务可以成功排队,那么我们仍然需要仔细检查
*是否应该添加一个线程(因为现有线程自上次检查后就死掉了)
*或该池自进入此方法后就关闭了。因此,我们重新检查状态,
*并在必要时回滚排队(如果已停止),或者在没有线程的情况下启动新线程。
*/
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
/**
*如果我们无法将任务排队,则尝试添加一个新线程。
*如果失败,则表明我们已关闭或已饱和,因此拒绝该任务。
*/
else if (!addWorker(command, false))
reject(command);//执行指定的拒绝策略
}
1、execute()方法的返回值为void类型,所以该方法没有返回值。即不能确定任务是否被执行;
2、在该方法中,先判断任务是否为null,如果为null,则会抛出null指针异常
3、通过ctl.get()方法获取ctl中保存的线程池当前的状态信息
4、然后进行以下三个步骤:
- 如果正在运行的线程少于corePoolSize线程,请尝试使用给定命令作为其第一个任务来启动新线程。对addWorker的调用从原子上检查runState和workerCount,从而通过返回false来防止在不应该添加线程的情况下发出虚假警报。
- 如果一个任务可以成功排队,那么我们仍然需要仔细检查是否应该添加一个线程(因为现有线程自上次检查后就死掉了)或该池自进入此方法后就关闭了。因此,我们重新检查状态,并在必要时回滚排队(如果已停止),或者在没有线程的情况下启动新线程。
- 如果我们无法将任务排队,则尝试添加一个新线程。如果失败,则表明我们已关闭或已饱和,因此拒绝该任务。
- 图解