线程池与阻塞队列

1、概述

由于频繁的创建和销毁线程会消耗很多资源,因此线程池应运而生来去除频繁的创建与删除线程这一过程。

2、常见线程池

①、newSingleThreadExecutor
单一线程池,使用唯一的工作线程执行任务,保证所有任务按照指定顺序执行。

ExecutorService service = Executors.newSingleThreadExecutor();

底层实现是FinalizableDelegatedExecutorService包装的ThreadPoolExecutor实例

public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}

核心线程数和最大线程数都是1,存活时间为0,阻塞队列为LinkedBlockingQueue无界队列

②、newCachedThreadPool
创建一个可缓存线程池,如果线程池长度超过需要,可回收空闲线程,否则就创建新线程。

ExecutorService service = Executors.newCachedThreadPool();

底层返回的是ThreadPoolExecutor实例

public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}

核心线程数为0,当工作线程空闲超过60秒时工作线程将终止,终止后如果有新任务提交,则线程池会创建一个新的线程作为工作线程。

最大线程数为Integer.MAX_VALUE,由于使用的是SynchronousQueue同步队列,因此会在线程池中寻找空闲线程来执行任务,如果没有空闲线程则新建线程执行任务。

③、newFixedThreadPool
创建固定大小的线程池,可控制最大并发数,超出的任务会在队列中

ExecutorService service = Executors.newFixedThreadPool(5);

底层返回的也是ThreadPoolExecutor

public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}

核心线程数与最大线程数都是传进来的参数值大小,存活时间为0,采用无界队列。也就是说没提交一个任务,当工作线程数大于最大线程数时会将任务存到无界队列中,但是在线程池空闲时不会释放工作线程,还会占用一定的资源。

④、newScheduledThreadPool
创建一个指定大小的线程池,而且支持定时任务和周期任务

ScheduledExecutorService service = Executors.newScheduledThreadPool(5);
//两秒后每隔3秒执行一次
service.scheduleAtFixedRate(() -> System.out.println("123"),2,3,TimeUnit.SECONDS);

//延迟1秒后执行
service.schedule(() -> System.out.println("456"),1,TimeUnit.SECONDS);

其底层实际上还是使用了ThreadPoolExecutor。

底层创建了ScheduledThreadPoolExecutor实例

public ScheduledThreadPoolExecutor(int corePoolSize) {
    super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
          new DelayedWorkQueue());
}

核心线程数为传进来的参数,最大线程数为Integer.MAX_VALUE,时间为0。大致来说会创建一个大小固定的线程池,存活时间无限制,支持定时和周期任务,如果所有线程都处于繁忙状态任务将进入DelayedWorkQueue

3、ThreadPoolExecutor

上面四种线程池对ThreadPoolExecutor进行了包装,方便使用,但有些情况下是直接使用ThreadPoolExecutor,通过自己设置参数来达到目的。
构造函数

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          RejectedExecutionHandler handler) {
      this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
           Executors.defaultThreadFactory(), handler);
  }
  • corePoolSize,核心线程数,当有新任务提交时会进行下面的判断

    • 如果当前线程数小于corePoolSize,则新建线程处理任务,即使此时有空闲线程也不使用;
    • 如果当前线程数大于corePoolSize且小于maximumPoolSize,如果workQueue未满,则加入workQueue,否则新建线程处理此任务;
    • 如果corePoolSize 和 maximumPoolSize相同,当workQueue未满时放入workQueue,否则按照拒绝策略处理新任务;
    • 如果运行的线程数量大于等于maximumPoolSize,这时如果workQueue已经满了,则通过handler所指定的策略来处理任务。
  • workQueue

    • 直接切换,这种方式常用的队列是SynchronousQueue,newCachedThreadPool使用的就是这种队列。
    • 无界队列,基于LinkedBlockingQueue实现,此时maximumPoolSize不会起作用,它是一个基于链表结构的阻塞队列,此队列按FIFO (先进先出) 排序元素,吞吐量通常要高于ArrayBlockingQueue,Executors.newFixedThreadPool()使用了这个队列。
    • 有界队列,基于ArrayBlockingQueue实现,可将线程池大小限制在maximumPoolSize
  • keepAliveTime,空闲时间等待超过这个keepAliveTime时间才会被回收

  • handler,拒绝策略,分为下面几种

    • AbortPolicy,直接抛出异常,默认的拒绝策略;
    • CallerRunsPolicy,使用调用者所在的线程执行任务;
    • DiscardOldestPolicy,丢弃队列中最前面的任务,并将当前任务加入到队列中;
    • DiscardPolicy,直接丢弃任务

线程池处理流程如下图
在这里插入图片描述
线程池生命周期

  • RUNNING,能接受新任务,并且能处理阻塞队列中的任务;
  • SHUTDOWN,关闭状态,不接受新提交的任务,但可以继续执行队列中的任务,在RUNNING调用shutdown()方法会转到此状态;
  • STOP,不接受新任务,中断正在执行的任务,相当于调用了shutdownNow()方法;
  • TIDYING,此状态所有任务都终止,有效线程为0,在此状态时会自动调用terminated()方法进入TERMINATED状态。
  • TERMINATED,在执行terminated()后进入此状态,此状态什么也没做

线程池生命周期图
在这里插入图片描述

4、线程池是如何保证线程重复利用的

在execute()方法中调用了addWorker()方法,这是一个添加线程的方法,向线程池中添加的Runnable被包装成一个Worker对象,并在addWorker()方法里执行了start()方法,会调用Worker类的run()方法,Worker类的run方法又调用了runWorker()方法,runWorker()方法里面的while循环就是线程池中线程不被销毁的关键所在:while (task != null || (task = getTask()) != null) { },在Worker的run方法中,如果while一直执行下去,那么Worker这个继承了Runnable接口的线程就会一直执行下去,而我们知道线程池中任务的载体是Worker,如果Worker一直执行下去,就表示该载体可以一直存在,换的只是载体上我们通过execute()方法添加的Runnable任务而已。

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Java线程池阻塞队列是用来存储等待执行的任务据结构。当线程池中的线程都在执行任务时,新的任务会被放入阻塞队列中等待执行。当线程池中的线程空闲下来时,它们会从阻塞队列中取出任务并执行。 Java中常见的线程池阻塞队列有以下几种: 1. ArrayBlockingQueue:基于组结构实现的FIFO阻塞队列。在构造该阻塞队列时需要指定队列的容量。当队列已满时,若再次进行据写入操作,则线程将会进入阻塞,一直等待直到其他线程对元素进行消费。当队列为空时,对该队列的消费线程将会进入阻塞,直到有其他线程写入据[^2]。 2. LinkedBlockingQueue:基于链表结构实现的FIFO阻塞队列。该队列的容量可以选择性地指定,如果不指定,默认大小为Integer.MAX_VALUE。当队列已满时,写入操作将会被阻塞,直到有其他线程队列中取出元素。当队列为空时,读取操作将会被阻塞,直到有其他线程写入据。 3. SynchronousQueue:一个不存储元素的阻塞队列。每个插入操作必须等待另一个线程的移除操作,反之亦然。当线程尝试插入元素时,如果没有其他线程正在等待移除元素,则插入操作将会失败。当线程尝试移除元素时,如果没有其他线程正在等待插入元素,则移除操作将会失败。 这些阻塞队列Java线程池中起到了重要的作用,可以根据实际需求选择适合的阻塞队列来管理任务的执行。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值