目录
4、ThreadPoolExecutor的四种构造方法及参数名解析
1、首先创建一个线程了解线程机制
public class TestDemo {
public static void main(String[] args) {
new Thread(() -> {
int i = 1;
System.out.println("线程开始工作...");
while (true) {
try {
Thread.sleep(100);
System.out.println("线程执行第" + i + "次");
i++;
} catch (InterruptedException e) {
e.printStackTrace();
}
if(i == 13){
System.out.println("线程结束工作");
break;
}
}
}).start();
}
}
2、为什么要用线程池
但为了合理控制创建线程的数量和优化资源开销,可以通过线程池提供线程资源,不在业务代码中显示创建线程。使用线程池创建线程的优点如下:
1、使线程的创建更加规范,可以合理控制开辟线程的数量;
2、线程的细节管理交给线程池处理;
3、在执行大量异步任务时,由于减少了每个任务的调用开销,再加上线程池提供了一种限制和管理资源和线程的方法,能够明显提升性能;
4、方便统计信息,每个ThreadPoolExecutor可以保存一些基本的统计信息,例如完成的任务数量。
3、Java Executors类下自带的4种线程池
newSingleThreadExexcutor | 单线程数的线程池(核心线程数=最大线程数=1) |
newFixedThreadPool | 固定线程数的线程池(核心线程数=最大线程数=自定义) |
newCacheThreadPool | 可缓存的线程池(核心线程数=0,最大线程数=Integer.MAX_VALUE) |
newScheduledThreadPool | 支持定时或周期任务的线程池(核心线程数=自定义,最大线程数=Integer.MAX_VALUE) |
但需要注意的是,线程池最好不要使用Executors去创建,而要使用ThreadPoolExecutor。主要原因如下:
1、JDK中的Executor框架虽然提供了newFixedThreadPool()、newSingleThreadExecutor()和newCachedThreadPool()等预定义线程池,但都有局限性,不够灵活;
2、由于前面几种方法内部也是通过ThreadPoolExecutor方式实现,使用ThreadPoolExecutor有助于明确线程池的运行规则,创建符合业务场景需要的线程池,避免资源耗尽的风险。
4、ThreadPoolExecutor的四种构造方法及参数名解析
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue)
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory)
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler)
public ThreadPoolExecutor(int corePoolSize, //核心线程池大小。
int maximumPoolSize, //最大线程池大小。
long keepAliveTime, //线程的最大空闲时间。
TimeUnit unit, //等待时间单位。
BlockingQueue<Runnable> workQueue, //线程阻塞队列。
ThreadFactory threadFactory, //线程创建工厂。
RejectedExecutionHandler handler //线程拒绝策略。)
5、线程池的简单应用
在实际的使用中,ThreadPoolExecutor线程池的创建、线程的执行顺序以及数量限制有以下特点:
1、进入核心线程池的线程将立即开始执行。
2、线程数超过核心线程池大小后,后续线程会进入阻塞队列,阻塞队列满了后再进入线程池开始执行,直到数量到达最大线程池大小。
3、阻塞队列满了后,线程池还能接收的线程数 = 最大线程池大小 - 核心线程池大小。
4、线程池能容纳的最大线程数 = 最大线程池大小 + 阻塞队列容量,线程数量超过该值后,后续加入的线程将触发拒绝策略。
5、allowCoreThreadTimeOut的默认值为false,调用allowCoreThreadTimeOut(true)方法后,超过线程最大空闲时间的线程将被销毁。
线程池的小Demo
任务类示例代码↓
import java.text.SimpleDateFormat;
import java.util.Date;
public class WorkerThread implements Runnable{
private int id;
// 构造方法
public WorkerThread(int id) {
this.id = id;
}
@Override
public void run() {
//设置日期格式
SimpleDateFormat format = new SimpleDateFormat("HH:mm:ss.sss");
System.out.println(format.format(new Date()) + " | The thread " + id + " starts working.");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(format.format(new Date()) + " | The thread " + id + " is down.");
}
}
测试代码↓
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.*;
public class SyncTest {
// 创建线程池
private static ThreadPoolExecutor executor = new ThreadPoolExecutor(2,3,100, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
public static void main(String[] args) {
SimpleDateFormat format = new SimpleDateFormat("HH:mm:ss.sss");
for (int i = 0; i < 9; i++) {
try {
// 向线程池中添加任务
executor.execute(new WorkerThread(i));
System.out.println(format.format(new Date()) +
" | The thread " + i + " joined the thread pool.");
Thread.sleep(100);
} catch (RejectedExecutionException re){
System.out.println(format.format(new Date()) + " | The thread " + i + " was rejected.");
}
catch (InterruptedException e) {
e.printStackTrace();
}
}
executor.shutdown();
}
}
输出结果↓
运行结果分析↓
1、在上述代码中线程池最大可容纳3(最大线程池大小) + 3(阻塞队列容量) = 6个,共有9个线程希望加入。
2、核心线程池大小2,线程0和1进入核心线程池立刻开始执行。
3、阻塞队列大小为3,线程2、3、4共三个线程将依次进入阻塞队列开始等待。
4、最大线程池大小为3,阻塞队列满后,线程池还能接收的线程数 = 3(最大线程池大小) - 2(核心线程池大小) = 1,所以线程5将进入线程池并开始执行。
5、线程池满了后,后续加入的3个线程将触发拒绝策略,线程6、7、8将被直接拒绝无法执行
6、出现结束运行的线程之后,将开始处理阻塞队列中的等待线程,由于使用的是LinkedBlockingQueue,依照先进先出的原则,先执行最早开始等待的线程,所以按照线程2、3、4的顺序开始执行。
6、线程池的其他注意事项
1、如果核心线程数量大于0,线程池在创建后,会一直存在,直到调用shutdown()方法,才会关闭线程池,所以忘记关闭线程池可能会导致内存溢出;
2、如果程序中不再持有线程池的引用,并且线程池中没有线程时,线程池将会自动关闭,如果希望确保即使忘记调用 shutdown()方法,也可以回收未引用的线程池,那么必须通过设置适当的keep-alive times并设置allowCoreThreadTimeOut(true),或设置核心线程数量为0;
3、ExecutorService是ThreadPoolExecutor的顶层接口,使用线程池中的线程执行每个提交的任务,我们可以使用Executors的工厂方法来创建ExecutorService。