线程是不是越多越好?
- 线程在java中是一个对象,更是操作系统的资源,线程创建,销毁需要时间。如果创建时间+销毁时间》执行任务时间就很不合算。
- java对象占用堆内存,操作系统线程占用系统内存,根据jvm规范,一个线程默认最大栈大小1M,这个栈空间时需要从系统内存中分配的。线程过多,会消耗很多的内存。
- 操作系统需要频繁切换线程上下文(大家都想被运行),影响性能。
线程池的推出,就是为了方便的控制线程数量
线程池原理
- 线程池管理器:用于创建并管理线程池,包括创建线程池,销毁线程池,添加新任务;
- 工作线程:线程池中线程,在没有任务时处于等待状态,可以循环的执行任务
- 任务接口:每个任务必须实现的接口,以供工作线程调度任务的执行,它主要规定了任务的入口,任务执行完之后的收尾工作,任务的执行状态等
-
任务队列:用于存放没有处理的任务,提供一种缓冲机制
1.png
线程池API-接口定义和实现类

-
ExecutorService
3.png
-
ScheduledExecutorService
4.png
示例代码:
private void threadPoolExecutorTest1() throws Exception {
//线程池信息:核心线程数量5,最大数据10,无界队列,超出核心线程数量的线程存活时间:5秒,指定拒绝策略
/**
* 无界队列:对任务队列不限制,可以无限添加
* 线程池当前核心线程数据5,如果第六个线程持续五秒无任务则第六个线程关闭
*/
ThreadPoolExecutor threadPoolExecutor=new ThreadPoolExecutor(5,10,5, TimeUnit.SECONDS,new LinkedBlockingQueue<>());
testCommon(threadPoolExecutor);
}
public void testCommon(ThreadPoolExecutor threadPoolExecutor) throws Exception{
for (int i = 0; i < 15; i++) {
int n=i;
threadPoolExecutor.submit(()->{
try {
System.out.println("开始执行:"+n);
Thread.sleep(3000L);
System.out.println("执行结束:"+n);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
System.out.println("任务提交成功:"+i);
}
Thread.sleep(500L);
System.out.println("当前线程池线程数量为:"+threadPoolExecutor.getPoolSize());
System.out.println("当前线程池等待的数量为:"+threadPoolExecutor.getQueue().size());
//等待十五秒,理论上,超出核心线程数量的线程都会被取消
Thread.sleep(1500L);
System.out.println("当前线程池线程数量为:"+threadPoolExecutor.getPoolSize());
System.out.println("当前线程池等待的数量为:"+threadPoolExecutor.getQueue().size());
}
在执行日志可以看出,理论上任务数多于核心线程,应该启动新线程直到最大线程数,但是实际上并没有。原因如下:

所以,核心就在于,队列数,上面的代码是无界队列,所以永远队列不会满也就不会启动超过核心线程数的线程
示例代码二
private void threadPoolExecutorTest1() throws Exception {
//线程池信息:核心线程数量5,最大数据10,等待队列最大是3,也就是说最大容纳13个任务,超出核心线程数量的线程存活时间:5秒,指定拒绝策略
//默认的策略是抛出RejectedExecutionException异常,java.util.concurrent.ThreadPoolExecutor.AbortPolicy
//此处已经做了RejectedExecutionHandler处理,则会打印内部的输出
ThreadPoolExecutor threadPoolExecutor=new ThreadPoolExecutor(5, 10, 5, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(3), new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
System.out.println("有任务被拒绝执行了");
}
});
testCommon(threadPoolExecutor);
}
- Executors工具类
可以自己实例化线程池,例如上面的代码,也可以用Executors创建的线程池的工厂类,常用方法如下:
- newFixedThreadPool(int nThreads): 创建一个固定大小,任务队列容量无界的线程池。核心线程数=最大线程数。
- new CachedThreadPool():创建的是一个大小无界的缓冲线程池。它的任务队列是一个同步队列。所谓同步队列就是有新任务添加发现队列满了或者其他原因导致任务无法放入队列,则加开新的线程,然后处理。任务加入到池中,如果线程池中有空闲线程,则用空闲线程执行,如无则创建新线程执行。池中的线程空闲超过60秒,将被销毁释放。线程数随任务的多少变化。适用于执行耗时较小的异步任务。池的核心线程数=0,最大线程数=Integer.MAX_VALUE
- newSingleThreadExecutor() 只有一个线程来执行无界任务队列的单一线程池。该线程池确保任务按照加入的顺序一个个依次执行。当唯一的线程因任务异常中止时,将创建一个新的线程来继续执行后续的任务。与newFixedThreadPool(1)的区别在于,单一线程池的池大小在newSingleThreadExecutor方法中硬编码不能再改变了。
- newScheduledThreadPool(int corePoolSize)能定时执行任务的线程池。该池的核心线程数由参数指定,最大线程数=Interget.MAX_VALUE
单次定时任务代码示例
//定时执行线程池信息:3秒后执行,一次性任务,到点就执行
//核心线程数量5,最大数量Integer.MAX_VALUE. 其本质就是延迟队列:DelayedWorkQueue,超出核心线程数量线程存活0秒
private void threadPoolExecutorTest2() throws Exception{
ScheduledThreadPoolExecutor threadPoolExecutor=new ScheduledThreadPoolExecutor(5);
threadPoolExecutor.schedule(()->{
System.out.println("任务呗执行:"+System.currentTimeMillis());
},3000,TimeUnit.MILLISECONDS);
}
循环执行任务代码示例
private void threadPoolExecutorTest2() throws Exception{
ScheduledThreadPoolExecutor threadPoolExecutor=new ScheduledThreadPoolExecutor(5);
//这个1秒,如果上次执行时间超过一秒,则该次任务立刻开始
threadPoolExecutor.scheduleAtFixedRate(()->{
try {
Thread.sleep(3000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("任务1被执行:"+System.currentTimeMillis());
//2秒后第一次执行,间隔1秒
},2000,1000,TimeUnit.MILLISECONDS);
//这个1秒值得上上一次任务结束之后一秒
threadPoolExecutor.scheduleWithFixedDelay(()->{
try {
Thread.sleep(3000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("任务2被执行:"+System.currentTimeMillis());
},2000,1000,TimeUnit.MILLISECONDS);
}
shutdown优雅关闭代码示例:等待正在运行完成后关闭,队列中也会执行,但是不接收新任务
private void threadPoolExecutorTest1() throws Exception {
ThreadPoolExecutor threadPoolExecutor=new ThreadPoolExecutor(5, 10, 5, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(3), new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
System.out.println("有任务被拒绝执行了");
}
});
testCommon(threadPoolExecutor);
}
public void testCommon(ThreadPoolExecutor threadPoolExecutor) throws Exception{
for (int i = 0; i < 15; i++) {
int n=i;
threadPoolExecutor.submit(()->{
try {
System.out.println("开始执行:"+n);
Thread.sleep(3000L);
System.out.println("执行结束:"+n);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
System.out.println("任务提交成功:"+i);
}
Thread.sleep(1000L);
threadPoolExecutor.shutdown();
threadPoolExecutor.submit(()->{
System.out.println("追加一个任务");
});
}
//结果分析:
//1. 10个任务被执行,3个任务进入等待队列,2个任务被拒绝执行
//2. 调用shutdown后,不接收新的任务,等待13个任务执行结束,所以“追加一个新任务”不会打印
//3. 追加的任务在线程池关闭后,无法再提交,会被拒绝执行,打印"有任务被拒绝执行了"
强制关闭代码示例:强制终止
private void threadPoolExecutorTest1() throws Exception {
ThreadPoolExecutor threadPoolExecutor=new ThreadPoolExecutor(5, 10, 5, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(3), new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
System.out.println("有任务被拒绝执行了");
}
});
testCommon(threadPoolExecutor);
}
public void testCommon(ThreadPoolExecutor threadPoolExecutor) throws Exception{
for (int i = 0; i < 15; i++) {
int n=i;
threadPoolExecutor.submit(()->{
try {
System.out.println("开始执行:"+n);
Thread.sleep(3000L);
System.out.println("执行结束:"+n);
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println("异常");
}
});
System.out.println("任务提交成功:"+i);
}
Thread.sleep(1000L);
List<Runnable> runnables = threadPoolExecutor.shutdownNow();
threadPoolExecutor.submit(()->{
System.out.println("追加一个任务");
});
System.out.println("未结束的任务有"+runnables.size());
}
/**
结果分析
1. 1个任务被执行,3个任务进入等待队列,2个任务被拒绝执行
2. 调用shutdownNow后,队列中的3个线程不再执行,10个正在执行的线程被强制终止(打印异常)
*/
- 如果确定合适数量的线程?
计算型任务:cpu数量的1-2倍
IO型任务:相对比计算型任务,需多一些线程,要根据具体IO阻塞时长进行考量决定。
如tomcat中默认的最大线程数为:200.
也可以考虑根据需要再一个最大数量和最小数量之间自定变化
网友评论