文章目录
一,线程池
java中的线程池是运用场景最多的并发框架,几乎所有需要异步或并发执行任务的程序都可以使用线程池
好处:
-
降低资源消耗,通过重复利用已经创建的线程来降低线程创建和销毁造成的消耗
-
提高响应速度,当任务到达时,可以省去线程创建的时间
-
提高线程的可管理性
二,线程池工作及框架
工作流程图:
Executor框架
1.两级调度模型
java程序通常会把应用分为若干个任务,然后使用调度器Executor将任务映射成固定数量的线程,在底层,操作系统内核将这些线程映射到各个硬件处理器上
2.框架结构
框架结构
框架成员
框架工作流程
实例:
@Slf4j
public class ExecutorTest {
/**
* callable测试内部类
*/
static class CallableTest implements Callable<Integer>{
@Override
public Integer call() throws Exception {
return 1;
}
}
/**
* runnable测试内部类
*/
static class RunnableTest implements Runnable{
@Override
public void run() {
log.info("runnable ........");
}
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
//构造线程池
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5, 10,
100, MILLISECONDS, new ArrayBlockingQueue<Runnable>(5));
//提交并执行runnable任务
threadPoolExecutor.execute(new RunnableTest());
//提交并执行callable任务,获取返回的Future对象
Future<Integer> future = threadPoolExecutor.submit(new CallableTest());
//通过该对象获取返回值
Integer i = future.get();
log.info("i ---------->"+i);
}
}
三,线程池实现
ThreadPoolExecutor
Executor框架最核心的类就是ThreadPoolExecutor,他是线程池的实现类
构成组件
-
corePool:核心线程池大小
-
maximumPool:最大线程池大小
-
BlockingQueue:用来暂时保存的任务的任务队列
-
RejectedExecutionHandler:当ThreadPoolExecutor关闭或者ThreadPoolExecutor饱和时对任务的处理策略
手动创建线程池
/**
* @Param: corePoolSize : 核心线程数
* @Param:maximumPoolSize:最大线程数
* @Param:keepAliveTime:空闲线程等待新任务的最长时间
* @Param:unit:计数单位
* @Param:workQueue:任务队列
*/
//ThreadPoolExecutor tpe = new ThreadPoolExecutor(int corePoolSize,int maximumPoolSize
// ,long keepAliveTime,TimeUnit unit, BlockingQueue<Runnable> workQueue);
/**
* 手动创建线程池
* @return
*/
public ExecutorService executorService(){
return new ThreadPoolExecutor(5, 10,
100, MILLISECONDS, new ArrayBlockingQueue<Runnable>(5));
}
FixedThreadPool
FixedThreadPool是固定线程数的线程池
/**
* 创建 FixedThreadPool
* @return
*/
public ExecutorService fixedThreadPool(){
return Executors.newFixedThreadPool(8);
}
源码:
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
分析:
-
使用无界的LinkedBlockingQueue来作为任务队列,容量为Integer.MAX_VALUE带来的影响
- 当线程池中的线程数达到corePoolSize时,任务将进入任务队列等待,此时,使用LinkedBlockingQueue不会拒绝任务,任务会源源不断的进入任务队列中
-
keepAliveTime是0,意味着多余的空闲线程会被立即停止
-
FixedThreadPool是线程数量固定的线程池,适用于为了满足资源管理的需求,而需要适当限制当前线程数量的情景,适用于负载比较重的服务器
SingleThreadExecutor
singleThreadExecutor是使用单个线程的Executor
/**
* 创建SingleThreadExecutor
* @return
*/
public ExecutorService singleThreadExecutor(){
return Executors.newSingleThreadExecutor();
}
源码
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
源码分析
-
corePoolSize = 1线程在执行完一个任务后,会在一个循环总从无界的任务队列中反复取一个任务执行
-
使用的还是无界队列,影响如上
-
SingleThreadExecutor是只有一个线程的线程池,常用于需要让线程顺序执行,并且在任意时间,只能有一个任务被执行,而不能有多个线程同时执行的场景。
CachedThreadPool
CachedThreadPool是一个会根据需要创建新线程的线程池
/**
* 创建CachedThreadPool
* @return
*/
public ExecutorService cachedThreadPool(){
return Executors.newCachedThreadPool();
}
源码:
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
源码分析:
-
SynchronizedQueue是不存储元素的阻塞队列,实现“一对一的交付”,也就是说,每次向队列中put一个任务必须等有线程来take这个任务,否则就会一直阻塞该任务,如果一个线程要take一个任务就要一直阻塞知道有任务被put进阻塞队列。
-
因为maximumPoolSize=Integer.MAX_VALUE,因此可以不断的创建新线程,这样可能会CPU和内存资源耗尽
-
CachedThreadPool适用于执行很多短期异步任务的小程序,或者是负载较轻的服务器。
四,线程池关闭及监控
1.线程池关闭
可以通过调用线程池的shutdown或shutdownNow方法来关闭线程池。
原理都是遍历线程池中的工作线程,然后逐个调用线程的interrupt方法来中断线程,所以无法响应中断的任务可能永远无法终止。
只要调用了这两个关闭方法中的任意一个,isShutdown方法就会返回true。
当所有的任务都已关闭后,才表示线程池关闭成功,这时调用isTerminaed方法会返回true。
至于应该调用哪一种方法来关闭线程池,应该由提交到线程池的任务特性决定,通常调用shutdown方法来关闭线程池,如果任务不一定要执行完,则可以调用shutdownNow方法。
/**
* 线程池关闭
* @param executorService
*/
public void shutDownThreadPool(ExecutorService executorService){
//遍历线程池中的工作线程,然后逐个调用线程的interrupt方法来中断线程
//按过去执行已提交任务的顺序发起一个有序的关闭
executorService.shutdown();
//shutdownNow会强制停止所有正在执行的任务
//executorService.shutdownNow();
}
}
2.监控线程池
如果在系统中大量使用线程池,则有必要对线程池进行监控,方便在出现问题时,可以根据线程池的使用状况快速定位问题。
可以通过线程池提供的参数进行监控,在监控线程池的时候可以使用以下属性
-
taskCount:线程池需要执行的任务数量,是个近似值。
-
completedTaskCount:线程池在运行过程中已完成的任务数量,小于或等于taskCount,是个近似值。
-
largestPoolSize:线程池里曾经创建过的最大线程数量。通过这个数据可以知道线程池
是否曾经满过。如该数值等于线程池的最大大小,则表示线程池曾经满过。 -
poolSize:线程池当前的线程数总量,包括活动的线程与闲置的线程。
-
activeCount:获取活动的线程数