理解线程池以及四种典型线程池(Java)

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)
: 拒绝策略,阻塞队列已满或无法将新任务添加到线程池时应对方案

 

新任务被提交给线程池后如何工作

注:图片来自https://blog.csdn.net/weixin_40271838/article/details/79998327?depth_1-utm_source=distribute.pc_relevant.none-task&utm_source=distribute.pc_relevant.none-task


举个例子

创建一个线程池:

    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)对线程进行调度,将线程的任务分配到指定的处理器。

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值