目录
池化技术
池化技术指的是提前准备一些资源,在需要时可以重复使用这些预先准备的资源。池化技术的优点主要有两个:提前准备和重复利用;
常见的池化技术的应用有:线程池、内存池、数据库连接池、HttpClient 连接池
好处
1.复用线程,降低资源消耗
2.提高响应速度
3.管控线程数和任务数
线程池
概述
线程池是一种可以复用线程的技术;
意义
线程是稀缺资源,它创建与销毁是相对偏重且耗资源的操作;线程池就是一个线程缓存,负责对线程进行统一分配、调优与监控。
优势
1、提高效率,创建好一定数量的线程放在池中等待,比需要时重新创建要快的多
2、减少创建和销毁的次数
3、提高响应速度
线程池参数说明
/**
* 用给定的初始参数创建一个新的ThreadPoolExecutor。
*/
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;
}
WorkQueue工作队列
workQueque决定了缓存任务的排队策略,对于不同的业务场景,我们可以选择不同的工作队列。
1.SynchronousQueue
没有容量,直接提交队列,是无缓存等待队列,当任务提交进来,它总是马上将任务提交给线程去执行,如果线程已经达到最大,则执行拒绝策略
2.LinkedBlockingQueue
默认情况下,LinkedBlockingQueue是个无界的任务队列,默认值是Integer.MAX_VALUE,当然我们也可以指定队列的大小。从构造LinkedBlockingQueue源码中可以看出它的大小指定方式
//默认构造函数,大小为Integer最大
public LinkedBlockingQueue() {
this(Integer.MAX_VALUE);
}
//也可以指定大小
public LinkedBlockingQueue(int capacity) {
if (capacity <= 0) throw new IllegalArgumentException();
this.capacity = capacity;
last = head = new Node<E>(null);
}
3.ArrayBlockingQueue
有界的队列,创建的时候必须要指定队列的大小,从源码可以看出构造的时候要传递值
public ArrayBlockingQueue(int capacity) {
this(capacity, false);
}
4.DelayQueue
是一个延迟队列,无界、队列中每个元素都有过期时间,当从队列获取元素时,只有过期的元素才会出队,而队列头部是最早过期的元素,若是没有过期,则进行等待
拒绝策略
AbortPolicy():队列满了,直接丢弃,并抛出异常
CallerRunsPolicy():队列满了,直接丢弃任务,不抛出异常
DiscardPolicy():队列满了,丢弃等待最久未处理的任务,并加入到等待队列中
DiscardOldestPolicy():队列满了,直接在主线程中运行,不再进入线程池
使用风险
1.死锁:两个或更多线程阻塞着等待其它处于死锁状态的线程所持有的锁,任何多线程应用程序都有死锁风险
2.资源不足:线程消耗包括内存和其他系统资源在内的大量资源
3.线程泄露:当从线程池中取出一个线程以执行任务,但任务完成后该线程没有返回池时,就会发生线程泄漏,如果这种情况发生次数足够多,线程池最终会为空,系统将停止,因为没有可用的线程来处理任务
创建方式
Executors类(并发包)提供了4种创建线程池方法,这些方法最终都是通过配置ThreadPoolExecutor的不同参数,来达到不同的线程管理效果
newCacheTreadPool(可缓存)
创建一个可以缓存的线程池,如果线程池长度超过处理需要,可以灵活回收空闲线程,没回收的话就新建线程
public static void main(String[] args) {
// 创建可缓存线程池
ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();
for (int i = 0; i < 5; i++) {
//创建任务
Runnable runnable = new Runnable(){
@Override
public void run() {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName());
}
};
newCachedThreadPool.execute(runnable);
}
}
线程池的最大核心线程为无限大,当执行第二个任务时第一个任务已经完成,则会复用执行第一个任务的线程;如果第一个线程任务还没有完成则会新建一个线程
newFixedThread(固定大小)
创建一个定长的线程池,可控制最大并发数,超出的线程进行队列等待
public static void main(String[] args) {
// 创建定长线程池
ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(2);
for (int i = 0; i < 5; i++) {
//创建任务
Runnable runnable = new Runnable(){
@Override
public void run() {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName());
}
};
// 将任务交给线程池管理
newFixedThreadPool.execute(runnable);
}
}
创建指定长度的线程池,任务超出当前线程池执行线程数量则会一直等待,直到运行。
newScheduleThreadPool(无限大小)
可以创建定长的、支持定时任务,周期任务执行。
public static void main(String[] args) {
// 创建支持定时线程池
ScheduledExecutorService newScheduledThreadPool = Executors.newScheduledThreadPool(2);
for (int i = 0; i < 5; i++) {
//创建任务
Runnable runnable = new Runnable(){
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
};
// 将任务交给线程池管理,延迟2秒后才开始执行线程池中的所有任务
newScheduledThreadPool.schedule(runnable, 2, TimeUnit.SECONDS);
}
}
以下案例中延迟2秒后开始执行线程池中的所有任务
newSingleExecutor(单线程)
创建一个单线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行
public static void main(String[] args) {
// 创建单线程-线程池,任务依次执行
ExecutorService newScheduledThreadPool = Executors.newSingleThreadExecutor();
for (int i = 0; i < 5; i++) {
//创建任务
Runnable runnable = new Runnable(){
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
};
// 将任务交给线程池管理
newScheduledThreadPool.execute(runnable);
}
}
ThreadPoolExecutor
/**
* 用给定的初始参数创建一个新的ThreadPoolExecutor。
*/
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;
}
处理流程
线程池状态
线程池有五种状态:
1.RUNNING,运行状态,线程池创建完成后就是运行状态。
2.SHUTDOWN,关闭状态,执行shutdown()
方法后进入此状态,继续处理队列中的任务,但是不再接收新的任务。
3.STOP,停止状态,shutdownNow()
方法后进入此状态,不处理队列中的任务,也不接收新的任务。
4.TIDYING,整理状态,运行的线程数为0,队列中任务为空时,则进入此状态,进入此状态后会执行terminated()
方法,进入销毁状态。
5.TERMINATED,销毁状态,执行terminated()
方法,进入此状态
private static final int RUNNING = -1 << COUNT_BITS;
private static final int SHUTDOWN = 0 << COUNT_BITS;
private static final int STOP = 1 << COUNT_BITS;
private static final int TIDYING = 2 << COUNT_BITS;
private static final int TERMINATED = 3 << COUNT_BITS;
线程配置
获取核心数:Runtime.getRuntime().availableProcessors()
IO密集型:
判断程序中十分消耗IO的线程数,最大线程数一般设置为线程数的2倍
CPU密集型:
判断电脑的核数,最大线程数设置为核数,可以保持cpu的效率最高