目录
一、线程池
1.概念
线程池其实就是一中多线程处理形式,处理过程中可以将任务添加到队列中,然后在创建线程后自动启动这些任务。这里的线程就是我们前面学过的线程,这里的任务就是我们前面学过的实现了Runnable或Callable接口的实例对象。
2.线程池的常用类和接口
在Java标准库提供了如下几个类或接口,来操作并使用线程池:
1.ExecutorService接口:进行线程池的操作访问;
2.Executors类:创建线程池的工具类;
3.ThreadPoolExecutor及其子类:封装线程池的核心参数和运行机制。
线程池的基本使用可见线程创建方式4.
3.线程池的常见使用方法
● 执行无返回值的线程任务:void execute(Runnable command);
● 提交有返回值的线程任务:Future<T> submit(Callable<T> task);
● 关闭线程池:void shutdown(); 或 shutdownNow();
● 等待线程池关闭:boolean awaitTermination(long timeout, TimeUnit unit);
执行线程任务:
execute()只能提交Runnable类型的任务,没有返回值,而submit()既能提交Runnable类型任务也能提交Callable类型任务,可以返回Future类型结果,用于获取线程任务执行结果。
execute()方法提交的任务是直接抛出异常,而submit()方法是捕获异常,当调用Future的get()方法获取返回值时,才会抛出异常。
关闭线程池:
线程池在程序结束的时候要关闭。shutDown()方法会等执行完任务然后再关闭。shutdownNow()会立刻停止正在执行的任务并且关闭;
当使用awaitTermination(timeout,TimeUnit)方法时,主线程会处于一种等待的状态,按照指定的timeout检查线程池。一般用来判断线程池是否关闭,配合while使用。
二、线程池的优点
1、重用存在的线程,减少对象创建销毁的开销。
2、可有效的控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞
争,避免堵塞。
3、提供定时执行、定期执行、单线程、并发数控制等功能。
三、线程池的执行流程和配置参数
执行流程:
1. 提交一个新线程任务,线程池会在线程池中分配一个空闲线程,用于执行线程任务;
2. 如果线程池中不存在空闲线程,则线程池会判断当前“存活的线程数”是否小于核心线程数corePoolSize。
① 如果小于核心线程数corePoolSize,线程池会创建一个新线程(核心线程)去处理新线程任务; 如果大于核心线程数corePoolSize,线程池会检查工作队列;
② 如果工作队列未满,则将该线程任务放入工作队列进行等待。线程池中如果出现空闲线程,将从工作队列中按照FIFO的规则取出1个线程任务并分配执行; 如果工作队列已满,则判断线程数是否达到最大线程数maximumPoolSize;
③ 如果当前“存活线程数”没有达到最大线程数maximumPoolSize,则创建一个新线 程(非核心线程)执行新线程任务;
如果当前“存活线程数”已经达到最大线程数maximumPoolSize,直接采用拒绝策 略处理新线程任务;
综上所述,执行顺序为:空闲线程——>核心线程——>工作队列——>最大线程数——>拒绝策略
配置参数:
参数 | 描述 |
---|---|
int corePoolSize | 核心线程池的大小(创建后会不被回收) |
int maximumPoolSize | 线程池最大线程数 |
long keepAliveTime | 超时时间:可被回收的线程的空闲时间大于该值会被回收 |
TimeUnit unit | 超时单位 |
BlockingQueue<Runnable> workQueue | 阻塞队列:存储等待执行的任务 |
ThreadFactory threadFactory | 用于创建线程,以及自定义线程名称,需要实现ThreadFactory接口 |
RejectedExecutorHandler handler | 拒绝策略:当线程池内的线程耗尽,并且工作队列达到已满时,新提交的任务,将使用拒绝策略进行处理 |
代码如下:
自定义线程池:
//自定义线程池(自定义线程工厂)
public class Demo4 {
public static void main(String[] args) {
//参数1:线程池核心线程数
//参数2:线程池最大线程数
//参数3:非核心线程存活时间
//参数4:时间单位
//参数5:阻塞工作队列
//参数6:线程工厂
ExecutorService executorService = new ThreadPoolExecutor(10,11,1, TimeUnit.SECONDS,new LinkedBlockingDeque<>(),new MyThreadFactory());
for (int i = 0;i < 11;i++) {
executorService.execute(new Task1("订单" + i));
}
executorService.shutdown();
}
}
//实现类
class Task1 implements Runnable{
private String name;
public Task1(String name){
this.name = name;
}
@Override
public void run() {
System.out.println("启动线程——>" + this.name + "(" + LocalDateTime.now() + ")");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("关闭线程<——" + this.name + "(" + LocalDateTime.now() + ")");
}
}
自定义线程工厂:
//自定义线程工厂
public class MyThreadFactory implements ThreadFactory {
private final AtomicInteger number = new AtomicInteger(0);
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r);
t.setName("线程:" + number.incrementAndGet());
return t;
}
}
四、线程池的状态
线程池的状态:
· RUNNING:运行状态,线程池一旦被创建,就会处于RUNNING状态,并且线程池中的任务数为0.该状态的线程池会接受新任务,并处理工作队列中的任务。
· SHUTDOWN:关闭状态,调用shutdown()方法可以切换到该状态,该状态的线程池不会接收新任务,但会处理工作队列中的任务;
· STOP:停止状态,调用shutdownNow()方法可以切换到该状态,该状态不会接收新任务,也不会处理阻塞队列中的任务,而且会中断正在运行的任务;
· TIDYING:整理状态,该状态表明所有的任务已经运行终止,记录的任务数量为0;
· TERMINATED:终止状态,该状态表示线程池彻底关闭。
线程池使用注意事项:
在《阿里巴巴java开发手册》中指出了线程资源必须通过线程池提供,不允许在应用中自行显示的创建,一方面是使线程的创建更加规范,合理开辟线程数量;另一方面将线程的细节管理交给线程池处理,优化了资源的开销。因为newFixedThreadPool()、newSingleThreadExecutor()、NewCachedTheadPool()等方法有局限性,不够灵活;应使用ThreadPoolExecutor有助于明确线程池的运行规则,创建符合自己业务场景需要的线程池,避免资源耗尽的风险,同时应避免线程使用默认名称。