深入理解线程池以及3大方法
一、线程池的优势
1、降低资源消耗
2、提高响应的速度
3、线程放到一个池子中,便于管理
总的来说就是可以实现线程复用、可以控制最大并发数、管理线程
二、线程池三大方法
需要用到Executor工具类,这个工具类有三个创建线程池的方法
1、创建单个线程 Executors.newSingleThreadExecutor()
测试代码:
public class Test01 {
public static void main(String[] args) {
ExecutorService threadPool = Executors.newSingleThreadExecutor();//创建单个线程池
try {
for (int i = 1; i <= 10; i++) {
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName() + "开启了!");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//用完线程池,记得关闭线程池
threadPool.shutdown();
}
}
}
控制台打印结果:
不难发现,自始至终都是一个线程来执行方法
2、创建指定的线程池中线程数量,最高支持指定个线程并发 Executors.newFixedThreadExecutor()
代码实现:
public class Test01 {
public static void main(String[] args) {
ExecutorService threadPool = Executors.newFixedThreadPool(6);
try {
for (int i = 1; i <= 10; i++) {
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName() + "开启了!");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//用完线程池,记得关闭线程池
threadPool.shutdown();
}
}
}
控制台:
最多有6个线程并发执行循环中的方法
3、创建一个可以伸缩大小的线程池 Executors.newCachedFixedThreadExecutor()
代码实现:
public class Test01 {
public static void main(String[] args) {
ExecutorService threadPool = Executors.newCachedThreadPool();//创建一个可伸缩大小的线程池
try {
for (int i = 1; i <= 10; i++) {
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName() + "开启了!");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//用完线程池,记得关闭线程池
threadPool.shutdown();
}
}
}
控制台:
循环中有10次循环,那么就会给我们创建10个线程并发执行
线程池7大参数以及4种拒绝策略
一、首先我们了解到线程池创建线程的方法是通过Executors工具类,有3大方法,分析源码
1、创建单个线程
public static ExecutorService newSingleThreadExecutor() {
return new Executors.FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue()));
}
2、创建指定线程的线程池
public static ExecutorService newFixedThreadPool(int var0) {
return new ThreadPoolExecutor(var0, var0, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue());
}
3、创建可伸缩的线程池
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, 2147483647, 60L, TimeUnit.SECONDS, new SynchronousQueue());
}
分析得知开启线程的本质上就是调用了ThreadPoolExecutor方法
二、分析ThreadPoolExecutor方法(含7大参数的介绍)
public ThreadPoolExecutor(
int var1, //核心线程池大小(一直会开启)
int var2, //最大核心线程池大小(当阻塞队列中等待的任务满了,会开启余下的线程,该参数表示能开启的最多的线程)
long var3, //超时了没有人调用就会释放(当开启了超过核心线程的数量的线程,如果过了超时时间没调用就会释放)
TimeUnit var5, //超时单位
BlockingQueue<Runnable> var6, //阻塞队列
ThreadFactory var7, //线程工厂,创建线程的,一般不用动
RejectedExecutionHandler var8 //拒绝策略(当线程都被占用,阻塞队列也满了,那么就会触发拒绝策略,任务要么走,要么等待)) {
this.ctl = new AtomicInteger(ctlOf(-536870912, 0));
this.mainLock = new ReentrantLock();
this.workers = new HashSet();
this.termination = this.mainLock.newCondition();
if (var1 >= 0 && var2 > 0 && var2 >= var1 && var3 >= 0L) {
if (var6 != null && var7 != null && var8 != null) {
this.acc = System.getSecurityManager() == null ? null : AccessController.getContext();
this.corePoolSize = var1;
this.maximumPoolSize = var2;
this.workQueue = var6;
this.keepAliveTime = var5.toNanos(var3);
this.threadFactory = var7;
this.handler = var8;
} else {
throw new NullPointerException();
}
} else {
throw new IllegalArgumentException();
}
}
三、4种拒绝策略
1、默认的是AbortPolicy策略
超过了最大线程数量+阻塞队列长度的负载量,就会抛出异常
2、CallerRunsPolicy策略:哪里来的去哪里,当线程池触发拒绝策略,如果该任务是main线程中的,那么就由main线程来执行
我设置了超过线程池负载量的任务,超出的任务,由main线程来执行
3、DiscardOldestPolicy策略:当达到最大的负载量,那么超出的任务会和第一个执行的任务去竞争,如果第一个任务快执行完了,那么就会去执行超出来的部分,反之将其抛掉不执行,不会抛出异常
这种情况一般是线程数较大,并且任务量较大的情况下才可能出现和最早的任务竞争的现象,其他情况下一般都是将他抛掉不执行
4、DiscardPolicy策略:当达到了线程池的负载量,不会像AbortPolicy策略那样抛出异常,而是将无法执行的任务丢掉不执行
我设置了最大开启线程数量是5,阻塞队列长度为3,而设置任务数为9,当前状态是超出负载1个任务量
控制台执行结果:依旧是只开启了5个线程,多余不能执行的任务就将其丢掉了
自定义线程池
自定义线程池的前提是了解了ThreadPoolExecutor的底层原理和传递的参数,那么自定义线程池就是根据需求来改变传递的参数即可
自定义线程池的代码
public class Test01 {
public static void main(String[] args) throws InterruptedException {
//自定义线程池,工作中只会使用ThreadPoolExecutor,用Executors工具类创建线程池不安全
ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(
2, //核心线程池(默认开启的)
5, //最大线程数
3, //超时时间
TimeUnit.SECONDS, //超时单位
new LinkedBlockingDeque<>(3), //阻塞队列(等待区)
Executors.defaultThreadFactory(), //默认的线程工厂,不需要改动
new ThreadPoolExecutor.AbortPolicy() //默认拒绝策略
);
//业务代码
try {
//计算线程池能够负载的最大的任务数量:最大线程数+阻塞队列(等待区),如果超出了最大负荷量,就会触发拒绝策略,抛出异常
for (int i = 1; i <= 7; i++) {
poolExecutor.execute(()->{
System.out.println(Thread.currentThread().getName() + "开启了!");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//用完线程池,记得关闭线程池
poolExecutor.shutdown();
}
}
}
CPU密集型和IO密集型(线程调优)
在高并发的状态下,可以存在两种高效率解决实现多线程的方式,换句话说,自定义线程池的时候,线程池的最大线程数有两种设置方式
一、CPU密集型
如果本地CPU核数较高,可以使用CPU密集型(几核CPU就可以并发执行几条线程),可以保证CPU效率最高!
一般我们不会在代码中将CPU核数写死,因为不同的电脑的CPU不同,实现方式如下
//自定义线程池,工作中只会使用ThreadPoolExecutor,用Executors工具类创建线程池不安全
ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(
2, //核心线程池(默认开启的)
Runtime.getRuntime().availableProcessors(), //最大线程数(CPU密集型)
3, //超时时间
TimeUnit.SECONDS, //超时单位
new LinkedBlockingDeque<>(3), //阻塞队
// 列(等待区)
Executors.defaultThreadFactory(), //默认的线程工厂,不需要改动
new ThreadPoolExecutor.DiscardOldestPolicy() //
);
拓展一下查看本地CPU核数的方法:
二、IO密集型
首先我们需要清楚,IO是非常占用资源的
IO密集型就是判断程序中十分消耗IO的线程,假设我们程序中有15个比较耗IO的线程,我们可以线程为他的一倍,设置30个线程,那么剩下的一半的线程就可以继续执行其他的线程,不会造成系统线程的阻塞等待