线程池优势
1.降低系统资源消耗,通过重用已存在的线程,降低线程创建和销毁造成的消耗;
2.提高系统响应速度,当有任务到达时,通过复用已存在的线程,无需等待新线程的创建便能立即执行;
3.方便线程并发数的管控。因为线程若是无限制的创建,可能会导致内存占用过多而产生OOM,并且会造成cpu过度切换(cpu切换线程是有时间成本的(需要保持当前执行线程的现场,并恢复要执行线程的现场))。
4.管理线程
线程池需要掌握的知识点
1.三大方法
package com.kuang.pool;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
// Executors 工具类、3大方法
public class Demo01 {
public static void main(String[] args) {
ExecutorService threadPool = Executors.newSingleThreadExecutor();// 单个线
程 //
ExecutorService threadPool = Executors.newFixedThreadPool(5); // 创建一
个固定的线程池的大小
// ExecutorService threadPool = Executors.newCachedThreadPool(); // 可伸缩
的,遇强则强,遇弱则弱
try {
for (int i = 0; i < 100; i++) {
// 使用了线程池之后,使用线程池来创建线程
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName()+" ok");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 线程池用完,程序结束,关闭线程池
threadPool.shutdown();
}
}
}
2.7大参数
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
1、corePoolSize(线程池基本大小):当向线程池提交一个任务时,若线程池已创建的线程数小于corePoolSize,即便此时存在空闲线程,也会通过创建一个新线程来执行该任务,直到已创建的线程数大于或等于corePoolSize时.
2、maximumPoolSize(线程池最大大小):线程池所允许的最大线程个数。
3、keepAliveTime(线程存活保持时间)当线程池中线程数大于核心线程数时,线程的空闲时间如果超过线程存活时间,那么这个线程就会被销毁,直到线程池中的线程数小于等于核心线程数。
4、TimeUnit unit 超时单位
5、BlockingQueue workQueue, // 阻塞队列
6、ThreadFactory threadFactory, // 线程工厂:创建线程的,一般不用动
7、RejectedExecutionHandler handle // 拒绝策略
整体流程
首先会先创建基本的核心线程数,然后此时如果有多余的任务进来,会在阻塞队列进行等候,如果超出了阻塞队列(也叫任务队列)数量,将会开启新的线程,用来处理新的任务,如果开辟的线程数已经达到了设定的最大线程数,且阻塞队列也满了的情况下,如果此时还有新的任务进来,将会进行最后一步处理,即拒绝策略,拒绝策略包含4种方法,具体如下。
package com.kuang.pool;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;
// Executors 工具类、3大方法
/**
* new ThreadPoolExecutor.AbortPolicy() // 银行满了,还有人进来,不处理这个人的,抛出异常
* new ThreadPoolExecutor.CallerRunsPolicy() // 哪来的去哪里!
* new ThreadPoolExecutor.DiscardPolicy() //队列满了,丢掉任务,不会抛出异常!
* new ThreadPoolExecutor.DiscardOldestPolicy() //队列满了,尝试去和最早的竞争,也不会抛出异常!
*/
public class Demo01 {
public static void main(String[] args) {
// 自定义线程池!工作 ThreadPoolExecutor
// 最大线程到底该如何定义
// 1、CPU 密集型,几核,就是几,可以保持CPu的效率最高!
// 2、IO 密集型 > 判断你程序中十分耗IO的线程,
// 程序 15个大型任务 io十分占用资源!
// 获取CPU的核数
System.out.println(Runtime.getRuntime().availableProcessors());
List list = new ArrayList();
ExecutorService threadPool = new ThreadPoolExecutor(
2,
Runtime.getRuntime().availableProcessors(),
3,
TimeUnit.SECONDS,
new LinkedBlockingDeque<>(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.DiscardOldestPolicy()); //队列满了,尝试去和最早的竞争,也不会抛出异常!
try {
// 最大承载:Deque + max
// 超过 RejectedExecutionException
for (int i = 1; i <= 9; i++) {
// 使用了线程池之后,使用线程池来创建线程
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName()+" ok");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 线程池用完,程序结束,关闭线程池
threadPool.shutdown();
}
}
}
小结以及注意点
线程池的最大线程数应该如何设置?
了解:IO密集型,CPU密集型:(调优)
1、CPU 密集型,看CPU是几核,就可以吧最大线程数设置成几,可以保持CPu的效率最高!因为CPU密集型任务使得CPU使用率很高,若开过多的线程数,会造成CPU过度切换。可以用下面的代码自动获取CPU核数
Runtime.getRuntime().availableProcessors()
2、IO 密集型 > 判断程序中十分耗IO的线程,比如有10个非常耗IO的线程,那么最大线程数就应该 > 10,比如设置成2倍,20,这样10个线程专门去处理那10个耗IO的任务,然后空余10个去处理其他任务。
3、混合型任务
可以将任务分成IO密集型和CPU密集型任务,然后分别用不同的线程池去处理。 只要分完之后两个任务的执行时间相差不大,那么就会比串行执行来的高效。
因为如果划分之后两个任务执行时间有数据级的差距,那么拆分没有意义。
因为先执行完的任务就要等后执行完的任务,最终的时间仍然取决于后执行完的任务,而且还要加上任务拆分与合并的开销,得不偿失。
线程池为什么需要使用(阻塞)队列?
1、因为线程若是无限制的创建,可能会导致内存占用过多而产生OOM,并且会造成cpu过度切换。
2、 阻塞队列可以保证任务队列中没有任务时阻塞获取任务的线程,使得线程进入wait状态,释放cpu资源。当队列中有任务时才唤醒对应线程从队列中取出消息进行执行。使得在线程不至于一直占用cpu资源。
参考:线程池详解_@Better@的博客-CSDN博客