并发编程:ThreadPoolExecutor 类
文章目录
前言
java中创建线程池来创建线程已经成为了一种时尚,原因在于(1)每次 new Thread 新建对象,性能差。(2)线程缺乏统一管理,可能无限制的新建线程,相互竞争,有可能占用过多系统资源导致死机或 OOM。(3)缺少更多的功能,如更多执行、定期执行、线程中断。这些通过线程池都能解决。
一、线程池是什么?
1.线程池类扩展
2.创建线程池常用的类
Executors.newCachedThreadPool:创建一个可缓存的线程池,如果线程池的大小超过了需要,可以灵活回收空闲线程,如果没有可回收线程,则新建线程
Executors.newFixedThreadPool:创建一个定长的线程池,可以控制线程的最大并发数,超出的线程会在队列中等待
Executors.newScheduledThreadPool:创建一个定长的线程池,支持定时、周期性的任务执行
Executors.newSingleThreadExecutor: 创建一个单线程化的线程池,使用一个唯一的工作线程执行任务,保证所有任务按照指定顺序(先入先出或者优先级)执行
Executors.newSingleThreadScheduledExecutor:创建一个单线程化的线程池,支持定时、周期性的任务执行
Executors.newWorkStealingPool:创建一个具有并行级别的 work-stealing 线程池
3.线程池实例的几种状态
Running:运行状态,能接收新提交的任务,并且也能处理阻塞队列中的任务
Shutdown: 关闭状态,不能再接收新提交的任务,但是可以处理阻塞队列中已经保存的任务,当线程池处于 Running 状态时,调用 shutdown()方法会使线程池进入该状态
Stop: 不能接收新任务,也不能处理阻塞队列中已经保存的任务,会中断正在处理任务的线程,如果线程池处于 Running 或 Shutdown 状态,调用 shutdownNow()方法,会使线程池进入该状态
Tidying: 如果所有的任务都已经终止,有效线程数为 0(阻塞队列为空,线程池中的工作线程数量为 0),线程池就会进入该状态。
Terminated: 处于 Tidying 状态的线程池调用 terminated()方法,会使用线程池进入该状态
4.ThreadPoolExecutor类讲解
先上代码:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler rejectHandler) {}
参数分析:
(1)corePoolSize:核心线程数量。
(2)maximumPoolSize:最大线程数。
(3)workQueue:阻塞队列,存储等待执行的任务,很重要,会对线程池运行过程产生重大影响。
(4)keepAliveTime:线程没有任务执行时最多保持多久时间终止当线程池中的线程数量大于 corePoolSize 时,如果此时没有新的任务提交,核心线程外的线程不会立即销毁,需要等待,直到等待的时间超过了 keepAliveTime 就会终止。
(5)unit:keepAliveTime 的时间单位
(6)threadFactory:线程工厂,用来创建线程默认会提供一个默认的工厂来创建线程,当使用默认的工厂来创建线程时,会使新创建的线程具有相同的优先级,并且是非守护的线程,同时也设置了线程的名称
(7)rejectHandler:拒绝处理任务时的策略
5 .线程池创建流程
- 如果运行的线程数小于 corePoolSize,直接创建新线程处理任务,即使线程池中的其他线程是空闲的。
- 如果运行的线程数大于等于 corePoolSize,并且小于 maximumPoolSize,此时,只有当 workQueue
满时,才会创建新的线程处理任务。 - 如果设置的 corePoolSize 与 maximumPoolSize 相同,那么创建的线程池大小是固定的,此时,如果有新任务提交,并且
workQueue 没有满时,就把请求放入到 workQueue 中,等待空闲的线程,从 workQueue 中取出任务进行处理。 - 如果运行的线程数量大于 maximumPoolSize,同时,workQueue 已经满了,会通过拒绝策略参数 rejectHandler
来指定处理策略。
6 .线程池执行队列
- 直接切换常用的队列就是 SynchronousQueue。
- 使用无限队列就是使用基于链表的队列,比如:LinkedBlockingQueue,如果使用这种方式,线程池中创建的最大线程数就是
corePoolSize,此时 maximumPoolSize
不会起作用。当线程池中所有的核心线程都是运行状态时,提交新任务,就会放入等待队列中。 - 使用有界队列使用的是 ArrayBlockingQueue,使用这种方式可以将线程池的最大线程数量限制为
maximumPoolSize,可以降低资源的消耗。但是,这种方式使得线程池对线程的调度更困难,因为线程池和队列的容量都是有限的了。
7 .线程池拒绝策略
- 直接抛出异常,这也是默认的策略。实现类为 AbortPolicy。
- 用调用者所在的线程来执行任务。实现类为 CallerRunsPolicy。
- 丢弃队列中最靠前的任务并执行当前任务。实现类为 DiscardOldestPolicy。
- 直接丢弃当前任务。实现类为 DiscardPolicy。
8 .常用方法
(1)execute():提交任务,交给线程池执行
(2)submit():提交任务,能够返回执行结果 execute+Future
(3)shutdown():关闭线程池,等待任务都执行完
(4)shutdownNow():立即关闭线程池,不等待任务执行完
(5)getTaskCount():线程池已执行和未执行的任务总数
(6)getCompletedTaskCount():已完成的任务数量
(7)getPoolSize():线程池当前的线程数量
(8)getCorePoolSize():线程池核心线程数
(9)getActiveCount():当前线程池中正在执行任务的线程数量
二、线程池使用步骤
1. newCachedThreadPool()
创建一个可根据需要创建新线程的线程池,但是在以前构造的线程可用时将重用它们。
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ExecutorsTest {
/**
* @param args
*/
public static void main(String[] args) {
//线程数
int num = 10;
//CountDownLatch是一个同步辅助类也可以使用AtomicInteger替代
CountDownLatch doneSignal = new CountDownLatch(num);
ExecutorService pool = Executors.newCachedThreadPool();
for(int i=0;i<num;i++)
//在未来某个时间执行给定的命令
pool.execute(new WorkerRunnable(doneSignal, i));
try {
doneSignal.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
//子线程执行完毕,可以开始后续任务处理了
System.out.println("所有任务执行完毕");
}
}
class WorkerRunnable implements Runnable {
private final CountDownLatch doneSignal;
private final int i;
WorkerRunnable(CountDownLatch doneSignal, int i) {
this.doneSignal = doneSignal;
this.i = i;
}
public void run() {
//子线程的任务
try{
doWork(i);
}catch (Exception e) {
e.printStackTrace();
}
//任务执行完毕递减锁存器的计数
doneSignal.countDown();
}
void doWork(int i) {
System.out.println("这是第"+(i+1)+"个任务");
}
}
2. newFixedThreadPool(int nThreads)
创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程。
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ExecutorsTest {
/**
* @param args
*/
public static void main(String[] args) {
线程数
int num = 10;
//CountDownLatch是一个同步辅助类也可以使用AtomicInteger替代
CountDownLatch doneSignal = new CountDownLatch(num);
ExecutorService pool = Executors.newFixedThreadPool(num);
for(int i=0;i<num;i++)
//在未来某个时间执行给定的命令
pool.execute(new WorkerRunnable(doneSignal, i));
try {
doneSignal.await();
} catch (InterruptedException e) {
e.printStackTrace();
}finally{
//关闭线程池
pool.shutdown();
}
//子线程执行完毕,可以开始后续任务处理了
System.out.println("所有任务执行完毕");
}
}
class WorkerRunnable implements Runnable {
private final CountDownLatch doneSignal;
private final int i;
WorkerRunnable(CountDownLatch doneSignal, int i) {
this.doneSignal = doneSignal;
this.i = i;
}
public void run() {
//子线程执行任务
try{
doWork(i);
} catch (Exception e) {
e.printStackTrace();
}
//任务执行完毕递减锁存器的计数
doneSignal.countDown();
}
void doWork(int i) {
System.out.println("这是第"+(i+1)+"个任务");
}
}
总结
从上面来看,线程池至少有以上几种好处:
(1)重用存在的线程,减少对象创建、消亡的开销,性能佳。
(2)可以有效控制最大并发线程数,提高系统资源利用率,同时可以避免过多资源竞争,避免阻塞。
(3)提供定时执行、定期执行、单线程、并发数控制等功能。
(4)提供支持线程池监控的方法,可对线程池的资源进行实时监控。