线程池
Java中的线程池是运用场景最多的并发框架。
合理使用线程池能带来三个好处:
a. 降低资源消耗
通过重复利用已创建的线程降低线程创建和销毁造成的消耗
b. 提高响应速度
当任务到达时,任务可以不需要等到线程创建就能立即执行
c. 提高线程的可管理性
线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性。
使用线程池可以进行同一的分配、调优和监控。
线程池的实现原理
当提交一个新任务到线程池时,线程池的处理流程如下 :
1. 线程池判断核心线程池里的线程是否都在执行任务。
如果不是,则创建一个新的工作线程来执行任务。如果核心线程池里的线程都在执行任务,则进入下个流程.
2. 线程池判断工作队列是否已经满。
如果工作队列没有满,则将新提交的任务存储在这个工作队列里。如果工作队列满了,则进入下个流程.
3. 线程池判断线程池的线程是否都处于工作状态。
如果没有,则创建一个新的工作线程来执行任务。如果已经满了,则交给饱和策略来处理这个任务
原书流程图
原书ThreadPoolExecutor执行示意图
1. (图上1) -- 当前运行的线程数量少于corePoolSize(核心线程池数量),创建新线程执行任务(这步操作需要获取全局锁)
2. (图上2) -- 当前运行的线程数量等于或多余corePoolSize(核心线程数量),则将任务加入BlockingQueue(阻塞队列、其他队列)
3. (图上3) -- 无法将任务加入BlockingQueue(队列已满),则创建新的线程来处理任务(这步操作需要获取全局锁)
4. (图上4) -- 如果创建新线程将使当前运行线程数量超出maximunPoolSize,任务将被拒绝,并调用异常策略。
ThreadPoolExecutor采取上述步骤的设计思路,为了在执行execute()方法时,尽可能的避免获取全局锁,在ThreadPoolExecutor完成预热之后,几乎所有的execute()方法调用都是执行步骤2,这样子不需要获取全局锁,以提高性能。
当一个线程无事可做,超过一定时间(KeepAliveTime)时,线程池会判断,如果当前运行的线程数大于corePoolsize,那么这个线程就会被停掉。所以线程池的所有任务完成之后,会尽量收缩到corePoolSize大小。
线程池ThreadPoolExecutor的使用
在编写ThreadPoolExecutor线程池Demo之前,我们先来看下这玩意构造方法的参数....
我们挑一个最长的来看下:
ThreadPoolExecutor
(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory, RejectedExecutionHandler handler)
corePoolSize:
核心线程池数量。
线程池启动后默认是空的,只有任务来临时才会创建线程以处理请求。
不超过maximumPoolSize(最大)值时,线程池中最多有corePoolSize个线程工作。
(原书中将此解释为:线程池的基本大小,感觉会有歧义)
线程池中运行的线程数永远不会超过corePoolSize个,默认情况下可以一直存活
maximumPoolsize:
线程池允许创建的最大线程数。
当workQueue使用"无界队列"时(如LinkedBlockingQueue),则此参数无效。
keepAliveTime:
空闲线程超时时间
线程池的工作线程空闲后,保持存活的时间。
so,如果任务很多,并且每个任务执行的时间比较短,可以调大时间,提高线程的利用率。
unit:
keepAliveTime参数的时间单位。
workQueue:
工作队列(缓冲队列),如果当前线程池中的线程数达到核心线程数(corePoolSize)时,且当前所有线程都处于活动状态,将新加入的任务,放入队列中
常用的任务队列:
- ArrayBlockingQueue:一个基于数组结构的有界阻塞队列、FIFO排序
- LinkedBlockingQueue:一个基于链表结构的阻塞队列、FIFO排序,吞吐量高于ArrayBlockingQueue
- SynchronousQueue:一个不存储元素的阻塞队列
- PriorityBlockingQueue:一个具有优先级的无限阻塞队列
建议使用有界队列
threadFactory:
设置创建线程的工厂,可以通过线程工厂给每个创建出来的线程设置有意义的名字。
newThreadFactoryBuilder().setNameFormat("XX-task-%d").build();
handler:
饱和策略
当队列和线程池都满了,说明线程池处于饱和状态,那么必须采取一个策略处理提交的新任务。
默认情况下是AbortPolicy,表示无法处理新任务时抛出的异常;
常用策略有以下四种:
- AbortPolicy:直接抛出异常(RejectedExecutionException异常)
- CallerRunsPolicy:只用调用者所在线程来运行任务
- DiscardOldestPolicy:丢弃队列中最近的一个任务,并执行当前任务
- DiscardPolicy:不处理,丢掉,没有异常
也可以根据现实场景实现RejectedExecutionHandler接口自定义策略,记录日志,持久化等等...
public class ThreadPoolTask implements Runnable {
private int i = 0;
private AtomicLong along;
public ThreadPoolTask(int i, AtomicLong along) {
this.i = i;
this.along = along;
}
@Override
public void run() {
// 模拟业务处理
try {
Thread.sleep(1000);
along.addAndGet(i);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " 模拟处理 : " + along.get());
}
}
public class TestThreadPoolExecutor {
// 核心线程池数量
private int corePoolSize = 1;
// 线程池最大线程数
private int maximumPoolSize = 10;
// 线程空闲最大存活时间
private long keepAliveTime = 3;
// 线程存活时间单位
private TimeUnit unit = TimeUnit.SECONDS;
private static AtomicLong along = new AtomicLong(0);
public static void main(String[] args) throws Exception {
TestThreadPoolExecutor test = new TestThreadPoolExecutor();
test.run();
}
public void run() throws Exception {
ThreadPoolExecutor pool = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, unit, new LinkedBlockingQueue<Runnable>(), new ThreadPoolExecutor.AbortPolicy());
for (int i = 0; i < 10; i++) {
pool.execute(new ThreadPoolTask(2, along));
}
pool.shutdown();
}
}
输出结果:
pool-1-thread-1 模拟处理 : 2
pool-1-thread-1 模拟处理 : 4
pool-1-thread-1 模拟处理 : 6
pool-1-thread-1 模拟处理 : 8
pool-1-thread-1 模拟处理 : 10
pool-1-thread-1 模拟处理 : 12
pool-1-thread-1 模拟处理 : 14
pool-1-thread-1 模拟处理 : 16
pool-1-thread-1 模拟处理 : 18
pool-1-thread-1 模拟处理 : 20
package com.runnablepriority.runnablepriority1;
import java.text.SimpleDateFormat;
import java.util.concurrent.PriorityBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
*
* 线程池队列插队Demo,自定义线程池,然后使用PriorityBlockingQueue类实现<br>
*
* 这个类创建一个ThreadPoolExecutor对象,名为executor,使用参数化为Runnable接口的PriorityBlockingQueue作为执行者用来存储处理任务的队列.<br>
*
*/
public class ThreadExecutor {
public static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss::SSS");
public static int count = 0;
public static void main(String[] args) {
/**
* corePoolSize : 线程池维护线程的最少数量.<br>
* maximumPoolSize : 线程池维护线程的最大数量.<br>
* keepAliveTime : 线程池维护线程所允许的空闲时间.<br>
* unit : 线程池维护线程所允许的空闲时间的单位.<br>
* workQueue : 线程池所使用的缓冲队列.<br>
*/
ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 1, 1, TimeUnit.MILLISECONDS, new PriorityBlockingQueue<Runnable>());
/**
* execute() :
* 在将来某个时间执行给定任务。可以在新线程中或者在现有池线程中执行该任务。如果无法将任务提交执行,或者因为此执行程序已关闭,或者因为已达到其容量,则该任务由当前RejectedExecutionHandler处理。
*/
// 模拟加入消息.
for (int i = 0; i < 5; i++) {
RunnablePriority r= new RunnablePriority(i);
r.currentThread().setName("线程 : "+String.valueOf(i));
executor.execute(r);
}
try {
Thread.sleep(100);
} catch (Exception e) {
e.printStackTrace();
}
// 模拟插入消息
for (int i = 5; i < 10; i++) {
executor.execute(new RunnablePriority(i));
}
try {
System.out.println("executor.getPoolSize() : " + executor.getPoolSize());
System.out.println("executor.getCorePoolSize() : " + executor.getCorePoolSize());
System.out.println("executor.getMaximumPoolSize() : " + executor.getMaximumPoolSize());
Thread.sleep(8000);
} catch (Exception e) {
e.printStackTrace();
}
// 结束
executor.shutdown();
try {
executor.awaitTermination(1, TimeUnit.DAYS);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("over~");
}
}
package com.runnablepriority.runnablepriority1;
/**
*
* 优先级比较.
*
* 此类实现声明在Comparable接口中的compareTo()方法.<br>
* 它接收一个RunnablePriority对象作为参数.<br>
* 比较着两个对象(当前对象和参数对象)的优先级.让优先级高的任务先优于优先级低的任务执行.<br>
*
* run()方法用来处理业务逻辑.<br>
*
*/
// public class RunnablePriority implements Runnable, Comparable<RunnablePriority> {
public class RunnablePriority extends Thread implements Comparable<RunnablePriority> {
public int priority;
public RunnablePriority(int priority) {
this.priority = priority;
}
public RunnablePriority() {
}
@Override
public int compareTo(RunnablePriority o) {
// 复写此方法进行任务执行优先级排序.
if (this.getPriority() < o.getPriority()) {
System.out.println(1);
return 1;
}
if (this.getPriority() > o.getPriority()) {
System.out.println(-1);
return -1;
}
System.out.println(0);
return 0;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " ____ " + priority);
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
// 其他业务逻辑代码.
}
}
taskCount:线程池需要执行的任务数量
completedTaskCount:线程池在运行过程中已完成的任务数量,小于或等于taskCount
largestPoolSize:线程池里曾经创建过的最大线程数量通过这个数据可以知道线程池是否曾经满过如该数值等于线程池的最大大小,则表示线程池曾经满过
getPoolSize:线程池的线程数量如果线程池不销毁的话,线程池里的线程不会自动销毁,所以这个大小只增不减
getActiveCount:获取活动的线程数
通过扩展线程池进行监控。
可以通过继承线程池来自定义线程池,重写线程池的beforeExecute、afterExecute和terminated方法,也可以在任务执行前、执行后和线程池关闭前执行一些代码来进行监控。
例如,监控任务的平均执行时间、最大执行时间和最小执行时间等。这几个方法在线程池里是空方法。
protected void beforeExecute(Thread t, Runnable r) { }
向线程池提交任务
向线程池提交任务有两个方法:submit()和execute()
submit()方法是AbstractExecutorService类中的,ThreadPoolExecutor继承了AbstractExecutorService类。
executer()方法用于提交不用返回结果的任务,所以无法判断任务是否被线程池执行成功。
submit()方法用于提交需要返回结果的任务。线程池会返回一个Future对象,通过这个Future对象可以判断任务是否执行成功。
关闭线程池
关闭线程池有两个方法:shutdown()和shutdownNow()
它们的原理是遍历线程池中的工作线程,然后逐个调用线程中的interrupt方法来中断线程,所以无法响应中断的任务可能永远没法终止。
shutdown():
只是将线程池的状态设置成SHUTDOWN状态,然后中断所有没有正在执行任务的线程。
通常调用shutdown()来关闭线程池,当然,并没有真正的将里面的工作线程干掉。
shutdownNow():
首先将线程池的状态设置成STOP,然后尝试停止所有的正在执行或暂停任务的线程,并返回等待执行任务的列表。
调用shutdownNow()之后,会将正在工作的线程全部干掉,如果任务不一定要执行完,可以调用这个。