Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >如何扩展和优化线程池?

如何扩展和优化线程池?

原创
作者头像
本人秃顶程序员
修改于 2019-04-29 02:02:20
修改于 2019-04-29 02:02:20
80700
代码可运行
举报
文章被收录于专栏:Java架构筑基Java架构筑基
运行总次数:0
代码可运行

多线程的软件设计方法确实可以最大限度的发挥现代多核处理器的计算能力,提高生产系统的吞吐量和性能。但是,如果一个系统同时创建大量线程,线程间频繁的切换上下文导致的系统开销将会拖慢整个系统。严重的甚至导致内存耗尽导致OOM异常。因此,在实际的生产环境中,线程的数量必须得到控制,盲目的创建大量新车对系统是有伤害的。

那么,怎么才能最大限度的利用CPU的性能,又能保持系统的稳定性呢?其中有一个方法就是使用线程池。

简而言之,在使用线程池后,创建线程便处理从线程池获得空闲线程,关闭线程变成了向池子归还线程。也就是说,提高了线程的复用。

而 JDK 在 1.5 之后为我提供了现成的线程池工具,我们今天就来学习看看如何使用他们

  1. Executors 线程池工厂能创建哪些线程池
  2. 如何手动创建线程池
  3. 如何扩展线程池
  4. 如何优化线程池的异常信息
  5. 如何设计线程池中的线程数量

一、Executors 线程池工厂能创建哪些线程池


