●线程池就是一个可以复用线程的技术。
●如果用户每发起一个请求,后台就创建一个新线程来处理,下次新任务来了又要创建新线程,而创建新线程的开销是很大的,这样会严重影响系统的性能。
●首先会线程池会在内部放一些固定的工作线程,每当来一个任务时,就会生成一个核心线程来处理,再来一个任务,就会再生成一个核心线程来处理。
●假设线程池控制只能生成三个工作线程,那么此时线程池就已经达到了上限,再进入新任务时就不会产生新的线程,而是已经生成的线程会等当前的任务完成之后,再去依次处理新的任务,从而实现三个线程可以处理很多的任务,避免突然生成很多的线程,严重影响系统的性能。
●JDK 5.0起提供了代表线程池的接口:ExecutorService
●方式一:使用ExecutorService的实现类ThreadPoolExecutor自创建一个线程池对象
●方式二:使用Executors(线程池的工具类)调用方法返回不同特点的线程池对象
●参数一:指定线程池的线程数量(核心线程): corePoolSize ----------------------------------> 不能小于0
●参数二:指定线程池可支持的最大线程数: maximumPoolSize -----------> 最大数量 >= 核心线程数量
●参数三:指定临时线程的最大存活时间: keepAliveTime -----------------------------------------> 不能小于0
●参数四:指定存活时间的单位(秒、分、时、天): unit ----------------------------------------------> 时间单位
●参数五:指定任务队列: workQueue ------------------------------------------------------------------> 不能为null
●参数六:指定用哪个线程工厂创建线程: threadFactory ------------------------------------------> 不能为null
●参数七:指定线程忙,任务满的时候,新任务来了怎么办: handler ---------------------------> 不能为null
抽象化理解:
●把线程池假设为一个餐厅,任务当作顾客,线程当作员工
●参数一:指定线程池的线程数量(核心线程): corePoolSize,就相当于餐厅里的正式员工,始终存在。
●参数二:指定线程池可支持的最大线程数: maximumPoolSize ,就相当于餐厅里的最多员工数,包括了正式员工和临时员工。
●参数三:指定临时线程的最大存活时间: keepAliveTime,是当每个顾客都有一名正式员工服务时,临时员工就空闲下来了,临时线程的最大存活时间就相当于临时员工最多可以空闲的多久后被开除。
●参数四:指定存活时间的单位(秒、分、时、天): unit ,相当于指定临时员工可以空闲的多久后被开除。
●参数五:指定任务队列: workQueue,就相当于餐厅外给等待的顾客坐的座位,座位数目是固定的。
●参数六:指定用哪个线程工厂创建线程: threadFactory,就相当于是餐厅里招服务员的HR
●参数七:指定线程忙,任务满的时候,新任务来了怎么办: handler,假设餐厅里有10个正式员工,5个临时员工,来了15个顾客,此时每个员工都在忙,并且餐厅外等待的座位也坐满了,此时如果再来客人如何处理,就是handler
●新任务提交时发现核心线程都在忙,任务队列也满了,并且还可以创建临时线程,此时才会创建临时线程。
●核心线程和临时线程都在忙,任务队列也满了,新的任务过来的时候才会开始任务拒绝。
方法名称 | 说明 |
---|---|
void execute(Runnable command) | 执行任务/命令,没有返回值,一般用来执行 Runnable 任务 |
Future submit(Callable task) | 执行任务,返回未来任务对象获取线程结果,一般拿来执行 Callable 任务 |
void shutdown() | 等任务执行完毕后关闭线程池 |
List shutdownNow() | 立刻关闭,停止正在执行的任务,并返回队列中未执行的任务 |
策略 | 详解 |
---|---|
ThreadPoolExecutor.AbortPolicy | 丢弃任务并抛出RejectedExecutionException异常。是默认的策略 |
ThreadPoolExecutor.DiscardPolicy | 丢弃任务,但是不抛出异常 这是不推荐的做法 |
ThreadPoolExecutor.DiscardOldestPolicy | 抛弃队列中等待最久的任务 然后把当前任务加入队列中 |
ThreadPoolExecutor.CallerRunsPolicy | 由主线程负责调用任务的run()方法从而绕过线程池直接执行 |
拒绝策略出发后,会丢弃任务并抛出RejectedExecutionException异常:
Exception in thread “main” java.util.concurrent.RejectedExecutionException: Task SummerDay36_threadpool.MyRunnable@34a245ab rejected from java.util.concurrent.ThreadPoolExecutor@7cc355be[Running, pool size = 5, active threads = 5, queued tasks = 5, completed tasks = 0]
不慌,根本不用强行记忆,按住Ctrl键,光标移到ThreadPoolExecutor上,单击后就会出现选择声明,选择最后一个有七个参数的声明点击,就可以看见这七个参数的具体使用啦~
public class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 1; i <= 5; i++) {
System.out.println(Thread.currentThread().getName() + "输出了:" + i);
}
try {
System.out.println(Thread.currentThread().getName() + "本任务与线程绑定了,线程在忙");
Thread.sleep(1000000);//让线程睡眠
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* 目标:自定义一个线程池,并测试其特性
*/
public class ThreadDemo01 {
public static void main(String[] args) {
//1.创建线程池对象
/**
* public ThreadPoolExecutor(int corePoolSize,
* int maximumPoolSize,
* long keepAliveTime,
* TimeUnit unit,
* BlockingQueue workQueue,
* ThreadFactory threadFactory,
* RejectedExecutionHandler handler)
*/
ExecutorService pool = new ThreadPoolExecutor(3,5,6,
TimeUnit.SECONDS,new ArrayBlockingQueue<>(5),Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
//2.给任务线程池处理
Runnable target = new MyRunnable();
//① corePoolSize = 3 最大核心线程数是三,当任务数少于等于三个时不会创建新的线程
pool.execute(target);
pool.execute(target);
pool.execute(target);
//② BlockingQueue workQueue = new ArrayBlockingQueue<>(5)
//当任务数 > 3 并且 <= (5+3) 时,线程池认为现有的三个核心线程数忙的过来,也不会创建新的线程
pool.execute(target);
pool.execute(target);
pool.execute(target);
pool.execute(target);
pool.execute(target);
//③ 如果任务数 > 8 时,临时线程就会开始创建了
pool.execute(target);
pool.execute(target);
//④ 此时三个核心线程和两个临时线程均在忙,任务满了,就触发了拒绝策略,不再创建!!
pool.execute(target);
pool.execute(target);
//⑤ 关闭线程池(开发中一般是不会使用的)
//pool.shutdownNow();//立即关闭,即使任务没有完成,丢失任务的!
pool.shutdown();//会等待全部任务执行完毕之后再关闭
}
}
线程池如何处理Runnable任务?
●使用ExecutorService的方法:
●void execute(Runnable target)
方法名称 | 说明 |
---|---|
void execute(Runnable command) | 执行任务/命令,没有返回值,一般用来执行 Runnable 任务 |
Future submit(Callable task) | 执行任务,返回未来任务对象获取线程结果,一般拿来执行 Callable 任务 |
void shutdown() | 等任务执行完毕后关闭线程池 |
List shutdownNow() | 立刻关闭,停止正在执行的任务,并返回队列中未执行的任务 |
●使用ExecutorService的方法:
●void execute(Runnable target)
/**
1、定义一个任务类 实现Callable接口 应该申明线程任务执行完毕后的结果的数据类型
*/
public class MyCallable implements Callable {
private int n;
public MyCallable(int n) {
this.n = n;
}
/**
2、重写call方法(任务方法)
*/
@Override
public String call() throws Exception {
int sum = 0;
for (int i = 1; i <= n ; i++) {
sum += i;
}
return Thread.currentThread().getName()
+ "执行 1-" + n + "的和,结果是:" + sum;
}
}
public class ThreadDemo02 {
public static void main(String[] args) throws Exception {
// 1、创建线程池对象
/**
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
*/
ExecutorService pool = new ThreadPoolExecutor(3, 5 ,
6, TimeUnit.SECONDS, new ArrayBlockingQueue<>(5) , Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy() );
//2.给任务线程池处理
Future f1 = pool.submit(new MyCallable(100));
Future f2 = pool.submit(new MyCallable(200));
Future f3 = pool.submit(new MyCallable(300));
Future f4 = pool.submit(new MyCallable(400));
// String rs = f1.get();
// System.out.println(rs);
System.out.println(f1.get());
System.out.println(f2.get());
System.out.println(f3.get());
System.out.println(f4.get());
}
}
●Executors:线程池的工具类通过调用方法返回不同类型的线程池对象。
方法名称 | 说明 |
---|---|
public static ExecutorService newCachedThreadPool() | 线程数量随着任务增加而增加,如果线程任务执行完毕且空闲了一段时间则会被回收掉。 |
public static ExecutorService newFixedThreadPool(int nThreads) | 创建固定线程数量的线程池,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程替代它。 |
public static ExecutorService newSingleThreadExecutor () | 创建只有一个线程的线程池对象,如果该线程出现异常而结束,那么线程池会补充一个新线程。 |
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) | 创建一个线程池,可以实现在给定的延迟后运行任务,或者定期执行任务。 |
●注意:Executors的底层其实也是基于线程池的实现类ThreadPoolExecutor创建线程池对象的。
●大型并发系统环境中使用Executors如果不注意可能会出现系统风险。
方法名称 | 说明 |
---|---|
public static ExecutorService newFixedThreadPool(int nThreads) | 允许请求的任务队列长度是Integer.MAX_VALUE,可能出现OOM错误( java.lang.OutOfMemoryError ) |
public static ExecutorService newSingleThreadExecutor() | |
public static ExecutorService newCachedThreadPool() | 创建的线程数量最大上限是Integer.MAX_VALUE,线程数可能会随着任务1:1增长,也可能出现OOM错误(java.lang.OutOfMemoryError) |
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) |
●线程池ExecutorService的实现类:ThreadPoolExecutor
●不合适。
●建议使用ThreadPoolExecutor来指定线程池参数,这样可以明确线程池的运行规则,规避资源耗尽的风险。