赞
踩
为什么需要用到线程池?
众所周知线程是操作系统一种宝贵的资源,创建和销毁都很耗费资源,对此,java中针对这种情况实现出线程池思想,维护一定数量的线程,用到时候就不需要去创建,不使用时候,就销毁一定量的,保留一部分线程数量。
Java线程池的工作流程为:线程池刚被创建时,只是向系统申请一个用于执行线程队列和管理线程池的线程资源。在调用execute()添加一个任务时,线程池会按照以下流程执行任务。
如果正在运行的线程数量少于corePoolSize(用户定义的核心线程数),线程池就会立刻创建线程并执行该线程任务。
如果正在运行的线程数量大于等于corePoolSize,该任务就将被放入阻塞队列中。
在阻塞队列已满且正在运行的线程数量少于maximumPoolSize时,线程池会创建非核心线程立刻执行该线程任务。
在阻塞队列已满且正在运行的线程数量大于等于maximumPoolSize时,线程池将拒绝执行该线程任务并抛出RejectExecutionException异常。
在线程任务执行完毕后,该任务将被从线程池队列中移除,线程池将从队列中取下一个线程任务继续执行。
在线程处于空闲状态的时间超过keepAliveTime时间时,正在运行 的线程数量超过corePoolSize,该线程将会被认定为空闲线程并停止。因 此在线程池中所有线程任务都执行完毕后,线程池会收缩到corePoolSize大小。
JAVA中提供了5种常用的线程池
newCachedThreadPool
用于创建一个缓存线程池。之所以叫缓存线程 池,是因为它在创建新线程时如果有可重用的线程,则重用它们,否则重 新创建一个新的线程并将其添加到线程池中。对于执行时间很短的任务而言,newCachedThreadPool线程池能很大程度地重用线程进而提高系统的性能。
执行时间很短的任务
执行时间很短的任务,无容量队列,提交一个任务创建一个线程。
使用 SynchronousQueue
队列,特点是没有容量,没有线程是放不进去的。
没有线程任务运行时,newCachedThreadPool将不会占用系统的线程资源。
newFixedThreadPool
核心线程数 == 最大线程数,无需超时销毁。
阻塞队列是无界的,可以存放任意数量的任务。
适用于任务量已知,相对耗时的任务。
newScheduledThreadPool创建了一个可定时调度的线程池,可设置在给定的延迟时间后执行或者定期执行某个线程任务
构造
延迟执行任务
按固定频率执行,与任务本身执行时间无关。但 有个前提条件,任务执行时间必须小于间隔时间,例如间隔时间是5 s,每5s执行一次任务,任务的执行时间无关,即使任务执行大于5s,每次间隔执行,也会等待上次执行结束才会执行。
周期执行任务
WithFixedDelay:按固定间隔执行,与任务本身执行时间有 关。例如,任务本身执行时间是10s,间隔2s,则下一次开始执行的
时间就是12s。
newSingleThreadExecutor线程池会保证永远有且只有一个可用的线程,在该线程停止或发生异常时,newSingleThreadExecutor线程池会启动一个新的线程来代替该线程继续执行任务
相比于TimerTask 定时任务类,发生异常不会退出,不影响执行任务。
JDK提供的一个新的线程池newWorkStealingPool
该线程池以上线程池构造不同,采用ForkJoin
线程池,并发是使用ThreadPoolExecutor
构造
newWorkStealingPool创建持有足够线程的线程池来达到快速运算的目
的,在内部通过使用多个队列来减少各个线程调度产生的竞争。这里所说 的有足够的线程指JDK根据当前线程的运行需求向操作系统申请足够的线 程,以保障线程的快速执行,并很大程度地使用系统资源,提高并发计算
的效率,省去用户根据CPU资源估算并行度的过程。当然,如果开发者想自
己定义线程的并发数,则也可以将其作为参数传入。
//获取cpu核数 // System.out.println(Runtime.getRuntime().availableProcessors()); // 不指定线程大小 则是cpu默认核心数 ExecutorService executorService = Executors.newWorkStealingPool(); //可以提交 有返回 或者 无返回 Future<String> submit = executorService.submit(() -> { TimeUnit.SECONDS.sleep(1); return "run able"; }); Future<String> submit1 = executorService.submit(new Callable<String>() { @Override public String call() throws Exception { TimeUnit.SECONDS.sleep(1); return "call able "; } }); System.out.println(submit.get()); System.out.println(submit1.get()); System.out.println(" ------------------- "); //可以 提交一个集合 会等全部线程运行结束 //invokeAny 执行给定的任务,如果有任务成功完成,则返回成功完成的任务的结果(即,不引发异常)。在正常或异常返回时,未完成的任务将被取消。 // List<Callable<String>> list = new ArrayList<>(); // executorService.invokeAny(list);
一般情况下,构造线程使用ThreadPoolExecutor
来显示的指定线程池参数,包括核心线程数,最大线程数,时间,队列选择以及大小,线程工厂,拒绝策略。防止因为默认的参数任务数量大或者频繁创建线程带来系统问题。
线程池中提交任务方法
非阻塞方法
//打断
// 20 个线程
// 10 个空闲 会打断线程 并且标记
// 10个工作 等待工作执行完成
public void shutdown() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();
//设置SHUTDOWN 标志 打断空闲线程,设置执行线程中断标志,等待任务运行结束
advanceRunState(SHUTDOWN);
interruptIdleWorkers(); //打断空闲线程
onShutdown(); // hook for ScheduledThreadPoolExecutor
} finally {
mainLock.unlock();
}
tryTerminate(); //尝试在打断
}
非阻塞方法
// 20个线程
// 10个在队列里
// 1 、会立即将队列里任务打断返回(这些不会处理完)
// 2 、打断工作中10个线程,最后执行任务中的10个线程 等待执行完成(处理完)
// 3 、 20个线程退出
public List<Runnable> shutdownNow() {
List<Runnable> tasks;
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();
advanceRunState(STOP); //设置stop 标志 打断 执行和没执行的线程都打断
interruptWorkers(); //打断线程
tasks = drainQueue();
} finally {
mainLock.unlock();
}
tryTerminate();
return tasks; //返回队列中任务
}
如
List<Runnable> runnables = executorService.shutdownNow();
//返回任务
System.out.println(runnables);
System.out.println(runnables.size());
需要自定义线程池 线程工厂
ExecutorService executorService = new ThreadPoolExecutor(10,20,30, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(10),
//线程工厂 参数
r ->{
Thread t = new Thread(r);
t.setDaemon(true);
return t;
},new ThreadPoolExecutor.AbortPolicy());
自定义线程工厂,设置主线程是后台线程, 线程池等待时间 5s 同时线程组也是后台线程,整个线程池就会一起退出!
设置等待时间,最多等待多少时间,没有执行完,则销毁线程池。
executorService.awaitTermination(5,TimeUnit.SECONDS);
对于非返回参数任务而言,想知道每个线程是否执行完成,这个时候,配合CountDownLatch
使用。
当最大线程池和队列都满的时候,此时再来任务,这时候就需要做出抉择,是抛弃任务还是怎么办。线程池中提供了4中策略。
线程池默认就是AbortPolicy直接抛出异常,阻止线程正常运行
CallerRunsPolicy的拒绝策略为:只要线程池未关闭,该策略直接在调用者线程中,运行当前的被丢弃的任务。
(线程池没有关闭,队列满了,则使用运行线程池的线程执行它。 如果在main里,则就是mian
)
移除线程队列中最早的一个线程任务,并尝试提交当前任务。
DiscardPolicy的拒绝策略为:丢弃当前的线程任务而不做任何处理。如果系统允许在资源不足的情况下丢弃部分任务,则这将是保障系统安
全、稳定的一种很好的方案。
上面四种都实现了RejectedExecutionHandler接口,若无法 满足实际需要,则用户可以自己扩展RejectedExecutionHandler接口来实 现拒绝策略,并捕获异常来实现自定义拒绝策略。
线程池最核心的东西是线程数量选择,一般而言,线程数量选择取决去该任务是计算型
还是IO型
任务。
理论而言
计算型
线程数量 = CPU核心数
+ 1
IO型
线程数量 = 2 * CPU核心数
+ 1
IO型还有一种计算公式
线程数量 = 核心数 * 期望CPU利用率 * 总时间(CPU计算时间 + 等待时间) / CPU 计算时间
当然具体参数是需要根据实际任务和CPU处理能力压测动态调节线程大小的。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。