先来一个最简单的线程池使用例子:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
  static class MyTask implements Runnable {

    @Override
    public void run() {
      System.out
          .println(System.currentTimeMillis() + ": Thread ID :" + Thread.currentThread().getId());
      try {
        Thread.sleep(1000);
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    }
  }

  public static void main(String[] args) {
    MyTask myTask = new MyTask();
    ExecutorService service1 = Executors.newFixedThreadPool(5);
    for (int i = 0; i < 10; i++) {
      service1.submit(myTask);
    }
    service1.shutdown();
  }

运行结果:

我们创建了一个线程池实例,并设置默认线程数量为5,并向线程池提交了10任务,分别打印当前毫秒时间和线程ID,从结果中,我们可以看到结果中有5个相同 id 的线程打印了毫秒时间。

这是最简单的例子。

接下来我们讲讲其他的线程创建方式。

  1. 固定线程池 ExecutorService service1 = Executors.newFixedThreadPool(5); 该方法返回一个固定线程数量的线程池。该线程池中的线程数量始终不变。当有一个新的任务提交时,线程池中若有空闲线程,则立即执行,若没有,则新的任务会被暂存在一个任务队列(默认无界队列 int 最大数)中,待有线程空闲时,便处理在任务队列中的任务。
  2. 单例线程池 ExecutorService service3 = Executors.newSingleThreadExecutor(); 该方法返回一个只有一个线程的线程池。若多余一个任务被提交到该线程池,任务会被保存在一个任务队列(默认无界队列 int 最大数)中,待线程空闲,按先入先出的顺序执行队列中的任务。
  3. 缓存线程池 ExecutorService service2 = Executors.newCachedThreadPool(); 该方法返回一个可根据实际情况调整线程数量的线程池,线程池的线程数量不确定,但若有空闲线程可以复用,则会优先使用可复用的线程,所有线程均在工作,如果有新的任务提交,则会创建新的线程处理任务。所有线程在当前任务执行完毕后,将返回线程池进行复用。
  4. 任务调用线程池 ExecutorService service4 = Executors.newScheduledThreadPool(2); 该方法也返回一个 ScheduledThreadPoolExecutor 对象,该线程池可以指定线程数量。

前3个线程的用法没什么差异,关键是第四个,虽然线程任务调度框架很多,但是我们仍然可以学习该线程池。如何使用呢?下面来个例子:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
class A {
    public static void main(String[] args) {
        ScheduledThreadPoolExecutor service4 = (ScheduledThreadPoolExecutor) Executors
                .newScheduledThreadPool(2);
        // 如果前面的任务没有完成,则调度也不会启动
        service4.scheduleAtFixedRate(new Runnable() {
            @Override
                  public void run() {
                try {
                    // 如果任务执行时间大于间隔时间,那么就以执行时间为准(防止任务出现堆叠)。
                    Thread.sleep(10000);
                    System.out.println(System.currentTimeMillis() / 1000);
                }
                catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            // initialDelay(初始延迟) 表示第一次延时时间 ; period 表示间隔时间
        }
        , 0, 2, TimeUnit.SECONDS);
        service4.scheduleWithFixedDelay(new Runnable() {
            @Override
                  public void run() {
                try {
                    Thread.sleep(5000);
                    System.out.println(System.currentTimeMillis() / 1000);
                }
                catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            // initialDelay(初始延迟) 表示延时时间;delay + 任务执行时间 = 等于间隔时间 period
        }
        , 0, 2, TimeUnit.SECONDS);
        // 在给定时间,对任务进行一次调度
        service4.schedule(new Runnable() {
            @Override
                  public void run() {
                System.out.println("5 秒之后执行 schedule");
            }
        }
        , 5, TimeUnit.SECONDS);
    }
}
}

上面的代码创建了一个 ScheduledThreadPoolExecutor 任务调度线程池,分别调用了3个方法,需要着重解释 scheduleAtFixedRate 和 scheduleWithFixedDelay 方法,这两个方法的作用很相似,唯一的区别就是他们执行人物的间隔时间的计算方式,前者时间间隔算法是根据指定的 period 时间和任务执行时间中取时间长的,后者取的是指定的 delay 时间 + 任务执行时间。如果同学们有兴趣,可以将上面的代码跑跑看。一样便能看出端倪。

好了,JDK 给我们封装了创建线程池的 4 个方法,但是,请注意,由于这些方法高度封装,因此,如果使用不当,出了问题将无从排查,因此,我建议,程序员应到自己手动创建线程池,而手动创建的前提就是高度了解线程池的参数设置。那么我们就来看看如何手动创建线程池。

二、如何手动创建线程池


下面是一个手动创建线程池的范本:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
  /**
   * 默认5条线程(默认数量,即最少数量),
   * 最大20线程(指定了线程池中的最大线程数量),
   * 空闲时间0秒(当线程池梳理超过核心数量时,多余的空闲时间的存活时间,即超过核心线程数量的空闲线程,在多长时间内,会被销毁),
   * 等待队列长度1024,
   * 线程名称[MXR-Task-%d],方便回溯,
   * 拒绝策略:当任务队列已满,抛出RejectedExecutionException
   * 异常。
   */
  private static ThreadPoolExecutor threadPool = new ThreadPoolExecutor(5, 20, 0L,
      TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(1024)
      , new ThreadFactoryBuilder().setNameFormat("My-Task-%d").build()
      , new AbortPolicy()
  );

我们看到,ThreadPoolExecutor 也就是线程池有 7 个参数,我们一起来好好看看:

  1. corePoolSize 线程池中核心线程数量
  2. maximumPoolSize 最大线程数量
  3. keepAliveTime 空闲时间(当线程池梳理超过核心数量时,多余的空闲时间的存活时间,即超过核心线程数量的空闲线程,在多长时间内,会被销毁)
  4. unit 时间单位
  5. workQueue 当核心线程工作已满,需要存储任务的队列
  6. threadFactory 创建线程的工厂
  7. handler 当队列满了之后的拒绝策略

前面几个参数我们就不讲了,很简单,主要是后面几个参数,队列,线程工厂,拒绝策略。

我们先看看队列,线程池默认提供了 4 个队列。

  1. 无界队列: 默认大小 int 最大值,因此可能会耗尽系统内存,引起OOM,非常危险。
  2. 直接提交的队列 : 没有容量,不会保存,直接创建新的线程,因此需要设置很大的线程池数。否则容易执行拒绝策略,也很危险。
  3. 有界队列:如果core满了,则存储在队列中,如果core满了且队列满了,则创建线程,直到maximumPoolSize 到了,如果队列满了且最大线程数已经到了,则执行拒绝策略。
  4. 先级队列:按照优先级执行任务。也可以设置大小。 楼主在自己的项目中使用了无界队列,但是设置了任务大小,1024。如果你的任务很多,建议分为多个线程池。不要把鸡蛋放在一个篮子里。

再看看拒绝策略,什么是拒绝策略呢?当队列满了,如何处理那些仍然提交的任务。JDK 默认有4种策略。

  1. AbortPolicy :直接抛出异常,阻止系统正常工作.
  2. CallerRunsPolicy : 只要线程池未关闭,该策略直接在调用者线程中,运行当前被丢弃的任务。显然这样做不会真的丢弃任务,但是,任务提交线程的性能极有可能会急剧下降。
  3. DiscardOldestPolicy: 该策略将丢弃最老的一个请求,也就是即将被执行的一个任务,并尝试再次提交当前任务.
  4. DiscardPolicy: 该策略默默地丢弃无法处理的任务,不予任何处理,如果允许任务丢失,我觉得这是最好的方案.

当然,如果你不满意JDK提供的拒绝策略,可以自己实现,只需要实现 RejectedExecutionHandler 接口,并重写 rejectedExecution 方法即可。

最后,线程工厂,线程池的所有线程都由线程工厂来创建,而默认的线程工厂太过单一,我们看看默认的线程工厂是如何创建线程的:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 /**
     * The default thread factory
     */
    static class DefaultThreadFactory implements ThreadFactory {
        private static final AtomicInteger poolNumber = new AtomicInteger(1);
        private final ThreadGroup group;
        private final AtomicInteger threadNumber = new AtomicInteger(1);
        private final String namePrefix;

        DefaultThreadFactory() {
            SecurityManager s = System.getSecurityManager();
            group = (s != null) ? s.getThreadGroup() :
                                  Thread.currentThread().getThreadGroup();
            namePrefix = "pool-" +
                          poolNumber.getAndIncrement() +
                         "-thread-";
        }

        public Thread newThread(Runnable r) {
            Thread t = new Thread(group, r,
                                  namePrefix + threadNumber.getAndIncrement(),
                                  0);
            if (t.isDaemon())
                t.setDaemon(false);
            if (t.getPriority() != Thread.NORM_PRIORITY)
                t.setPriority(Thread.NORM_PRIORITY);
            return t;
        }
    }

可以看到,线程名称为 pool- + 线程池编号 + -thread- + 线程编号 。设置为非守护线程。优先级为默认。

如果我们想修改名称呢?对,实现 ThreadFactory 接口,重写 newThread 方法即可。但是已经有人造好轮子了, 比如我们的例子中使用的 google 的 guaua 提供的 ThreadFactoryBuilder 工厂。可以自定义线程名称,是否守护,优先级,异常处理等等,功能强大。

三、如何扩展线程池


那么我们能扩展线程池的功能吗?比如记录线程任务的执行时间。实际上,JDK 的线程池已经为我们预留的接口,在线程池核心方法中,有2 个方法是空的,就是给我们预留的。还有一个线程池退出时会调用的方法。我们看看例子:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 /**
 * 如何扩展线程池,重写 beforeExecute, afterExecute, terminated 方法,这三个方法默认是空的。
 *
 * 可以监控每个线程任务执行的开始和结束时间,或者自定义一些增强。
 *
 * 在 Worker 的 runWork 方法中,会调用这些方法
 */
public class ExtendThreadPoolDemo {

  static class MyTask implements Runnable {

    String name;

    public MyTask(String name) {
      this.name = name;
    }

    @Override
    public void run() {
      System.out
          .println("正在执行:Thread ID:" + Thread.currentThread().getId() + ", Task Name = " + name);
      try {
        Thread.sleep(100);
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    }
  }

  public static void main(String[] args) throws InterruptedException {
    ExecutorService es = new ThreadPoolExecutor(5, 5, 0L, TimeUnit.MILLISECONDS,
        new LinkedBlockingQueue<>()) {
      @Override
      protected void beforeExecute(Thread t, Runnable r) {
        System.out.println("准备执行:" + ((MyTask) r).name);
      }

      @Override
      protected void afterExecute(Runnable r, Throwable t) {
        System.out.println("执行完成: " + ((MyTask) r).name);
      }

      @Override
      protected void terminated() {
        System.out.println("线程池退出");
      }
    };

    for (int i = 0; i < 5; i++) {
      MyTask myTask = new MyTask("TASK-GEYM-" + i);
      es.execute(myTask);
      Thread.sleep(10);

    }

    es.shutdown();
  }

}

我们重写了 beforeExecute 方法,也就是执行任务之前会调用该方法,而 afterExecute 方法则是在任务执行完毕后会调用该方法。还有一个 terminated 方法,在线程池退出时会调用该方法。执行结果是什么呢?

可以看到,每个任务执行前后都会调用 before 和 after 方法。相当于执行了一个切面。而在调用 shutdown 方法后则会调用 terminated 方法。

四、如何优化线程池的异常信息


如何优化线程池的异常信息? 在说这个问题之前,我们先说一个不容易发现的bug:

看代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public static void main(String[] args) throws ExecutionException, InterruptedException {
    ThreadPoolExecutor executor = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 0L,
            TimeUnit.MILLISECONDS, new SynchronousQueue<>());
    for (int i = 0; i < 5; i++) {
        executor.submit(new DivTask(100, i));
    }
}
static class DivTask implements Runnable {
    int a, b;
    public DivTask(int a, int b) {
        this.a = a;
        this.b = b;
    }
    @Override
        public void run() {
        double re = a / b;
        System.out.println(re);
    }
}

执行结果:

注意:只有4个结果,其中一个结果被吞没了,并且没有任何信息。为什么呢?如果仔细看代码,会发现,在进行 100 / 0 的时候肯定会报错的,但是却没有报错信息,令人头痛,为什么呢?实际上,如果你使用 execute 方法则会打印错误信息,当你使用 submit 方法却没有调用它的get 方法,异常将会被吞没,因为,如果发生了异常,异常是作为返回值返回的。

怎么办呢?我们当然可以使用 execute 方法,但是我们可以有另一种方式:重写 submit 方法,楼主写了一个例子,大家看一下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
static class TraceThreadPoolExecutor extends ThreadPoolExecutor {
    public TraceThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime,
            TimeUnit unit, BlockingQueue<Runnable> workQueue) {
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
    }
    @Override
        public void execute(Runnable command) {
        //      super.execute(command);
        super.execute(wrap(command, clientTrace(), Thread.currentThread().getName()));
    }
    @Override
        public Future<?> submit(Runnable task) {
        //      return super.submit(task);
        return super.submit(wrap(task, clientTrace(), Thread.currentThread().getName()));
    }
    private Exception clientTrace() {
        return new Exception("Client stack trace");
    }
    private Runnable wrap(final Runnable task, final Exception clientStack,
            String clientThreaName) {
        return new Runnable() {
            @Override
                    public void run() {
                try {
                    task.run();
                }
                catch (Exception e) {
                    e.printStackTrace();
                    clientStack.printStackTrace();
                    throw e;
                }
            }
        }
        ;
    }
}

我们重写了 submit 方法,封装了异常信息,如果发生了异常,将会打印堆栈信息。我们看看使用重写后的线程池后的结果是什么?

从结果中,我们清楚的看到了错误信息的原因:by zero!并且堆栈信息明确,方便排错。优化了默认线程池的策略。

五、如何设计线程池中的线程数量


线程池的大小对系统的性能有一定的影响,过大或者过小的线程数量都无法发挥最优的系统性能,但是线程池大小的确定也不需要做的非常精确。因为只要避免极大和极小两种情况,线程池的大小对性能的影响都不会影响太大,一般来说,确定线程池的大小需要考虑CPU数量,内存大小等因素,在《Java Concurrency in Practice》 书中给出了一个估算线程池大小的经验公式:

总结


好了,到这里,我们已经对如何使用线程池有了一个认识,这里,楼主建议大家手动创建线程池,这样对线程池中的各个参数可以有精准的了解,在对系统进行排错或者调优的时候有好处。比如设置核心线程数多少合适,最大线程数,拒绝策略,线程工厂,队列的大小和类型等等,也可以是G家的线程工厂自定义线程。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
java并发线程池---了解ThreadPoolExecutor就够了
总结:线程池的特点是,在线程的数量=corePoolSize后,仅任务队列满了之后,才会从任务队列中取出一个任务,然后构造一个新的线程,循环往复直到线程数量达到maximumPoolSize执行拒绝策略。
intsmaze-刘洋
2018/08/29
2.7K0
面试-线程池的成长之路
相信大家在面试过程中遇到面试官问线程的很多,线程过后就是线程池了。从易到难,都是这么个过程,还有就是确实很多人在工作中接触线程池比较少,最多的也就是创建一个然后往里面提交线程,对于一些经验很丰富的面试官来说,一下就可以问出很多线程池相关的问题,与其被问的晕头转向,还不如好好学习。此时不努力更待何时。
纯洁的微笑
2019/09/05
6090
并发编程之线程池的使用
并发编程之线程池的使用 转载自http://www.cnblogs.com/dolphin0520/p/3932921.html https://blog.csdn.net/wanghao_0206/article/details/76460877 Java中的ThreadPoolExecutor类 java.uitl.concurrent.ThreadPoolExecutor类是线程池中最核心的一个类,因此如果要透彻地了解Java中的线程池,必须先了解这个类。下面我们来看一下ThreadPoolExe
爱撒谎的男孩
2018/07/04
3600
线程池(面试常考)
这种创建方式对应就是ThreadPoolExecutor构造参数中的threadFactory参数,使用工厂对象提供的创建方式
终有救赎
2023/10/16
2480
线程池(面试常考)
由浅入深理解Java线程池及线程池的如何使用
前言 多线程的异步执行方式,虽然能够最大限度发挥多核计算机的计算能力,但是如果不加控制,反而会对系统造成负担。线程本身也要占用内存空间,大量的线程会占用内存资源并且可能会导致Out of Memory。即便没有这样的情况,大量的线程回收也会给GC带来很大的压力。 为了避免重复的创建线程,线程池的出现可以让线程进行复用。通俗点讲,当有工作来,就会向线程池拿一个线程,当工作完成后,并不是直接关闭线程,而是将这个线程归还给线程池供其他任务使用。 接下来从总体到细致的方式,来共同探讨线程池。 总体的架构 来看Exe
Janti
2018/04/10
7.8K0
由浅入深理解Java线程池及线程池的如何使用
Java多线程之细说线程池
前言   在认识线程池之前,我们需要使用线程就去创建一个线程,但是我们会发现有一个问题:    如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间。   那么有没有一种办法使得线程可以复用,就是执行完一个任务,并不被销毁,而是可以继续执行其他的任务?   在Java中可以通过线程池来达到这样的效果。今天我们就来详细讲解一下Java的线程池,首先我们从最核心的ThreadPoolExecutor类中的方法讲起,然后
用户1195962
2018/01/18
1.7K0
Java多线程之细说线程池
你了解线程池吗?
为了「防止在使用的时候创建连接和销毁的时间损耗」,于是选择在服务开启的时候就创建一部分连接供后续使用,使用完以后放入池中,形成复用,而出现的池的概念.
石的三次方
2021/01/05
4720
一文带你彻底弄懂线程池
虽然 Java 对线程的创建、中断、等待、通知、销毁、同步等功能提供了很多的支持,但是从操作系统角度来说,频繁的创建线程和销毁线程,其实是需要大量的时间和资源的。
Java极客技术
2023/12/12
1.3K0
一文带你彻底弄懂线程池
Juc并发编程12——2万字深入源码:线程池这篇真的讲解的透透的了
本文将介绍常见的线程池的使用方法,介绍线程池的参数、拒绝策略、返回参数获取以及定时调度。
半旧518
2022/10/26
1710
Juc并发编程12——2万字深入源码:线程池这篇真的讲解的透透的了
【Java】一文看懂Thread 线程池的 7 种创建方式、任务队列及自定义线程池(代码示例)
Java线程池是提高应用性能的关键组件。线程池通过预先创建并管理一组线程,可以显著减少因频繁创建和销毁线程而产生的资源消耗。本文将探讨Java线程池的基本概念、创建方法以及最佳实践。
程序员洲洲
2024/06/07
1.8K0
【Java】一文看懂Thread 线程池的 7 种创建方式、任务队列及自定义线程池(代码示例)
【JDK并发包基础】线程池详解
        为了更好的控制多线程,JDK提供了一套线程框架Executor来帮助程序员有效的进行线程控制。Java.util.concurrent 包是专为 Java并发编程而设计的包,它下有很多
我叫刘半仙
2018/04/16
1.4K0
【JDK并发包基础】线程池详解
线程池详解(通俗易懂超级好)「建议收藏」
【理解】线程池基本概念 【理解】线程池工作原理 【掌握】自定义线程池 【应用】java内置线程池 【应用】使用java内置线程池完成综合案例
全栈程序员站长
2022/11/01
5120
线程池详解(通俗易懂超级好)「建议收藏」
聊一聊Java中的线程池
为了避免系统频繁地创建和销毁线程,我们可以让创建的线程进行复用。其实和数据库连接池是一样的道理,为了避免每次数据库查询都重新建立和销毁数据库连接,我们可以使用数据库连接池维护一些数据库连接,让他们长期保持一个激活状态。当系统需要使用数据库时,并不是创建一个新的连接,而是从连接池中获得一个可用的连接。
beifengtz
2019/06/03
6430
聊一聊Java中的线程池
使用ExecutorService实现线程池
  当一个程序中创建了许多线程,并在任务结束后销毁,会给系统带来过度消耗资源,以及过度切换线程的危险,从而可能导致系统崩溃。为此我们应使用线程池来解决这个问题。
HUC思梦
2020/09/03
7220
使用ExecutorService实现线程池
线程池整理
一般在生产环境中,我们都不会直接new一个Thread,然后再去start(),因为这么做会不断频繁的创建线程,销毁线程,过大的线程会耗尽CPU和内存资源,大量的垃圾回收,也会给GC带来压力,延长GC停顿时间.
算法之名
2019/08/20
6080
java并发编程(4)--线程池的使用
转载:http://www.cnblogs.com/dolphin0520/p/3932921.html 一. java中的ThreadPoolExecutor类 java.util.concurrent.ThreadPoolExecutor类时线程池中最核心的一个类,因此如果要透彻的了解java中线程池,必须先了解这个类。下面看ThreadPoolExecutor类的具体实现源码: 在ThreadPoolExecutor类中提供了四个构造方法: public class ThreadPoolExecuto
Ryan-Miao
2018/03/13
7930
线程池之ThreadPoolExecutor使用
知道了各个参数的作用后,我们开始构造符合我们期待的线程池。首先看JDK给我们预定义的几种线程池:
用户6182664
2020/05/07
4280
Java线程池分享
分析一下: T1,T3是多线程本身的带来的开销,希望减少T1,T3所用的时间,从而减少T的时间。如果在程序中频繁的创建或销毁线程,这导致T1和T3在T中占有相当比例。显然这是突出了线程的弱点(T1,T3),而不是优点(并发性)。线程池技术正是关注如何缩短或调整T1,T3时间的技术,从而提高服务器程序性能的
每天学Java
2020/06/01
7540
Java线程池分享
【Java 并发编程】线程池机制 ( 线程池阻塞队列 | 线程池拒绝策略 | 使用 ThreadPoolExecutor 自定义线程池参数 )
个参数 : BlockingQueue<Runnable> workQueue ;
韩曙亮
2023/03/29
1.9K0
【Java 并发编程】线程池机制 ( 线程池阻塞队列 | 线程池拒绝策略 | 使用 ThreadPoolExecutor 自定义线程池参数 )
深入理解 Java 线程池
线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务。
静默虚空
2019/12/26
5070
深入理解 Java 线程池
相关推荐
java并发线程池---了解ThreadPoolExecutor就够了
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档