什么是池化技术
池化技术 (Pool) 是一种很常见的编程技巧,在请求量大时能明显优化应用性能,降低系统频繁建连的资源开销。我们日常工作中常见的有数据库连接池、线程池、对象池等,它们的特点都是将 “昂贵的”、“费时的” 的资源维护在一个特定的 “池子” 中,规定其最小连接数、最大连接数、阻塞队列等配置,方便进行统一管理和复用,通常还会附带一些探活机制、强制回收、监控一类的配套功能。
线程池的三大方法:
- Executors.newSingleThreadExecutor()方法,该方法会在线程池中创建一个线程,所有放入该线程池中的任务都由该线程执行
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* @Author : chenjianbo
* @Data : 2022/1/26 17:11
*/
public class Threads {
public static void main(String[] args) {
//使用线程池创建单个线程
ExecutorService threadPool =Executors.newSingleThreadExecutor();
//通过循环 把任务放进线程池中 查看线程的使用情况
try{
for (int i=0;i<=10;i++){
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName());
});
}
}catch (Exception e){
e.printStackTrace();
}finally {
//使用try catch 确保线程池可以被关闭
threadPool.shutdown();
}
}
}
运行结果:因为是单例线程池,所以循环中的所有任务只会由这一个线程来执行
2.Executors.newFixedThreadPool(4)创建一个线程数量固定的线程池。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* @Author : chenjianbo
* @Data : 2022/1/26 17:11
*/
public class Threads {
public static void main(String[] args) {
//使用线程池创建单个线程
ExecutorService threadPool =Executors.newFixedThreadPool(4);
//通过循环 把任务放进线程池中 查看线程的使用情况
try{
for (int i=0;i<=10;i++){
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName());
});
}
}catch (Exception e){
e.printStackTrace();
}finally {
//使用try catch 确保线程池可以被关闭
threadPool.shutdown();
}
}
}
结果:由上面代码可知,线程池中有四个线程,所有放到线程池中的任务由这个四个线程中的某一个来执行
3.Executors.newCachedThreadPool()创建数量不定的线程,会根据任务的多少和执行程序电脑的CPU来自动改变线程的数量。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* @Author : chenjianbo
* @Data : 2022/1/26 17:11
*/
public class Threads {
public static void main(String[] args) {
//使用线程池创建单个线程
ExecutorService threadPool =Executors.newCachedThreadPool();
//通过循环 把任务放进线程池中 查看线程的使用情况
try{
for (int i=0;i<=10;i++){
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName());
});
}
}catch (Exception e){
e.printStackTrace();
}finally {
//使用try catch 确保线程池可以被关闭
threadPool.shutdown();
}
}
}
结果:当循环十次的时候 有十个任务执行,这时线程池创建十个线程来执行任务,当循环100次的时候,这时候程序并没有创建100个线程来执行,而根据任务量和电脑的cpu来确定线程数,所以只创建32个线程便能执行。
线程池中的七大参数:
通过上面的方法我们可以创建不同数量线程的线程池,但当我们继续深入这三个方法的底层我们会发现它们三个方法其实都是调用的同一个方法ThreadPoolExecutor(),只是通过输入不同的参数而应对不同的需求。而ThreadPoolExecutor()中的这七个参数就是创建不同线程池的关键。
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
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.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
七大参数讲解::当使用ThreadPoolExecutor()方法创建线程池,如果有任务放到线程池中则会根据corePoolSize的大小来创建线程并执行任务,当任务过多时所有的线程都在执行任务时再过来的任务则会放到阻塞队列( BlockingQueue workQueue)中。当阻塞队列满了以后线程池会根据maximumPoolSize创建更多的线程来执行任务。当根据maximumPoolSize创建的线程用完以后仍有多余的任务时,线程池会根据不同的拒绝策略(RejectedExecutionHandler handler)来拒绝多余的任务。当任务执行完corePoolSize创建的线程可以应对时,会根据keepAliveTime的时间和 TimeUnit unit的单位来确定一个时间,超过这个时间仍没有多余的任务时 maximumPoolSize创建的线程则会被关闭从而释放资源。,
线程池中的四大拒绝策略:
new ThreadPoolExecutor.AbortPolicy()
new ThreadPoolExecutor.CallerRunsPolicy()
new ThreadPoolExecutor.DiscardOldestPolicy()
new ThreadPoolExecutor.DiscardPolicy()
AbortPolicy (默认的拒绝策略):当corePoolSize,maximumPoolSize,workQueue都满了的时候再有任务过来时不会再接收和处理该任务并抛出异常
CallerRunsPolicy: 当corePoolSize,maximumPoolSize,workQueue都满了的时候再有任务过来时线程池不会接受该任务而是让该任务由原来的线程执行。例如当主线程把任务放到线程池中,而线程池满了则该任务会由主线程执行
DiscardOldestPolicy:当corePoolSize,maximumPoolSize,workQueue都满了的时候再有任务过来时会和最早的执行任务的线程进行竞争如果原来的线程任务已经结束那则会使用该线程如果没有结束则不执行该任务 且不会抛异常
DiscardPolicy::当corePoolSize,maximumPoolSize,workQueue都满了的时候再有任务过来时不会再接收和处理该任务且不会抛异常
问题:
最大核心线程数应该怎么确定(如何确定最大线程数)?
1.根据CPU确定,根据电脑的核数来确定线程数,可以把CPU的效率最大化
Runtime.getRuntime().availableProcessors() //获取电脑的核数
2.根据任务的数量来确定,当程序中有任务大量使用IO流的时候,根据任务和IO流任务的数量来确定线程数
注意
在我们创建线程池的时候最好不要使用开头说的那三中方法来创建线程池,而是使用ThreadPoolExecutor()自己定义参数来创建线程池。
ExecutorService threadPool =new ThreadPoolExecutor(
1,
Runtime.getRuntime().availableProcessors(),
10,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.DiscardPolicy());
阿里巴巴开发手册规定