线程池概念
线程池是指在初始化一个多线程应用程序过程中创建一个线程集合,然后在需要执行新的任务时重用这些线程而不是新建一个线程(提高线程复用,减少性能开销)。线程池中线程的数量通常完全取决于可用内存数量和应用程序的需求。然而,增加可用线程数量是可能的。线程池中的每个线程都有被分配一个任务,一旦任务已经完成了,线程回到池子中然后等待下一次分配任务
使用线程池的优点:
- 降低资源的消耗。降低线程创建和销毁的资源消耗;
- 提高响应速度:线程的创建时间为T1,执行时间T2,销毁时间T3,免去T1和T3的时间
- 提高线程的可管理性。
自己实现一个线程池:
【要求】:
- 线程必须在池子已经创建好了,并且可以保持住,要有容器保存多个线程;
- 线程还要能够接受外部的任务,运行这个任务。容器保持这个来不及运行的任务
public class MyThreadPool2 {
// 线程池中默认线程的个数为5
private static int WORK_NUM = 5;
// 队列默认任务个数为100
private static int TASK_COUNT = 100;
// 工作线程组
private WorkThread[] workThreads;
// 任务队列,作为一个缓冲
private final BlockingQueue<Runnable> taskQueue;
private final int worker_num;//用户在构造这个池,希望的启动的线程数
// 创建具有默认线程个数的线程池
public MyThreadPool2() {
this(WORK_NUM,TASK_COUNT);
}
// 创建线程池,worker_num为线程池中工作线程的个数
public MyThreadPool2(int worker_num,int taskCount) {
if (worker_num<=0) worker_num = WORK_NUM;
if(taskCount<=0) taskCount = TASK_COUNT;
this.worker_num = worker_num;
taskQueue = new ArrayBlockingQueue<>(taskCount);
workThreads = new WorkThread[worker_num];
for(int i=0;i<worker_num;i++) {
workThreads[i] = new WorkThread();
workThreads[i].start();
}
Runtime.getRuntime().availableProcessors();
}
// 执行任务,其实只是把任务加入任务队列,什么时候执行有线程池管理器决定
public void execute(Runnable task) {
try {
taskQueue.put(task);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 销毁线程池,该方法保证在所有任务都完成的情况下才销毁所有线程,否则等待任务完成才销毁
public void destroy() {
// 工作线程停止工作,且置为null
System.out.println("ready close pool.....");
for(int i=0;i<worker_num;i++) {
workThreads[i].stopWorker();
workThreads[i] = null;//help gc
}
taskQueue.clear();// 清空任务队列
}
// 覆盖toString方法,返回线程池信息:工作线程个数和已完成任务个数
@Override
public String toString() {
return "WorkThread number:" + worker_num
+ " wait task number:" + taskQueue.size();
}
/**
* 内部类,工作线程
*/
private class WorkThread extends Thread{
@Override
public void run(){
Runnable r = null;
try {
while (!isInterrupted()) {
r = taskQueue.take();
if(r!=null) {
System.out.println(getId()+" ready exec :"+r);
r.run();
}
r = null;//help gc;
}
} catch (Exception e) {
// TODO: handle exception
}
}
public void stopWorker() {
interrupt();
}
}
}
线程池操作
创建一个线程池,ThreadPoolExecutor
,jdk所有线程池实现的父类
corePoolSize
:线程池中核心线程数,< corePoolSize ,就会创建新线程,= corePoolSize ,这个任务就会保存到BlockingQueue,如果调用prestartAllCoreThreads()方法就会一次性的启动corePoolSize 个数的线程。maximumPoolSize
:允许的最大线程数,BlockingQueue也满了,< maximumPoolSize时候就会再次创建新的线程keepAliveTime
:线程空闲下来后,存活的时间,这个参数只在> corePoolSize才有用TimeUnit unit
:存活时间的单位值BlockingQueue<Runnable> workQueue
:保存任务的阻塞队列ThreadFactory threadFactory
:创建线程的工厂,给新建的线程赋予名字RejectedExecutionHandler handler
:饱和策略- AbortPolicy :直接抛出异常,默认;
- CallerRunsPolicy:用调用者所在的线程来执行任务
- DiscardOldestPolicy:丢弃阻塞队列里最老的任务,队列里最靠前的任务
- DiscardPolicy :当前任务直接丢弃
实现自己的饱和策略,实现RejectedExecutionHandler接口即可
在生产环境中,99%的任务都不能丢弃,往往需要实现自己的饱和策略,使用日志记录或者对任务进行持久化操作
提交任务:
execute(Runnable command)
: 不需要返回Future<T> submit(Callable<T> task)
:需要返回
关闭线程池:
shutdownNow()
:设置线程池的状态,还会尝试停止正在运行或者暂停任务的线程shutdown()
:设置线程池的状态,只会中断所有没有执行任务的线程
工作机制
【1】:线程数小于corePoolSize
,创建新的线程
【2】:线程数等于corePoolSize
,这个任务就会保存到BlockingQueue
【3】:corePoolSize
满了,BlockingQueue
满了,线程数小于maximumPoolSize
,创建新线程执行新的任务
【4】:线程数等于maximumPoolSize
,新来的任务就会被拒绝
合理配置线程池
根据任务的性质来:计算密集型(CPU),IO密集型,混合型
计算密集型
:加密,大数分解,正则……., 线程数适当小一点,最大推荐:机器的Cpu核心数+1(为什么+1,防止缺页,机器的Cpu核心=Runtime.getRuntime().availableProcessors()?IO密集型
:读取文件,数据库连接,网络通讯, 线程数适当大一点,机器的Cpu核心数*2,混合型
:尽量拆分,IO密集型>>计算密集型,拆分意义不大,IO密集型~计算密集型
BlockingQueue队列的选择上,应该使用有界,无界队列可能会导致内存溢出,OOM
预定义线程池
FixedThreadPool
:创建固定线程数量的,适用于负载较重的服务器,使用了无界队列SingleThreadExecutor
:创建单个线程,需要顺序保证执行任务,不会有多个线程活动,使用了无界队列CachedThreadPool
:会根据需要来创建新线程的,执行很多短期异步任务的程序,使用了SynchronousQueueWorkStealingPool
(JDK7以后) :基于ForkJoinPool实现ScheduledThreadPoolExecutor
:需要定期执行周期任务,Timer不建议使用了。newSingleThreadScheduledExecutor
:只包含一个线程,只需要单个线程执行周期任务,保证顺序的执行各个任务newScheduledThreadPool
可以包含多个线程的,线程执行周期任务,适度控制后台线程数量的时候