池化思想:
是一种空间换时间的一种思想,池相当于一个共享资源。
为了减少频繁的创建对象回收对象造成内存资源的浪费与加快申请资源的时间。
白话解析(自我理解):
相当于:屋子里面有个缸,我和张三李四想喝水,想喝水就得有瓢,我喝我买一个瓢,张三喝买一个瓢,李四喝也买一个瓢,喝完水瓢就多了,多了怎么办呢踩碎它(也就是销毁),这就造成了资源浪费。但是呢你买了一个或者多个专门喝水的瓢,我要是想喝水的话直接舀水就行了,喝完了放回去其他人也是,这个瓢就一直放在水缸里,也不会存在我喝完水就把瓢踩碎这一说,但是屋子里面也不会买那么多瓢是吧,你的缸里面只要有一个瓢就行了(池里面设置一个对象)。但是呢人多了,需求就上来了就得多买几个瓢。比如植树节中大家刚刚植完树,多需要喝水(这就是需要的业务场景)。
目前比较常见的就是常量池,连接池(数据库连接池,http请求连接池),线程池。
数据库连接池
这里主要讲解的是C3P0。其实这种数据库连接池都差不多是一样的比如Druid。只不过应该是代理不一样。对基本的数据源与连接对象都进行了封装
使用Druid来进行查看讲解
配置数据源
package com.example.practise;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import javax.sql.DataSource;
import java.sql.Connection;
import java.util.HashMap;
import java.util.Map;
/**
* ClassName: DatabaseConnectionPoolTest
* Package: com.example.practise
* Description: 数据库连接池学习
* Datetime: 2023/4/22 14:38
*
* @author: xx-zh
*/
public class DatabaseConnectionPoolTest {
public static void main(String[] args) throws Exception{
extracted();
}
private static void extracted() throws Exception {
// 配置连接需要参数。使用这种map实现方式,你需要掌握每个配置属性,不推荐
Map<String, Object> map = new HashMap(4);
map.put("driverClassName", "com.mysql.cj.jdbc.Driver");
map.put("username", "root");
map.put("password", "mengweiqi666");
map.put("url", "jdbc:mysql://localhost:3307/test?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8");
// 查看返回对象DataSource,打印DataSource对象类型返回。class com.alibaba.druid.pool.DruidDataSource
DataSource dataSource = DruidDataSourceFactory.createDataSource(map);
// 查看连接对象Connection,打印Connection对象类型返回。class com.alibaba.druid.pool.DruidPooledConnection
Connection connection = dataSource.getConnection();
System.out.println(dataSource.getClass());
System.out.println(connection.getClass());
// 可以根据打印返回类型得出,三方数据库连接池,只不过是在java原有的基础上进行封装,代理实现
DruidDataSource druidDataSource = new DruidDataSource();
druidDataSource.setUsername("root");
druidDataSource.setPassword("mengweiqi666");
druidDataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
druidDataSource.setUrl("jdbc:mysql://localhost:3307/test?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8");
//用来检测连接是否有效
druidDataSource.setValidationQuery("SELECT 1");
//借用连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能
druidDataSource.setTestOnBorrow(false);
//归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能
druidDataSource.setTestOnReturn(false);
//连接空闲时检测,如果连接空闲时间大于timeBetweenEvictionRunsMillis指定的毫秒,执行validationQuery指定的SQL来检测连接是否有效
//如果检测失败,则连接将被从池中去除
druidDataSource.setTestWhileIdle(true);
//1分钟
druidDataSource.setTimeBetweenEvictionRunsMillis(60000);
druidDataSource.setMaxActive(20);
druidDataSource.setInitialSize(5);
}
}
根据上方代码执行方式实现,System打印,可以看出DataSource与Connection的实现子类是Druid包下的类
在进行测试的时候,尽量使用下方直接使用DruidDataSource方式来创建数据库连接池,只进行方法普及了解,以后项目中使用还是配置文件配置进行获取
yml文件配置数据库连接池
spring:
datasource:
username: 用户名
password: 密码
url: jdbc:mysql://localhost:3307/test?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
druid:
# 下面为连接池的补充设置,应用到上面所有数据源中
# 初始化大小,最小,最大
initial-size: 5
min-idle: 5
max-active: 20
# 配置获取连接等待超时的时间
max-wait: 60000
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
time-between-eviction-runs-millis: 60000
# 配置一个连接在池中最小生存的时间,单位是毫秒
min-evictable-idle-time-millis: 300000
validation-query: SELECT 1 FROM DUAL
test-while-idle: true
test-on-borrow: false
test-on-return: false
# 打开PSCache,并且指定每个连接上PSCache的大小
pool-prepared-statements: true
# 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
max-pool-prepared-statement-per-connection-size: 20
filters: stat,wall
use-global-data-source-stat: true
# 通过connectProperties属性来打开mergeSql功能;慢SQL记录
connect-properties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
线程池
原始线程调用:
手动创建线程对象,执行任务执行完毕,释放线程对象,和数据库连接池差不多。
线程池中内部执行流程:
我的线程池中创建了3个线程对象,但是有5个任务需要调用,这样就需要两个排队(等待队列这个是固定大小的),如果等待队列满了就会创建一个新的线程,但是不会来一个创建一个,会有一个最大(线程对象数),如果超过这个大小的话就会发生拒绝策略。
线程池中的线程需要一个存活时间的概念,就是过期时间,没用到的线程就干掉。
JUC并发工具包中提供了一个ThreadPoolExcutor。使用其来创建线程池
进入ThreadPoolExcutor中可以看到线程池中定义的常量,也可以说是线程的状态
//是一个it类型的数值,表达了两个意思,1:声明当前线程池的状态,2:声明线程池中的线程数
//高3位是:线程池状
低29位是:线程池中的线程个数
private final AtomicInteger ctl new AtomicInteger(ctlof(RUNNING,0));
private static final int COUNT_BITS = Integer.SIZE-3;
//29,方便后面做位运算
private static final int CAPACITY = (1<COUNT_BITS)-1; //通过为运算得出最大容量
//线程池状态
private static final int RUNNING = -1 <COUNT BITS:
//111代表线程池为RUNNING,代表正常接收任务
private static final int SHUTDOWN = 0 <COUNT BITS: //000代表线程池为SHUTDOWN状态,不接收新任务,但是内部还会处理阻塞队列中的任务,正在进行的任务也正常处理
private static final int STOP = 1 <COUNT BITS: //001代表线程池为STOP状态,不接收新任务,也不去处理阻塞队列中的任务,同时会中断正在执行的任务
private static final int TIDYING = 2 <COUNT_BITS; //010代表线程池为TIDYING状态,过渡的状态,代表当前线程池即将Game Over
private static final int TERMINATED = 3 <COUNT_BITS;//O11代表线程池为TERMINATED.真的凉凉了。
private static int runStateOf(int c){return c&CAPACITY:} //得到线程池的状态
private static int workerCountof(intc){return c&CAPACITY:} //得到当前线程池的线程数量
线程池的状态变化
主要记住线程池的5中状态(感觉会是面试题)
- running:运行
- shutdown:线程池不接收新的线程请求,但是会完成没有运行完成的线程包括队列中的线程。线程池执行方法shutdown()
- stop:线程池不接收新的线程请求,内部正在运行的线程也会直接关闭。线程池执行方法shutdownNow()
- tidying:线程池进入到整理状态(由shutdown状态时,队列为空,工作线程为空。stop状态时,工作线程全空)
- terminated:线程池进入终止状态(由整理状态调用terminater()方法进入)。线程池彻底完蛋
创建一个简单的线程池
public static void main(String[] args) {
/**
* 参数解析
* 第一个 核心线程数
* 第二个 最大线程数(等待队列满了之后会增加线程对象但不会超过最大线程数,如果超过返回拒绝策略)
* 第三个 非核心线程数/其他线程数(我自定义的,最大线程数-默认线程数的线程)存活/空闲时间
* 第四个 与第三个呼应,存活时间格式
* 第五个 等待队列
* 第六个 线程工厂,可以控制线程名称
* 第七个 拒绝策略
*/
ExecutorService executorService = new ThreadPoolExecutor(3, 5, 1L, TimeUnit.SECONDS, new ArrayBlockingQueue<>(3), new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r);
t.setName("线程名称");
return t;
}
}, new ThreadPoolExecutor.AbortPolicy());
for (int i = 0; i < 9; i++) {
executorService.execute(() -> {
System.out.println(Thread.currentThread().getName() + "===>办理业务");
});
}
executorService.shutdown();
}
解析线程池构造方法
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:最大线程数
keepAliveTime:非核心线程数存在时间
unit:时间格式(时分秒)
workQueue:等待队列
threadFactory:线程工厂
handler:决绝策略
核心线程数和非核心线程数
ThreadPoolExecutor会根据corePoolSize
和maximumPoolSize
参数自动调整线程池的大小。当方法execute(Runnable)
提交一个新任务的时候,如果运行的线程数量小于corePoolSize
,即使存在空闲的线程也会创建一个新的线程来处理请求,如果运行的线程数量大于corePoolSize
但小于maximumPoolSize
时,并且队列已经满了,则会创建一个新的线程执行任务。corePoolSize和maximumPoolSize的数量也可以通过setCorePoolSize
和setMaximumPoolSize
动态的修改。
默认情况下ThreadPoolExecutor
核心线程在任务到达的时候创建和启动的。你可以使用prestartCoreThread
、prestartAllCoreThreads
来动态覆盖来预先启动线程,或者使用非空队列的时候,可能需要虚线启动线程。
创建线程
使用ThreadFactory
创建新的线程,如果不指定,默认使用Executors.defaultThreadFactory,他创建的线程具有相同的TheadGroup、相同的优先级、相同的非守护状态。通过不同的ThreadFactory,你可以方便的更改线程的名字、线程的优先级、线程的守护状态。
非核心线程空闲后终止策略
当前线程池中超过了corePoolSize
数量的线程,多余的空闲线程在keepAliveTime
时间到达后将被终止。这提供了一种减少资源消耗的方法。如果稍后任务量更加大的时候,线程池又会创建新的线程执行任务。默认情况下只有线程达到了corePoolSize
数量时,才会应用keepAliveTime
策略。但是方法allowCoreThreadTimeOut(boolean)
也可以将这个超时时间策略应用在核心线程上,前提是keepAliveTime不是0。
队列
任何的BlockingQueue
队列都可以用来传输和提交任务。
如果线程池中运行线程数量小于corePoolSize
,那么线程池更倾向于创建一个新的线程执行任务,而不是将线程放入等待队列中,
如果线程池中的任务大于了corePoolSize
,那么线程池更加倾向于将任务放入队列,而不是创建新的线程,如果此时队列满了,线程池会创建一个新的线程,如果在此时线程数已经达到了maximumPoolSize
,这种情况下任务将被拒绝(决绝策略)。
直接交换队列
直接交换队列的一个很好的选择是SynchronousQueue。它本身不存储任何任务,只是将任务传递给线程,如果此时没有任何线程来执行这个任务,并且下一个任务已经到达,那么任务就排队就会失败,导致线程池创建一个新的非核心线程。直接传递需要无限的maxmunPoolsize,以避免决绝新提交的任务。然任务的到达速度高于线程处理任务的速度,那么就会导致线程无限增长。
无界队列
无界队列LinkedBlockingQueue表示没有设置具体容量的队列,这将导致在corePoolSize都处于繁忙时,任务都会放入队列中,因而不会创建超过corePoolSize的线程,因此maxmunPoolSize没有任何效果
。如果任务平均到达速度高于线程处理任务的速度,将可能造成队列的任务过多导致OOM
。
有界队列
有界队列,例如ArrayBlockingQueue可以使用maxmumPoolSize防止系统的资源被耗尽,种情况下更加难以调优和控制。线程池大小和队列大小可以相互权衡,大队列小线程池可以减少CPU的开销,但系统的吞吐量就会变低。大线程池小队列可以增加CPU的开销,可能会遇到不可预测的调度开销,也会降低吞吐量。
决绝策略
当线程池已经关闭或者使用有界队列的前提下,线程的数量已经达到了maxmunPoolSize并且有界队列已经饱和,那么通过execute(Runnable)
方法提交的新任务将被拒绝。这种情况下execute方法会调用RejectedExecutionHandler#rejectedExecution(Runnable,ThreadPoolExecutor)
方法。提供了四个预定义的拒绝粗略:
AbortPolicy(默认):抛出RejectedExecutionException运行时异常
CallerRunPolicy:抛给调用execute(Runnable)的线程去执行,也就是交给启动该任务的线程去执行。
DiscardPolicy:丢弃策略,直接将任务丢弃。
DiacardOldestPolicy:丢弃队列头部任务,也就是丢弃最老的任务。
Hook函数
ThreadPoolExecutor提供可覆写的preExecute(Thread,Runbale)、afterExecute(Thread,Runnable)
方法,在任务执行之前和执行之后调用。这些可以用来控制执行环境,例如可以重新初始化ThreadLocals、收集统计数据或者增加日志。此外,还可以覆写的terminated方法,可以在所有Executor全部终止后执行所需要的特殊处理逻辑。
如果Hook函数或者回调方法失败,那么内部的工作线程将会失败并突然终止。
队列维护
getQueue()方法允许访问工作队列,以进行监视和调试。强烈反对将此方法用于其他目的。提供了两个remove(Runnable)和purge方法,可用于在大量任务被取消执行的时候进行回收存储。
excute方法定义
public void execute(Runnable command) {
// 线程为空直接返回异常
if (command == null)
throw new NullPointerException();
// 获取线程池正在运行的线程数
int c = ctl.get();
// 当前线程池的线程数量小于核心线程数的话进行addWorker方法
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
else if (!addWorker(command, false))
reject(command);
}
excute方法主要执行方法addWorker
private boolean addWorker(Runnable firstTask, boolean core) {
// 定义标签,方便跳出循环
retry:
for (;;) {
// 获取运行线程数,与线程池状态
int c = ctl.get();
int rs = runStateOf(c);
// Check if queue empty only if necessary.
// 线程池状态不是运行并且(线程池为shutdown状态,并且当前线程为空,并且等待队列不为空)取反
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false;
// 开始遍历
for (;;) {
// 获取当前线程池的线程状态
int wc = workerCountOf(c);
// 当前线程数大于等于最大容量,或者大于等于核心线程数/最大线程数(根据code判断)
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
if (compareAndIncrementWorkerCount(c)) //cas运算,运行线程数+1
// 跳出循环
break retry;
c = ctl.get(); // Re-read ctl
// 当前线程池状态不等于原始线程池状态就跳出循环一次
if (runStateOf(c) != rs)
continue retry;
// else CAS failed due to workerCount change; retry inner loop
}
}
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
w = new Worker(firstTask);
final Thread t = w.thread;
if (t != null) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// Recheck while holding lock.
// Back out on ThreadFactory failure or if
// shut down before lock acquired.
int rs = runStateOf(ctl.get());
// 当前线程池状态为运行,或者(状态为shutdown并且线程为空),
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
if (t.isAlive()) // precheck that t is startable
throw new IllegalThreadStateException();
// 锁池添加线程
workers.add(w);
int s = workers.size();
// 锁池线程数大于锁池的最大线程数,就将最大线程数重新赋值为锁池线程数
if (s > largestPoolSize)
largestPoolSize = s;
workerAdded = true;
}
} finally {
mainLock.unlock();
}
// 启动线程
if (workerAdded) {
t.start();
workerStarted = true;
}
}
} finally {
// 继续跟踪
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}
创建一个Worker就会执行runWorker方法
final void runWorker(Worker w) {
// 获得当前线程
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock(); // allow interrupts
boolean completedAbruptly = true;
try {
while (task != null || (task = getTask()) != null) {
w.lock();
// If pool is stopping, ensure thread is interrupted;
// if not, ensure thread is not interrupted. This
// requires a recheck in second case to deal with
// shutdownNow race while clearing interrupt
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() &&
runStateAtLeast(ctl.get(), STOP))) &&
!wt.isInterrupted())
wt.interrupt();
try {
// 线程执行前方法,需要自己重写
beforeExecute(wt, task);
Throwable thrown = null;
try {
// 执行线程
task.run();
} catch (RuntimeException x) {
thrown = x; throw x;
} catch (Error x) {
thrown = x; throw x;
} catch (Throwable x) {
thrown = x; throw new Error(x);
} finally {
// 线程执行后方法,需要自己重写
afterExecute(task, thrown);
}
} finally {
task = null;
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
processWorkerExit(w, completedAbruptly);
}
}
引申方法getTask()
private Runnable getTask() {
boolean timedOut = false; // Did the last poll() time out?
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// Check if queue empty only if necessary.
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
int wc = workerCountOf(c);
// Are workers subject to culling?
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
try {
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
if (r != null)
return r;
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
连接池
等待编写。。。