建设申请网站首页,怎么写网站建设推广,一般的学校网站怎么做,小程序商城名字一#xff1a;什么是线程池
当我们运用多线程技术处理任务时#xff0c;需要不断通过new的方式创建线程,这样频繁创建和销毁线程#xff0c;会造成cpu消耗过多。那么有没有什么办法避免频繁创建线程呢#xff1f; 当然有#xff0c;和我们以前学习过多连接池技术类似什么是线程池
当我们运用多线程技术处理任务时需要不断通过new的方式创建线程,这样频繁创建和销毁线程会造成cpu消耗过多。那么有没有什么办法避免频繁创建线程呢 当然有和我们以前学习过多连接池技术类似线程池通过提前创建好线程保存在线程池中在任务要执行时取出任务结束时再放回去由此大大提高线程利用率避免频繁创建销毁带来的开销
二Java提供的线程池有哪些
那么我们怎么才能创建一个线程池呢可以通过Executors的以下方法创建
newFixedThreadPool 固定线程池数量
newSingleThreadExecutor 只有一个线程的线程池
newCachedThreadPool 可以缓存的线程池
newScheduledThreadPool 按周期执行的线程池例如
ExecutorService executorService Executors.newFixedThreadPool(3);//创建一个拥有三个线程的线程池这些方法可以创建线程池但是实际工作中并不推荐使用这种方式因为这里阻塞队列使用的是LinkedBlockingQueue是无界的如果不断有任务添加进去占用内存越来越多可能导致OOM
所以更多时候可以通过手动创建线程池 那么如何手动创建线程池呢可以先点开上面提到的几个方法会发现这些方法本质上都是最后构造一个ThreadPoolExecutor实例 如下是Executors的newFixedThreadPool方法
public class Executors {public static ExecutorService newFixedThreadPool(int nThreads) {return new ThreadPoolExecutor(nThreads, nThreads,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueueRunnable());}所以手动创建线程池只需要创建ThreadPoolExecutor就可以了在创建之前我们先要弄懂构造方法中的参数含义才能创建合适的线程池
三线程池参数
从以上源代码中可以看到构造ThreadPoolExecutor需要一些参数那么这些参数分别是什么意思呢先看一下ThreadPoolExecutor的构造方法
public ThreadPoolExecutor(int corePoolSize, //控制核心线程数int maximumPoolSize,//控制最大线程数(核心线程救急线程)long keepAliveTime,//生存时间:针对救急线程#这里是一个数字TimeUnit unit,//时间单位#这里可以是秒毫秒等BlockingQueueRunnable workQueue,//阻塞队列ThreadFactory threadFactory,//可以为线程创建时起个好名字RejectedExecutionHandler handler)//拒绝策略那么什么是核心线程什么又是救急线程呢
核心线程 执行完任务后需要保留在线程池中的 救急线程 线程执行任务后不需要保留在线程池中的线程 阻塞队列 对任务做缓冲作用例如三个核心线程都在执行任务这时候来了第四个任务怎么办就将新任务放入workQueue队列中等核心线程执 行完任务空闲了就会从队列中获取任务 救急线程如果核心线程已满队列已满这时候又来任务怎么办就由救急线程来执行 拒绝策略核心线程放满了任务队列也满了救急线程不能无限创建啊 这时候再来线程怎么办
四线程池状态
线程池状态 ThreadPoolExecutor 使用int的高三位表示线程池状态 低29位表示线程数量
RUNNING 111
SHUTDOWN 000 线程池调用SHUTDOWN 方法不会接受新任务但是会处理阻塞队列中的任务
STOP 001 不会接受新任务正在执行的任务也会停止阻塞队列任务抛弃
TIDYING 010 任务执行完毕
TERMINATED 011 终结状态这些信息存储在一个原子变量ctl中目的是将线程池状态与 线程池个数合二为一这样就可以通过一次CAS操作进行赋值
五execute方法
public void execute(Runnable command) {if (command null)throw new NullPointerException();int c ctl.get(); //拿到32位intif (workerCountOf(c) corePoolSize) { //workerCountOf(c)获取工作线程数 corePoolSize 核心线程数if (addWorker(command, true)) //addWorker(command, true)创建核心线程数return;c ctl.get();}if (isRunning(c) workQueue.offer(command)) { // 1 isRunning判断线程池是否是Running状态 // 2 workQueue.offer(command) 将线程添加到阻塞队列int recheck ctl.get(); // 3 成功再次Ctl.get ()拿到32位intif (! isRunning(recheck) remove(command))// 4 isRunning(recheck)再次判断是否是Running// 5 如果不是Runningremove(command)移除任务reject(command);else if (workerCountOf(recheck) 0) // 6 获取当前工作的线程个数如果是0addWorker(null, false); // 7 阻塞队列有任务但是没有工作线程添加一个任务为空} else if (!addWorker(command, false)) // 8 如果7的判断是running,创建非核心线程处理任务reject(command); // 9 如果上一步创建失败 拒绝策略 reject(command);
}其中拒绝策略在第三节讲参数的时候提到那么具体有哪些拒绝策略呢 下图是拒绝策略的实现
AbortPolicy线程池默认的拒绝策略丢弃任务并抛出拒绝执行 RejectedExecutionException 异常信息。 必须处理好抛出的异常否则会打断当前的执行流程影响后续的任务执行。
CallerRunsPolicy 当触发拒绝策略并且线程池没有关闭时则使用父线程直接运行任务这会阻塞父进程继续往线程池中添加新的任务。个人认为仅仅适用于比较特殊的场景
DiscardPolicy直接丢弃不抛出任何一场适用于比较特殊的场景
DiscardOldestPolicy 当触发拒绝策略只要线程池没有关闭的话丢弃阻塞队列 workQueue 中最老的一个任务并将新任务加入
六线程池参数如何设置
通过以上我们了解了线程池各个参数的含义但是当我们自己创建线程池时应该如何选择合适的参数呢
这里需要重点考虑的就是核心线程数 如何设置这里主要难点在于任务类型无法控制例如任务有CPU密集型、IO密集型
CPU密集型系统硬盘、内存性能相对CPU要好很多此时系统运作 大部分状况是CPU Loading 100%,CPU读写IO(内存/硬盘)在短时间内可以完成而CPU还有许多运算要处理CPU Loading很高
IO密集型CPU相对系统硬盘、内存性能要好很多此时系统运作大部分状况是CPU在等IO内存/硬盘)读写此时CPU Loading 不高
IO密集型通常设置 2n1,n是CPU核心数
CPU密集型通常设置为 n1实际中IO密集型较多,但是按照2n的公式在实际中可能不理想如果增大线程数会显著提高消息的处理能力 怎么判断需要增加更多线程呢 可以使用jstack命令查看进程的线程栈如果线程池中线程都处于等待状态说明线程够用 如果大部分线程处于运行状态可以适当调高线程数 可以套用这个公式
线程数CPU核心数/1-阻塞系数通常0.8