线程池
一、线程池的优势
方式:
线程池做的工作主要是控制线程运行的数量,处理过程中将任务放入队列,然后在线程创建后启动这些任务,如果线程数量超过了最大数量,超出数量的线程排队等候,等其他线程执行完毕后,再从队列中取出任务来执行。
特点:
- 线程复用
- 控制最大并发数
- 管理线程
优势:
- 降低资源消耗。通过重复使用已创建的线程降低线程创建和销毁所带来的消耗
- 提高响应速度。当任务达到时,可以不需要等到线程创建就能立即执行
- 提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅消耗资源,并且还会降低系统的稳定性,使用线程池可以进行统一分配,调优,监控
二、创建线程池的方式
-
Executors.newFixedThreadPool(int):创建一个固定长度线程的线程池
执行长期任务,性能好很多
-
Executors.newSingleThreadExecutor():创建一个单线程的线程池
一个任务一个任务执行的场景
-
Executors.newCachedThreadPool():创建一个可缓存的线程池,可根据处理需要自动创建N个线程
执行很多短期异步的小程序或者负载较轻的服务器
-
Executors.newScheduledThreadPool():创建一个可调度的线程池(了解)
-
Executors.newWorkStealingPool():使用目前机器上可用的处理器作为它的并行级别(Java8新增)(了解)
package com.ctgu.juc;
import java.util.concurrent.*;
/**
* 创建线程方式,通过线程池创建
*/
public class MyThreadPoolDemo {
public static void main(String[] args) throws InterruptedException {
//通过Executors线程池工具类创建一个线程池:一个池子一个线程
ExecutorService threadPool1 = Executors.newSingleThreadExecutor();
threadPoolTest(threadPool1);
//通过Executors线程池工具类创建一个线程池:一个池子固定有5个线程
ExecutorService threadPool2 = Executors.newFixedThreadPool(5);
threadPoolTest(threadPool2);
//通过Executors线程池工具类创建一个线程池:一个池子N个线程
ExecutorService threadPool3 = Executors.newCachedThreadPool();
threadPoolTest(threadPool3);
}
private static void threadPoolTest(ExecutorService threadPool) throws InterruptedException {
//模拟10个用户办理业务,一个用户就是一个线程
try{
for (int i = 0; i < 10; i++) {
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName()+"\t 办理业务");
});
}
}catch (Exception e){
e.printStackTrace();
}finally {
threadPool.shutdown();
}
}
}
三、线程池的参数
三个创建线程池方法的源代码:
都是用ThreadPoolExecutor()传入参数创建线程池
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
threadFactory));
}
三个线程池创建方法最终用该类构造方法:
ThreadPoolExecutor.java
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
七大参数
- corePoolSize:线程池中的常驻核心线程数
- maximumPoolSize:线程池中能够容纳同时执行的最大线程数,此值必须大于1
- keepAliveTime:多余的空闲线程存活时间
- unit:keepAliveTime的单位
- workQueue:任务队列,被提交但尚未被执行的任务
- threadFactory:表示生成线程池中工作线程的线程工厂,用于创建线程一般默认的即可
- handler:拒绝策略,表示当队列满了并且工作线程大于等于线程池的最大线程数时,如何拒绝请求执行的Runnable的策略
四、线程池的工作原理
- 在创建了线程池以后,等待提交过来的任务请求
- 当调用execute()方法添加一个请求任务的时候,线程池做一下判断:
- 如果正在运行的线程数量小于corePoolSize,那么马上创建线程执行这个任务
- 如果正在运行的线程数量大于等于corePoolSize,那么将这个队列放入任务队列
- 如果这时候任务队列也满了,且正在运行的线程还小于maximumPoolSize,那么创建非核心线程立刻执行这个任务
- 如果这时候队列满了,且正在运行的线程大于等于maximumPoolSize,那么线程池会启动饱和拒绝策略来执行
- 当一个线程完成任务时,它会从队列中取出一个任务来执行
- 当一个线程空闲超过一定时间时,线程会做以下判断:
- 如果当前运行线程大于corePoolSize,那么这个线程就会被停掉
- 所以线程池的所有任务完成后它最终会收缩到corePoolSize这个大小
五、拒绝策略
AbortPolicy(默认):直接抛出RejectExecutionException异常阻止系统正常运行
CalerRunsPolicy:既不会抛弃任务,也不会抛异常,而是将某些任务回退到调用者,从而降低新任务的流量
DiscardOldestPolicy:丢弃队列中等待最久的任务,然后把当前任务加入队列尝试再次提交当前任务
DiscardPolicy:直接丢弃任务,不予处理也不抛异常。如果允许丢失,这是最好的一种方案
六、使用线程池
一般不直接用Executors工具类创建线程池,因为它创建的线程池用的阻塞队列都是LinkedBlockingQueue,该阻塞队列默认是有界但是大小为Integer.MAX_VALUE,会造成并发问题
所以一般都是用 ThreadPoolExecutor 类手动创建线程池
package com.ctgu.juc;
import java.util.concurrent.*;
/**
* 创建线程方式,通过线程池创建
*/
public class MyThreadPoolDemo {
public static void main(String[] args) throws InterruptedException {
ExecutorService executor = new ThreadPoolExecutor(
2, //corePoolSize
5, //maximumPoolSize
1, //keepAliveTime
TimeUnit.SECONDS, //unit
new LinkedBlockingQueue<Runnable>(3), //BlockingQueue
Executors.defaultThreadFactory(), //ThreadFactory
new ThreadPoolExecutor.DiscardPolicy());//RejectedExecutionHandler
//模拟10个用户办理业务,一个用户就是一个线程
try{
for (int i = 0; i < 10; i++) {
executor.execute(()->{
System.out.println(Thread.currentThread().getName()+"\t 办理业务");
});
}
}catch (Exception e){
e.printStackTrace();
}finally {
executor.shutdown();
}
}
}
七、合理配置线程池
CPU密集型:该任务需要大量的运算,而没有阻塞,CPU一直全速运转。
CPU密集型任务配置尽可能少的线程数量
一般公式:CPU核数+1个线程的线程池
IO密集型:该任务需要大量的IO,即大量的阻塞。
IO密集型中大多数线程都阻塞,所以需要尽可能多的线程数
参考公式:CPU核数 / (1 - 阻塞系数) 阻塞系数一般在0.8~0.9之间