线程池的任务调度流程:
1:如果当前工作线程数量小于核心线程数量,执行器总是优先创建一个任务线程,而不是从线程队列中获取一个空闲线程.
2:如果线程池中总的任务数量大于核心线程数量.新接收的任务将被加入阻塞队列中.一直到阻塞队列满,在核心线程数量已经用完,并且阻塞队列没有满,线程池不会为新任务创建一个新线程.
3:当完成一个任务执行的时候.执行器总是优先从阻塞队列中获取下一个任务,并开始执行,一直到阻塞队列为空.
4:在核心线程数量用完,并且阻塞队列也已经满的情况下,如果线程池接收的新的任务,就会创建一个新的线程(非核心线程)执行这个任务.
5:在核心线程都用完,阻塞队列也已经满了,非核心线程加上核心线程大于最大线程数,对新来的任务就会执行拒绝策略.
不合理线程池配置
public class CreateThreadPoolErrorDemo {
public static void main(String[] args) throws InterruptedException {
ThreadPoolExecutor executor = new ThreadPoolExecutor(
//核心线程数.
1,
//最大线程数.
100,
//空闲存活时长.
100,
//存活时长单位.
TimeUnit.SECONDS,
//阻塞队列.
new LinkedBlockingDeque<>(100));
//提交五个任务.
for (int i = 0; i < 5; i++) {
final int taskIndex = i;
executor.execute(() -> {
System.out.println("taskIndex+" + taskIndex);
//极端测试.
try {
Thread.sleep(Integer.MAX_VALUE);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
while (true) {
System.out.println("工作任务数量-->" + executor.getActiveCount()
+ "总任务数-->" + executor.getTaskCount());
Thread.sleep(1000);
}
}
}
从以上例子可以知道:
1:核心线程和最大线程数量阻塞队列等参数如果配置不合理,可能会造成异步任务无法达到预期的并发执行,造成严重的排队等待现象.
2:线程调度器创建线程的一条重要规则是:在核心线程已满以后,只有阻塞队列满了以后,才会去创建新的线程.
ThreadFactory(线程工厂)
ThreadFactory是java线程工厂的一个接口.
public interface ThreadFactory {
/**
* Constructs a new {@code Thread}. Implementations may also initialize
* priority, name, daemon status, {@code ThreadGroup}, etc.
*
* @param r a runnable to be executed by new thread instance
* @return constructed thread, or {@code null} if the request to
* create a thread is rejected
*/
Thread newThread(Runnable r);
}
用线程工厂创建新线程时,可以更改新线程的名称 线程组 优先级 守护进程状态等,如果返回值为null,表示线程未能创建成功,线程池可能无法执行任务.使用Executors创建线程时可以指定ThreadFactory实例,如果没有指定的话,就会使用默认的线程工厂.
public static ThreadFactory defaultThreadFactory() {
return new DefaultThreadFactory();
}
下面贴出一个关于线程工厂的例子.
public class ThreadFactoryDemo {
//一个简单的线程工厂.
static public class SimpleThreadFactory implements ThreadFactory {
static AtomicInteger threadNo = new AtomicInteger(1);
//实现唯一的线程创建方法.
@Override
public Thread newThread(Runnable r) {
String threadName = "simpleThread-" + threadNo.get();
System.out.println("创建一条线程为,名称为" + threadName);
threadNo.getAndIncrement();
//设置线程名称和异步执行目标.
Thread thread = new Thread(r, threadName);
//设置为守护线程.
thread.setDaemon(true);
return thread;
}
}
public static void main(String[] args) throws InterruptedException {
ExecutorService threadPool =
Executors.newFixedThreadPool(2, new SimpleThreadFactory());
for (int i = 0; i < 5; i++) {
threadPool.submit(()->{
System.out.println("我是手动创建的线程在执行.");
});
}
Thread.sleep(1000);
System.out.println("关闭线程池.");
threadPool.shutdown();
}
}
任务阻塞队列
java阻塞队列和普通队列有一个区别是,阻塞队列为空时,会阻塞当前线程的获取操作.有数据了会唤醒对应的线程.
阻塞队列BlockingQueue的实例
1:ArrayBlockingQueue
是一个数组实现的有界阻塞队列,队列中的元素按FIFO排序.在创建时必须设置大小.接收任务超过核心线程数量的时候会放入该阻塞队列,若阻塞队列已满的话,会创建新的线程,直到线程数量等于最大线程数.
2:LinkedBlockingQueue
是一个基于链表实现的阻塞队列,队列中的元素按FIFO排序.可以设置队列的大小,为有界队列,不设置的话默认用Integer.MAX_VALUE,是无界队列.该队列的吞吐量高于ArrayBlockingQueue.
如果不设置该队列的容量的话,核心线程数量达到,会因为无限接收任务而导致资源耗尽.
3:PriorityQueue
具有优先级的阻塞队列.
4:DelayQueue
是一个无界延迟阻塞队列,底层基于PriorityQueue实现,队列中每个元素都有过期时间,从队列获取元素时,只有已经过期的元素才会出队,而队列头部是最先过期的元素.
5:SynchronousQueue(同步队列)
这是一个不存储元素的阻塞队列,,每个插队操作必须等到另一个线程的调用移除操作.否则插入一条数据一直处于,吞吐量通常高于LinkedBlockingQueue,与前面的队列相比,这个队列不会存储任务,而是直接创建一个线程执行.
调度器的钩子方法
ThreadPool线程调度器为每个任务执行前后都提供了钩子方法.
//任务执行前的钩子方法.
protected void beforeExecute(Thread t, Runnable r) { }
//任务执行后的钩子方法.
protected void afterExecute(Runnable r, Throwable t) { }
//线程终止的钩子方法.
protected void terminated() { }
beforeExecute:异步任务执行之前的钩子方法.
线程池工作线程在异步执行目标实例前调用此钩子方法.此方法仍由执行任务的工作线程调用.此钩子方法的默认实现不执行任何操作,可以在调度器子类中对其进行自定义.
此方法由执行目标实例的工作线程调用,可用于初始化ThreadLocal线程本地变量实例 更新日志记录 开始计时统计 更新上下文变量.
afterExecute:异步任务执行之后的钩子方法.
线程池工作线程在异步执行目标实例后调用此钩子方法.此方法仍由执行任务的工作线程调用.此钩子方法的默认实现不执行任何操作,可以在调度器子类中对其进行自定义.
terminated:线程终止时的钩子方法.
在Executor终止时调用,默认不实现任何操作.
public class ThreadPoolDemo {
public static void main(String[] args) throws InterruptedException {
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2,
4,
60,
TimeUnit.SECONDS,
new LinkedBlockingDeque<>(2)) {
@Override
protected void terminated() {
System.out.println("调度器停止工作");
}
@Override
protected void beforeExecute(Thread t, Runnable r) {
System.out.println(r + "执行前工作.");
super.beforeExecute(t, r);
}
@Override
protected void afterExecute(Runnable r, Throwable t) {
super.afterExecute(r, t);
System.out.println(r + "执行后工作.");
}
};
for (int i = 0; i < 5; i++) {
threadPool.execute(()->{
System.out.println("执行任务.");
});
}
Thread.sleep(1000);
threadPool.shutdown();
}
}
线程池的拒绝策略:
任务拒绝有两种情况:
1:阻塞队列已经满了,并且已经达到了最大线程数.
2:线程池关闭.
不管哪种情况调用拒绝策略,线程池都会调用这个接口的实例.
public interface RejectedExecutionHandler {
/**
* Method that may be invoked by a {@link ThreadPoolExecutor} when
* {@link ThreadPoolExecutor#execute execute} cannot accept a
* task. This may occur when no more threads or queue slots are
* available because their bounds would be exceeded, or upon
* shutdown of the Executor.
*
* <p>In the absence of other alternatives, the method may throw
* an unchecked {@link RejectedExecutionException}, which will be
* propagated to the caller of {@code execute}.
*
* @param r the runnable task requested to be executed
* @param executor the executor attempting to execute this task
* @throws RejectedExecutionException if there is no remedy
*/
void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}
拒绝策略:
public static class AbortPolicy implements RejectedExecutionHandler {
/**
* Creates an {@code AbortPolicy}.
*/
public AbortPolicy() { }
/**
* Always throws RejectedExecutionException.
*
* @param r the runnable task requested to be executed
* @param e the executor attempting to execute this task
* @throws RejectedExecutionException always
*/
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
throw new RejectedExecutionException("Task " + r.toString() +
" rejected from " +
e.toString());
}
}
使用该策略,如果线程池队列满了,新任务就会被拒绝,并且抛出RejectedExecutionHandler异常,这个是线程池的默认策略.
抛弃策略:
public static class DiscardPolicy implements RejectedExecutionHandler {
/**
* Creates a {@code DiscardPolicy}.
*/
public DiscardPolicy() { }
/**
* Does nothing, which has the effect of discarding task r.
*
* @param r the runnable task requested to be executed
* @param e the executor attempting to execute this task
*/
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
}
}
如果线程池的队列满了,新任务会直接丢掉,不会抛出任何异常.
抛弃最老任务策略:
public static class DiscardOldestPolicy implements RejectedExecutionHandler {
/**
* Creates a {@code DiscardOldestPolicy} for the given executor.
*/
public DiscardOldestPolicy() { }
/**
* Obtains and ignores the next task that the executor
* would otherwise execute, if one is immediately available,
* and then retries execution of task r, unless the executor
* is shut down, in which case task r is instead discarded.
*
* @param r the runnable task requested to be executed
* @param e the executor attempting to execute this task
*/
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
e.getQueue().poll();
e.execute(r);
}
}
}
使用该策略,如果队列满了,就会将最早进入的队列的任务抛弃,从队列中腾出空间,在尝试加入队列,因为队列是队尾进,队头出.,所以每次都是移除队头的元素在尝试入队.
调用者执行策略:
public static class CallerRunsPolicy implements RejectedExecutionHandler {
/**
* Creates a {@code CallerRunsPolicy}.
*/
public CallerRunsPolicy() { }
/**
* Executes task r in the caller's thread, unless the executor
* has been shut down, in which case the task is discarded.
*
* @param r the runnable task requested to be executed
* @param e the executor attempting to execute this task
*/
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
r.run();
}
}
}
在新任务被添加到线程池时,如果添加失败,那么提交任务的线程会自己去执行该任务.
自定义拒绝策略:
class CustomIgnorePolicy implements RejectedExecutionHandler {
@Override
public void rejectedExecution(Runnable r,
ThreadPoolExecutor executor) {
//做日志记录等.
System.out.println("任务" + r + "-getTaskCount:"
+ executor.getTaskCount());
//可以自己搞一个线程自己处理.
Thread thread = new Thread(r);
thread.start();
}
}
这块完全可以发挥自己的想象力,比如发送mq 发送邮件其实都是可以的.
优雅的关闭线程池:
了解关闭线程池之前,先看下线程池的五种状态.
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;
RUNNING:
线程池创建后的初始状态,这种状态下可以执行任务.
SHUTDOWN:
该状态下线程不会在接收任务,但是会将工作队列中剩余的任务执行完成.
STOP:
该状态下的线程池不会在接受任务,也不会在执行队列中剩下的任务,也会中断所有线程.
TIDYING:
该状态下所有任务都已终止或者处理完成,将会执行terminated()钩子方法.
TERMINATED:
执行完terminated()钩子方法之后的状态.
线程池的状态转换规则:
1:线程池创建后为RUNNING.
2:执行线程池的shutdown实例方法,会使线程池状态从RUNNING转变为SHUTDOWN.
3:执行线程池的shutdownNow实例方法,会使线程池的状态从RUNNING变为STOP.
4:当前线程池处于SHUTDOWN状态,执行其shutdownNow方法会让其状态变为STOP.
5:等待线程池的所有工作线程停止,工作队列清空之后,线程池状态会从STOP转变为TIDYING,
6:执行完terminated钩子方法以后,线程池状态从TIDYING转变为TERMINATED.
优雅的关闭线程池涉及的方法有三种:
1:shutdown方法
是juc提供的一个有序关闭线程池的方法,此方法会等待当前队列中的剩余任务全部执行完成才会执行关闭,此方法调用以后线程池的状态会变为SHUTDOWN,线程池不会在接收新的任务.
2:shutdownNow方法
是juc提供的一个立即关闭线程池的方法,此方法会打断正在执行的工作线程,并且会清空当前工作队列中剩余的任务,返回的是尚未执行的任务.
3:awaitTermination方法
等待线程池完成关闭.在调用线程池的shutdown和shutdownNow方法当前线程会立即返回,不会一直等待线程池关闭.如果需要等待线程池关闭可以使用awaitTermination方法.
追逐的路上,也许我会慢一些,但是我一定不会放弃.
如果大家喜欢我的分享的话,可以关注一下微信公众号.