JVM使用的什么线程模型(Java)里提到了,JVM使用的是KLT线程,当任务提交进来,需要等线程创建才能执行,执行完毕后,线程会被内核自动销毁。当任务小而体量大时,大量的创建和销毁造成资源浪费,因此,可以借助线程池来管理调度线程,减少线程的创建和销毁的开销,提高任务相应速度,提高线程的可管理性。
/**
* Creates a new {@code ThreadPoolExecutor} with the given initial
* parameters and
* {@linkplain Executors#defaultThreadFactory default thread factory}.
*
* @param corePoolSize the number of threads to keep in the pool, even
* if they are idle, unless {@code allowCoreThreadTimeOut} is set
* @param maximumPoolSize the maximum number of threads to allow in the
* pool
* @param keepAliveTime when the number of threads is greater than
* the core, this is the maximum time that excess idle threads
* will wait for new tasks before terminating.
* @param unit the time unit for the {@code keepAliveTime} argument
* @param workQueue the queue to use for holding tasks before they are
* executed. This queue will hold only the {@code Runnable}
* tasks submitted by the {@code execute} method.
* @param handler the handler to use when execution is blocked
* because the thread bounds and queue capacities are reached
* @throws IllegalArgumentException if one of the following holds:<br>
* {@code corePoolSize < 0}<br>
* {@code keepAliveTime < 0}<br>
* {@code maximumPoolSize <= 0}<br>
* {@code maximumPoolSize < corePoolSize}
* @throws NullPointerException if {@code workQueue}
* or {@code handler} is null
*/
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), handler);
}
ThreadPoolExecutor的参数
int corePoolSize : 核心线程数, 当任务提交进来时,当核心线程未被任务占满时,任务先进入核心线程
int maximumPoolSize : 线程池可容纳最多线程数
long keepAliveTime, TimeUnit unit : 当前线程数大于核心线程数时,非核心线程的等待被执行的等待时间
BlockingQueue<Runnable> workQueue : 阻塞队列,当核心线程被占满,新的任务会进入阻塞队列等待执行
RejectedExecutionHandler handler) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), handler) : 拒绝策略,阻塞队列已满或无法将新任务添加到线程池时应对方案
新任务被提交给线程池后如何工作
举个例子
创建一个线程池:
ThreadPoolExecutor pool = new ThreadPoolExecutor(
2, 3, //核心线程数和同时可容纳线程最大数量
60, TimeUnit.SECONDS, // 线程没有工作时最多可以存活多久
new ArrayBlockingQueue<Runnable>(3), //阻塞队列
new ThreadPoolExecutor.CallerRunsPolicy()); //拒绝策略
核心线程数为2,最大线程数为3(即非核心线程数为1),非核心线程最长等待时间为60秒,阻塞队列为基于数组的队列,大小为3, 拒绝策略选择如果添加到线程池失败,那么调度线程池的线程自己去执行该任务
现在给线程池提交任务->
当核心线程还有空闲时,任务先抢占核心线程,如果核心线程已占满->
当阻塞队列未满时,任务进入队列排队,如果队列已满->
当非核心线程还有空闲时,任务抢占非核心线程,如果非核心线程已满->
拒绝策略决定该任务由调度线程池的线程去执行。
创建一个Task类模拟任务
class Task implements Runnable{
int _i;
Task(int i){
_i = i;
}
@Override
public void run() {
System.out.println("任务"+_i+"开始执行");
try {
Thread.sleep(2000);
System.out.println("任务"+_i+"已完成");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
循环模拟提交多个任务
for(int i=0;i<10;i++) {
System.out.println("提交任务"+i);
pool.execute(new Task(i));
}
执行结果
提交任务0
提交任务1
提交任务2
提交任务3
提交任务4
提交任务5
提交任务6
pool-1-thread-3开始执行任务5
pool-1-thread-2开始执行任务1
pool-1-thread-1开始执行任务0
main开始执行任务6
pool-1-thread-1已完成任务0
pool-1-thread-1开始执行任务2
pool-1-thread-2已完成任务1
main已完成任务6
提交任务7
pool-1-thread-2开始执行任务3
提交任务8
提交任务9
main开始执行任务9
pool-1-thread-3已完成任务5
pool-1-thread-3开始执行任务4
pool-1-thread-1已完成任务2
pool-1-thread-1开始执行任务7
main已完成任务9
pool-1-thread-2已完成任务3
pool-1-thread-2开始执行任务8
pool-1-thread-3已完成任务4
pool-1-thread-1已完成任务7
pool-1-thread-2已完成任务8
Process finished with exit code 0
结果解释:
pool-1-thread-1和pool-1-thread-2是核心线程,被任务0和任务1抢占,随后任务2,3,4进入阻塞队列,任务5则抢占非核心线程pool-1-thread-3,任务6因为无法进入线程池被调度线程池的mian执行;
提交任务0
提交任务1
提交任务2
提交任务3
提交任务4
提交任务5
提交任务6
pool-1-thread-3开始执行任务5
pool-1-thread-2开始执行任务1
pool-1-thread-1开始执行任务0
main开始执行任务6
核心线程的任务0被执行完,堵塞队列队头的任务2就会进入核心线程开始执行,阻塞队列有剩余空间,任务7被提交进入队列;任务1被执行完,堵塞队列队头的任务3就会进入核心线程开始执行,阻塞队列有剩余空间,任务8被提交进入队列;mian执行完任务6接着执行被线程池拒绝的任务9;非核心线程执行完任务5,堵塞队列队头的任务4就会进入非核心线程开始执行;
pool-1-thread-1已完成任务0
pool-1-thread-1开始执行任务2
pool-1-thread-2已完成任务1
main已完成任务6
提交任务7
pool-1-thread-2开始执行任务3
提交任务8
提交任务9
main开始执行任务9
pool-1-thread-3已完成任务5
pool-1-thread-3开始执行任务4
核心线程的任务2被执行完,堵塞队列队头的任务7就会进入核心线程开始执行,阻塞队列有剩余空间,但已经没有新的任务了;mian执行完任务9也没有新的任务了;核心线程的任务3被执行完,堵塞队列队头的任务8就会进入核心线程开始执行;非核心线程完成任务4;核心线程完成任务7和8
pool-1-thread-1已完成任务2
pool-1-thread-1开始执行任务7
main已完成任务9
pool-1-thread-2已完成任务3
pool-1-thread-2开始执行任务8
pool-1-thread-3已完成任务4
pool-1-thread-1已完成任务7
pool-1-thread-2已完成任务8
注意,任务的进入线程池并不一定顺序的!
ThreadPoolExecutor的参数
int corePoolSize : 核心线程数, 当任务提交进来时,当核心线程未被任务占满时,任务先进入核心线程
int maximumPoolSize : 线程池可容纳最多线程数
long keepAliveTime, TimeUnit unit : 当前线程数大于核心线程数时,非核心线程的等待被执行的等待时间
BlockingQueue<Runnable> workQueue : 阻塞队列,当核心线程被占满,新的任务会进入阻塞队列等待执行
RejectedExecutionHandler handler) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), handler) : 拒绝策略,阻塞队列已满或无法将新任务添加到线程池时应对方案
阻塞队列
ThreadPoolExecutor支持三种阻塞队列
new ArrayBlockingQueue<>(maxQueueSize); //基于数组实现的阻塞队列
new LinkedBlockingDeque<>(); //基于链表实现的阻塞队列
new SynchronousQueue<>(); //无缓冲的等待队列
SynchronousQueue没有容量,是无缓冲等待队列,必须等队内的任务被执行完,新的任务才能进队
拒绝策略
new ThreadPoolExecutor.AbortPolicy();//默认,队列满了丢任务抛出异常, 和DiscardPolicy的区别在于会抛出异常
new ThreadPoolExecutor.DiscardPolicy();//队列满了丢任务不异常 (超出maxinumPoolSize+BlockingQueue大小的任务会被丢弃)
new ThreadPoolExecutor.DiscardOldestPolicy();//将最早进入队列的任务删,之后再尝试加入队列
new ThreadPoolExecutor.CallerRunsPolicy();//如果添加到线程池失败,那么主线程会自己去执行该任务
简单来说,
AbortPolicy策略下,当线程池满后,被拒绝的新任务直接丢弃,且抛出异常,适用于系统被提交大量的业务时,过多的任务就会被丢弃且通过异常通知用户,比如某宝限额抢购时,未成功抢占到名额的订单无法提交成功且会通知你当前访问量太大balabala的,就是买不到啦。
DiscardPolicy则是丢弃新任务但不抛出异常,适用于无关紧要的冗余任务不需要提醒用户时。
DiscardOldestPolicy则是丢弃阻塞队列队头的数据再提交新任务,适用于有时效要求的任务,当太久未响应(任务排队排到头滞留了太久)已经失效可抛弃的情况。
CallerRunsPolicy则将新任务交给调度线程池的线程去执行(不抛弃不放弃任何一个任务)
线程有5个状态
new:初始化一个线程;
Runnable:线程处于可运行状态;
Running: 线程处于运行状态;
Blocked: 线程进入阻塞状态;
Dead: 线程死亡,被销毁;
线程池有5个状态
Running: 线程池接受新任务,并处理阻塞队列内的任务;
Shutdown: 线程池不接受新任务,但会继续处理线程的任务和阻塞队列内的任务;
Stop:线程池不接受新任务,且中断正在执行的线程和抛弃阻塞队列的任务;
Tidying:线程池的ctl计数器清零;
Terminated:线程池彻底终止;
四种常见的线程池
ExecutorService是Java提供的用于管理线程池的类,通过工具类Executors创建封装好的四种常用的线程池
newCachedThreadPool()
核心线程数为0,非核心线程数为Integer.MAX_VALUE,使用的是无缓冲的等待队列SynchronousQueue
来一个任务创建一个线程
//定义
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
/
ExecutorService pool = Executors.newCachedThreadPool();
newFixedThreadPool()
核心线程由用户指定,没有非核心线程(keepAlive时间也就为0了),使用的是基于链表实现的无界缓冲的阻塞队列LinkedBlockingQueue
//定义
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
///
ExecutorService threadPool = Executors.newFixedThreadPool(5);
newScheduledThreadPool()
核心线程由用户指定,非核心线程Integer.MAX_VALUE,阻塞队列使用的是DelayedWorkQueue类,这个类实现了从队列中延迟取节点,可以实现任务周期性进行
//定义
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
//ScheduledThreadPoolExecutor.class
public class ScheduledThreadPoolExecutor
extends ThreadPoolExecutor //继承ThreadPoolExecutor
implements ScheduledExecutorService {
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE,
DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
new DelayedWorkQueue());
}
static class DelayedWorkQueue extends AbstractQueue<Runnable>
implements BlockingQueue<Runnable>
ExecutorService threadPool = Executors.newScheduledThreadPool(5);
newSingleThreadExecutor()
核心线程为1,没有非核心线程(keepAlive时间也就为0了),使用的是基于链表实现的无界缓冲的阻塞队列LinkedBlockingQueue,同一时刻只有一个核心线程在工作,保证所有任务按照顺序进行
适合生成线程和消费线程速率较平衡的工作场景
//定义
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
///
ExecutorService threadPool = Executors.newSingleThreadExecutor();
Java多线程在操作系统底层是怎么实现的?
(当线程在用户空间内实现时(ULT),OS无法感知线程的存在,只能看到用户进程,ULT线程由用户进程自己管理)
Java线程属于KLT内核线程,内核通过调度器(Schedular)对线程进行调度,将线程的任务分配到指定的处理器。