精华内容
下载资源
问答
  • 多线程线程数设置多少合适

    千次阅读 2020-06-30 01:15:04
    线程数的设置的最主要的目的是为了充分并合理地使用 CPU 和内存等资源,从而最大限度地提高程序的性能,因此让我们一起去探索吧! 首先要考虑到 CPU 核心数,那么在 Java 中如何获取核心线程数? 可以使用 Runtime....

    前沿

    大家都用过线程池,但是线程池数量设置为多少比较合理呢?

    线程数的设置的最主要的目的是为了充分并合理地使用 CPU 和内存等资源,从而最大限度地提高程序的性能,因此让我们一起去探索吧!

    首先要考虑到 CPU 核心数,那么在 Java 中如何获取核心线程数?

    可以使用 Runtime.getRuntime().availableProcessor() 方法来获取(可能不准确,作为参考)
    或者直接去服务器查看

    温故为什么使用线程

    场景

    如果有两个任务需要处理,一个任务A,一个任务B

    方案一:一个线程执行任务A和B,A执行完后,执行B
    方案二:两个线程A和B去执行任务A 和 B,同时进行

    哪个方案会快点?应该很多人会回答,肯定是方案二啊,多线程并行去处理任务A和B,肯定快啊。是这样吗?回答这个问题之前,先带着大家去回顾梳理一下。

    线程执行

    线程的执行,是由CPU进行调度的,一个CPU在同一时刻只会执行一个线程,我们看上去的线程A 和 线程B并发执行。

    为了让用户感觉这些任务正在同时进行,操作系统利用了时间片轮转的方式,CPU给每个任务都服务一定的时间,然后把当前任务的状态保存下来,在加载下一任务的状态后,继续服务下一任务。任务的状态保存及再加载,这段过程就叫做上下文切换。

    上下文切换过程是需要时间的;现在我们来看一下上面的问题,小伙伴们再看一下是哪个方案快呢?是不是有些小伙伴们会说方案一,因为不需要线程切换;方案二需要来回切换这两个线程,耗时会多点。

    小伙伴们心中此时是不是会有疑惑,那为什么会有多线程?先不急,再往下看。

    为什么要使用多线程

    小伙伴想想在我们真实业务中,我们是什么流程?
    在这里插入图片描述

    上图的流程:

    1、先发起网络请求

    2、Web服务器解析请求

    3、请求后端的数据库获取数据

    4、获取数据后,进行处理

    5、把处理结果放回给用户

    这个是我们处理业务的时候,常规的请求流程;我们看一下整个过程涉及到什么计算机处理。

    1、网络请求----->网络IO

    2、解析请求----->CPU

    3、请求数据库----->网络IO

    4、MySQL查询数据----->磁盘IO

    5、MySQL返回数据----->网络IO

    6、数据处理----->CPU

    7、返回数据给用户----->网络IO

    讲到这里,小伙伴们是不是感觉又不乱了,在真实业务中我们不单单会涉及CPU计算,还有网络IO和磁盘IO处理,这些处理是非常耗时的。如果一个线程整个流程是上图的流程,真正涉及到CPU的只有2个节点,其他的节点都是IO处理,那么线程在做IO处理的时候,CPU就空闲出来了,CPU的利用率就不高。

    小伙伴们现在知道多线程的用处了吧,对,就是为了提升CPU利用率。

    提升QPS/TPS

    衡量系统性能如何,主要指标系统的(QPS/TPS)

    QPS/TPS:每秒能够处理请求/事务的数量

    并发数:系统同时处理的请求/事务的数量

    响应时间:就是平均处理一个请求/事务需要时长

    QPS/TPS = 并发数/响应时间

    上面公式代表并发数越大,QPS就越大;所以很多人就会以为调大线程池,并发数就会大,也会提升QPS,所以才会出现一开始前言所说的,大多数人的误区。

    其实QPS还跟响应时间成反比,响应时间越大,QPS就会越小。

    虽然并发数调大了,就会提升QPS,但线程数也会影响响应时间,因为上面我们也提到了上下文切换的问题,那怎么设置线程数的呢?

    如何设置线程数

    那我们如何分配线程?我们提供一个公式:

    最佳线程数目 = ((线程等待时间+线程CPU时间)/线程CPU时间 )* CPU数目

    备注:这个公式也是前辈们分享的,当然之前看了淘宝前台系统优化实践的文章,和上面的公式很类似,不过在CPU数目那边,他们更细化了,上面的公式只是参考。不过不管什么公式,最终还是在生产环境中运行后,再优化调整。

    我们继续上面的任务,我们的服务器CPU核数为4核,一个任务线程cpu耗时为20ms,线程等待(网络IO、磁盘IO)耗时80ms,那最佳线程数目:( 80 + 20 )/20 * 4 = 20。也就是设置20个线程数最佳。

    从这个公式上面我们就得出,线程的等待时间越大,线程数就要设置越大,这个正好符合我们上面的分析,可提升CPU利用率。那从另一个角度上面说,线程数设置多大,是根据我们自身的业务的,需要自己去压力测试,设置一个合理的数值。

    基础常规标准

    在确认了核心数后,再去判断是 CPU 密集型任务还是 IO 密集型任务:
    CPU 密集型任务:
    比如像加解密,压缩、计算等一系列需要大量耗费 CPU 资源的任务,大部分场景下都是纯 CPU 计算。
    IO 密集型任务:
    比如像 MySQL 数据库、文件的读写、网络通信等任务,这类任务不会特别消耗 CPU 资源,但是 IO 操作比较耗时,会占用比较多时间。

    1、CPU密集型:操作内存处理的业务,一般线程数设置为:CPU核数 + 1 或者 CPU核数*2。核数为4的话,一般设置 5 或 8

    2、IO密集型:文件操作,网络操作,数据库操作,一般线程设置为:cpu核数 / (1-0.9),核数为4的话,一般设置 40
    在知道如何判断任务的类别后,让我们分两个场景进行讨论:

    CPU 密集型任务

    对于 CPU 密集型计算,多线程本质上是提升多核 CPU 的利用率,所以对于一个 8 核的 CPU,每个核一个线程,理论上创建 8 个线程就可以了。

    如果设置过多的线程数,实际上并不会起到很好的效果。此时假设我们设置的线程数量是 CPU 核心数的 2 倍,因为计算任务非常重,会占用大量的 CPU 资源,所以这时 CPU 的每个核心工作基本都是满负荷的,而我们又设置了过多的线程,每个线程都想去利用 CPU 资源来执行自己的任务,这就会造成不必要的上下文切换,此时线程数的增多并没有让性能提升,反而由于线程数量过多会导致性能下降。

    因此,对于 CPU 密集型的计算场景,理论上线程的数量 = CPU 核数就是最合适的,不过通常把线程的数量设置为CPU 核数 +1,会实现最优的利用率。即使当密集型的线程由于偶尔的内存页失效或其他原因导致阻塞时,这个额外的线程也能确保 CPU 的时钟周期不会被浪费,从而保证 CPU 的利用率。

    如下图就是在一个 8 核 CPU 的电脑上,通过修改线程数来测试对 CPU 密集型任务(素数计算)的性能影响。
    在这里插入图片描述

    可以看到线程数小于 8 时,性能是很差的,在线程数多于处理器核心数对性能的提升也很小,因此可以验证公式还是具有一定适用性的。

    除此之外,我们最好还要同时考虑在同一台机器上还有哪些其他会占用过多 CPU 资源的程序在运行,然后对资源使用做整体的平衡。

    IO 密集型任务

    对于 IO 密集型任务最大线程数一般会大于 CPU 核心数很多倍,因为 IO 读写速度相比于 CPU 的速度而言是比较慢的,如果我们设置过少的线程数,就可能导致 CPU 资源的浪费。而如果我们设置更多的线程数,那么当一部分线程正在等待 IO 的时候,它们此时并不需要 CPU 来计算,那么另外的线程便可以利用 CPU 去执行其他的任务,互不影响,这样的话在任务队列中等待的任务就会减少,可以更好地利用资源。

    对于 IO 密集型计算场景,最佳的线程数是与程序中 CPU 计算和 IO 操作的耗时比相关的,《Java并发编程实战》的作者 Brain Goetz 推荐的计算方法如下:

    线程数 = CPU 核心数 * (1 + IO 耗时/ CPU 耗时)

    通过这个公式,我们可以计算出一个合理的线程数量,如果任务的平均等待时间长,线程数就随之增加,而如果平均工作时间长,也就是对于我们上面的 CPU 密集型任务,线程数就随之减少。可以采用 APM 工具统计到每个方法的耗时,便于计算 IO 耗时和 CPU 耗时。

    在这里引用Java并发编程实战中的图,方便大家更容易理解:
    在这里插入图片描述

    还有一派的计算方式是《Java虚拟机并发编程》中提出的:

    线程数 = CPU 核心数 / (1 - 阻塞系数)

    其中计算密集型阻塞系数为 0,IO 密集型阻塞系数接近 1,一般认为在 0.8 ~ 0.9 之间。比如 8 核 CPU,按照公式就是 2 / ( 1 - 0.9 ) = 20 个线程数
    在这里插入图片描述

    上图是 IO 密集型任务的一个测试,是在双核处理器上开不同的线程数(从 1 到 40)来测试对程序性能的影响,可以看到线程池数量达到 20 之后,曲线逐渐水平,说明开再多的线程对程序的性能提升也毫无帮助。

    太少的线程数会使得程序整体性能降低,而过多的线程也会消耗内存等其他资源,所以如果想要更准确的话,可以进行压测,监控 JVM 的线程情况以及 CPU 的负载情况,根据实际情况衡量应该创建的线程数,合理并充分利用资源。

    同时,有很多线程池的应用,比如 Tomcat、Redis、Jdbc 等,每个应用设置的线程数也是不同的,比如 Tomcat 为流量入口,那么线程数的设置可能就要比其他应用要大。

    总结

    通过对线程数设置的探究,我们可以得知线程数的设置首先和 CPU 核心数有莫大关联,除此之外,我们需要根据任务类型的不同选择对应的策略,具体的怎么设置要根据业务上需要/服务器的环境/QPS/TPS等指标等等有关系。
    线程的平均工作时间所占比例越高,就需要越少的线程;线程的平均等待时间所占比例越高,就需要越多的线程;增强cpu的使用率。
    针对不同的程序,进行对应的实际测试就可以得到最合适的选择。

    展开全文
  • ThreadPoolExecutor是JDK中的线程池实现,这个类实现了一个线程池需要的各个方法,它实现了任务提交、线程管理、监控等等方法。 来看看ThreadPoolExecutor类的构造方法源码,其他创建线程池的方法最终都会导向这个...

    源码简介

    ThreadPoolExecutor是JDK中的线程池实现,这个类实现了一个线程池需要的各个方法,它提供了任务提交、线程管理、监控等方法。

    下面是ThreadPoolExecutor类的构造方法源码,其他创建线程池的方法最终都会导向这个构造方法,共有7个参数:corePoolSize、maximumPoolSize、keepAliveTime、unit、workQueue、threadFactory、handler。

        public ThreadPoolExecutor(int corePoolSize,
                                  int maximumPoolSize,
                                  long keepAliveTime,
                                  TimeUnit unit,
                                  BlockingQueue<Runnable> workQueue,
                                  ThreadFactory threadFactory,
                                  RejectedExecutionHandler handler) {
            if (corePoolSize < 0 ||
                maximumPoolSize <= 0 ||
                maximumPoolSize < corePoolSize ||
                keepAliveTime < 0)
                throw new IllegalArgumentException();
            if (workQueue == null || threadFactory == null || handler == null)
                throw new NullPointerException();
            this.acc = System.getSecurityManager() == null ?
                    null :
                    AccessController.getContext();
            this.corePoolSize = corePoolSize;
            this.maximumPoolSize = maximumPoolSize;
            this.workQueue = workQueue;
            this.keepAliveTime = unit.toNanos(keepAliveTime);
            this.threadFactory = threadFactory;
            this.handler = handler;
        }
    

    这些参数都通过volatile修饰:

    public class ThreadPoolExecutor extends AbstractExecutorService {
        private final BlockingQueue<Runnable> workQueue;
        private volatile ThreadFactory threadFactory;
        private volatile RejectedExecutionHandler handler;
        private volatile long keepAliveTime;
        // 是否允许核心线程被回收
        private volatile boolean allowCoreThreadTimeOut;
        private volatile int corePoolSize;
        private volatile int maximumPoolSize;
    }

    corePoolSize:核心线程数

    线程池维护的最小线程数量,核心线程创建后不会被回收(注意:设置allowCoreThreadTimeout=true后,空闲的核心线程超过存活时间也会被回收)。

    大于核心线程数的线程,在空闲时间超过keepAliveTime后会被回收。

    线程池刚创建时,里面没有一个线程,当调用 execute() 方法添加一个任务时,如果正在运行的线程数量小于corePoolSize,则马上创建新线程并运行这个任务。

    maximumPoolSize:最大线程数

    线程池允许创建的最大线程数量。

    当添加一个任务时,核心线程数已满,线程池还没达到最大线程数,并且没有空闲线程,工作队列已满的情况下,创建一个新线程并执行。

    keepAliveTime:空闲线程存活时间

    当一个可被回收的线程的空闲时间大于keepAliveTime,就会被回收。

    可被回收的线程:

    1. 设置allowCoreThreadTimeout=true的核心线程。
    2. 大于核心线程数的线程(非核心线程)。

    unit:时间单位

    keepAliveTime的时间单位:

    TimeUnit.NANOSECONDS
    TimeUnit.MICROSECONDS
    TimeUnit.MILLISECONDS // 毫秒
    TimeUnit.SECONDS
    TimeUnit.MINUTES
    TimeUnit.HOURS
    TimeUnit.DAYS

    workQueue:工作队列

    存放待执行任务的队列:当提交的任务数超过核心线程数大小后,再提交的任务就存放在工作队列,任务调度时再从队列中取出任务。它仅仅用来存放被execute()方法提交的Runnable任务。工作队列实现了BlockingQueue接口。

    JDK默认的工作队列有五种:

    1. ArrayBlockingQueue 数组型阻塞队列:数组结构,初始化时传入大小,有界,FIFO,使用一个重入锁,默认使用非公平锁,入队和出队共用一个锁,互斥。
    2. LinkedBlockingQueue 链表型阻塞队列:链表结构,默认初始化大小为Integer.MAX_VALUE,有界(近似无解),FIFO,使用两个重入锁分别控制元素的入队和出队,用Condition进行线程间的唤醒和等待。
    3. SynchronousQueue 同步队列:容量为0,添加任务必须等待取出任务,这个队列相当于通道,不存储元素。
    4. PriorityBlockingQueue 优先阻塞队列:无界,默认采用元素自然顺序升序排列。
    5. DelayQueue 延时队列:无界,元素有过期时间,过期的元素才能被取出。

    threadFactory:线程工厂

    创建线程的工厂,可以设定线程名、线程编号等。

    默认线程工厂:

        /**
         * 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;
            }
        }

    handler:拒绝策略

    当线程池线程数已满,并且工作队列达到限制,新提交的任务使用拒绝策略处理。可以自定义拒绝策略,拒绝策略需要实现RejectedExecutionHandler接口。

    JDK默认的拒绝策略有四种:

    1. AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
    2. DiscardPolicy:丢弃任务,但是不抛出异常。可能导致无法发现系统的异常状态。
    3. DiscardOldestPolicy:丢弃队列最前面的任务,然后重新提交被拒绝的任务。
    4. CallerRunsPolicy:由调用线程处理该任务。

    默认拒绝策略:

        /**
         * The default rejected execution handler
         */
        private static final RejectedExecutionHandler defaultHandler = new AbortPolicy();
    
        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());
            }
        }

    线程池的执行流程

    自定义线程池工具

    import java.util.concurrent.*;
    import java.util.concurrent.atomic.AtomicInteger;
    
    /**
     * 线程池工厂工具
     *
     * @author 向振华
     * @date 2021/04/11 10:24
     */
    public class ThreadPoolFactory {
    
        /**
         * 生成固定大小的线程池
         *
         * @param threadName 线程名称
         * @return 线程池
         */
        public static ExecutorService createFixedThreadPool(String threadName) {
            AtomicInteger threadNumber = new AtomicInteger(0);
            return new ThreadPoolExecutor(
                    // 核心线程数
                    desiredThreadNum(),
                    // 最大线程数
                    desiredThreadNum(),
                    // 空闲线程存活时间
                    60L,
                    // 空闲线程存活时间单位
                    TimeUnit.SECONDS,
                    // 工作队列
                    new ArrayBlockingQueue<>(1024),
                    // 线程工厂
                    new ThreadFactory() {
                        @Override
                        public Thread newThread(Runnable r) {
                            return new Thread(r, threadName + "-" + threadNumber.getAndIncrement());
                        }
                    },
                    // 拒绝策略
                    new RejectedExecutionHandler() {
                        @Override
                        public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
                            if (!executor.isShutdown()) {
                                try {
                                    //尝试阻塞式加入任务队列
                                    executor.getQueue().put(r);
                                } catch (Exception e) {
                                    //保持线程的中断状态
                                    Thread.currentThread().interrupt();
                                }
                            }
                        }
                    });
        }
    
        /**
         * 理想的线程数,使用 2倍cpu核心数
         */
        public static int desiredThreadNum() {
            return Runtime.getRuntime().availableProcessors() * 2;
        }
    }

     

    展开全文
  • Java线程池核心线程数与最大线程数的区别

    万次阅读 多人点赞 2020-06-23 15:20:45
    corePoolSize:核心线程数;maximunPoolSize:最大线程数 每当有新的任务到线程池时, 第一步: 先判断线程池中当前线程数量是否达到了corePoolSize,若未达到,则新建线程运行此任务,且任务结束后将该线程保留在...

    线程池策略

    corePoolSize:核心线程数;maximunPoolSize:最大线程数
    每当有新的任务到线程池时,
    第一步: 先判断线程池中当前线程数量是否达到了corePoolSize,若未达到,则新建线程运行此任务,且任务结束后将该线程保留在线程池中,不做销毁处理,若当前线程数量已达到corePoolSize,则进入下一步;
    第二步: 判断工作队列(workQueue)是否已满,未满则将新的任务提交到工作队列中,满了则进入下一步;
    第三步: 判断线程池中的线程数量是否达到了maxumunPoolSize,如果未达到,则新建一个工作线程来执行这个任务,如果达到了则使用饱和策略来处理这个任务。注意: 在线程池中的线程数量超过corePoolSize时,每当有线程的空闲时间超过了keepAliveTime,这个线程就会被终止。直到线程池中线程的数量不大于corePoolSize为止。
    (由第三步可知,在一般情况下,Java线程池中会长期保持corePoolSize个线程。)

    饱和策略

    当工作队列满且线程个数达到maximunPoolSize后所采取的策略
    AbortPolicy:默认策略;新任务提交时直接抛出未检查的异常RejectedExecutionException,该异常可由调用者捕获。
    CallerRunsPolicy:既不抛弃任务也不抛出异常,使用调用者所在线程运行新的任务。
    DiscardPolicy:丢弃新的任务,且不抛出异常。
    DiscardOldestPolicy:调用poll方法丢弃工作队列队头的任务,然后尝试提交新任务
    自定义策略:根据用户需要定制。

    展开全文
  • Java线程池的核心线程数和最大线程数

    千次阅读 多人点赞 2021-03-14 19:24:35
    //队列最大容量 //当提交的任务个大于QueueCapacity,就需要设置该参数,但spring提供的都不太满足业务场景,可以自定义一个,也可以注意不要超过QueueCapacity即可 taskExecutor.setRejectedExecutionHandler(new...

    Java的线程池就像是一个花瓶容器。

    而把任务提交给线程池就像是把小球塞进花瓶。

    整个过程就像下面这个有趣的动画:

    下面我们先来了解一下Java线程池的参数。

    希望看完这篇文章后, 再提起线程池的时候, 你脑海首先出现的, 会是一个花瓶 : )


    线程池的参数意义

    Java线程池的构造函数如下:

    public ThreadPoolExecutor(
      int corePoolSize,
      int maximumPoolSize,
      long keepAliveTime,
      TimeUnit unit,
      BlockingQueue<Runnable> workQueue,
      ThreadFactory threadFactory,
      RejectedExecutionHandler handler) {
        //...
    }

    线程池有这么几个重要的参数:

    • corePoolSize=> 线程池里的核心线程数量
    • maximumPoolSize=> 线程池里允许有的最大线程数量
    • keepAliveTime=> 空闲线程存活时间
    • unit=> keepAliveTime的时间单位,比如分钟,小时等
    • workQueue=> 缓冲队列
    • threadFactory=> 线程工厂用来创建新的线程放入线程池
    • handler=> 线程池拒绝任务的处理策略,比如抛出异常等策略

    线程池大体的原理就是这样的:corePoolSize ->queue -> maxPoolSzie , 吧啦吧啦......

    那么现在重点来了, 这堆参数解释不看源码真的搞不懂怎么办?

    或者你看懂了这些参数的文字解析,但是到用的时候总是记不住怎么办?

    或者我们来一组实际参数,你能理解这代表的含义吗?

    corePoolSize:1
    mamximumPoolSize:3
    keepAliveTime:60s
    workQueue:ArrayBlockingQueue,有界阻塞队列,队列大小是4
    handler:默认的策略,抛出来一个ThreadPoolRejectException

    别慌,我们可以把线程池的参数做成花瓶的参数,这样一来很多东西就不言自明了。

    线程池的参数可视化

    我们回到前面所说的花瓶。

    这个花瓶由 瓶口 、 瓶颈 、 瓶身 三个部分组成。

    这三个部分分别对应着线程池的三个参数:maximumPoolSize, workQueue,corePoolSize。

    线程池里的线程,我用一个红色小球表示,每来一个任务,就会生成一个小球:

    而核心线程,也就是正在处理中的任务,则用灰色的虚线小球表示 (目前第一版动画先这样简陋点吧......)

    于是画风就变成了这样,“花瓶”有这么几个重要的参数:

    • corePoolSize=> 瓶身的容量
    • maximumPoolSize=> 瓶口的容量
    • keepAliveTime=> 红色小球的存活时间
    • unit=> keepAliveTime的时间单位,比如分钟,小时等
    • workQueue=> 瓶颈,不同类型的瓶颈容量不同
    • threadFactory=> 你投递小球进花瓶的小手 (线程工厂)
    • handler=> 线程池拒绝任务的处理策略,比如小球被排出瓶外

    如果往这个花瓶里面放入很多小球时(线程池执行任务);

    瓶身 (corePoolSize) 装不下了, 就会堆积到 瓶颈 (queue) 的位置;

    瓶颈还是装不下, 就会堆积到 瓶口 (maximumPoolSize);

    直到最后小球从瓶口溢出。

    还记得上面提到的那一组实际参数吗,代表的花瓶大体上是如下图这样的:

    那么参数可视化到底有什么实际意义呢?


    3 阿里的规范

    首先我们来看阿里开发手册中对于 Java 线程池的使用规范:

    为什么规范中提及的四种线程会导致OOM呢?

    我们看看这四种线程池的具体参数,然后再用花瓶动画演示一下导致OOM的原因。

    线程池FixedThreadPool

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

    我们关心的参数如下

    corePoolSize:nThreads
    mamximumPoolSize:nThreads
    workQueue:LinkedBlockingQueue

    FixedThreadPool表示的花瓶就是下图这样子:

    线程池SingleThreadPool:

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

    我们关心的参数如下

    corePoolSize:1
    mamximumPoolSize:1
    workQueue:LinkedBlockingQueue

    SingleThreadPool表示的花瓶就是下图这样子:


    虽然两个线程池的样子没什么差异,但是这里我们发现了一个问题:

    为什么 FixedThreadPool 和 SingleThreadPool 的 corePoolSize和mamximumPoolSize 要设计成一样的?

    回答这个问题, 我们应该关注一下线程池的 workQueue 参数。

    线程池FixedThreadPool和SingleThreadPool 都用到的阻塞队列 LinkedBlockingQueue。

    LinkedBlockingQueue

    The capacity, if unspecified, is equal to {@link Integer#MAX_VALUE}. Linked nodes are dynamically created upon each insertion unless this would bring the queue above capacity.

    从LinkedBlockingQueue的源码注释中我们可以看到, 如果不指定队列的容量, 那么默认就是接近无限大的。

    从动画可以看出, 花瓶的瓶颈是会无限变长的, 也就是说不管瓶口容量设计得多大, 都是没有作用的!

    所以不管线程池FixedThreadPool和SingleThreadPool 的mamximumPoolSize 等于多少, 都是不生效的!


    线程池CachedThreadPool

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

    我们关心的参数如下

    corePoolSize:0
    mamximumPoolSize:Integer.MAX_VALUE
    workQueue:SynchronousQueue

    表示的花瓶就是下图这样子:

    这里我们由发现了一个问题:

    为什么CachedThreadPool的mamximumPoolSize要设计成接近无限大的?

    回答这个问题, 我们再看一下线程池CachedThreadPool的 workQueue 参数:SynchronousQueue。

    SynchronousQueue

    来看SynchronousQueue的源码注释:

    A synchronous queue does not have any internal capacity, not even a capacity of one.

    从注释中我们可以看到, 同步队列可以认为是容量为0。

    所以如果mamximumPoolSize不设计得很大, 就很容易导致溢出。

    但是瓶口设置得太大,堆积的小球太多,又会导致OOM(内存溢出)。

    线程池ScheduledThreadPool

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

    我们关心的参数如下

    corePoolSize:corePoolSize
    mamximumPoolSize:Integer.MAX_VALUE
    workQueue:DelayedWorkQueue

    可以看到, 这里出现了一个新的队列 workQueue:DelayedWorkQueue

    DelayedWorkQueue

    DelayedWorkQueue 是无界队列, 基于数组实现, 队列的长度可以扩容到 Integer.MAX_VALUE。

    同时ScheduledThreadPool的 mamximumPoolSize 也是接近无限大的。

    可以想象得到,ScheduledThreadPool就是史上最强花瓶, 极端情况下长度已经突破天际了!

    到这里, 相信大家已经明白, 为什么这四种线程会导致OOM了。

    怎么感觉这四种线程还真是名副其实的“花瓶”呢 :)


    后续

    目前花瓶动画还只是粗略的版本, 有部分瑕疵是不可避免的, 根据二八定律, 我的主要想法大体上是先做出来了,剩下的细节再慢慢补。

    目前只体现了线程池的三个参数。

    如果现在加入参数 keepAliveTime, 那么动画又会有什么效果的呢?

    敬请期待后续更新的文章。


    可视化的意义

    有很多人或许会认为, 学习个线程池, 还要做什么动画, 这不是走偏了吗?

    引用大神的一句话回答这个问题:

    Data visualization knowledge is not necessary -- just the desire to spread some knowledge.

    —— Ben Johnson

    数据可视化确实不是必需的, 但是有时候我们仅仅只是渴望给大家分享一些知识。

    而且在这个分享的过程中, 动画会让你做出更多的思考:

    思考动画怎么才能符合真实场景的效果。

    比如当我们开始思考,动画中花瓶颈部的长度变化,以及DelayedWorkQueue队列容量的变化,这两者如何才能对应的上时,于是不可避免的, 我们会开始研究起DelayedWorkQueue的扩容方式

    甚至每一种队列都可以单独展开做成更加细化的动画。

    而想要做好这些动画, 又要开始研究不同队列的源码了, 有需求才有动力!

    /**
     * 简单配置示例
     */
    
    //获取当前机器的核数
    public static final int cpuNum = Runtime.getRuntime().availableProcessors();
    
    @Override
        public Executor getAsyncExecutor() {
            ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
            taskExecutor.setCorePoolSize(cpuNum);//核心线程大小
            taskExecutor.setMaxPoolSize(cpuNum * 2);//最大线程大小
            taskExecutor.setQueueCapacity(500);//队列最大容量
            //当提交的任务个数大于QueueCapacity,就需要设置该参数,但spring提供的都不太满足业务场景,可以自定义一个,也可以注意不要超过QueueCapacity即可
            taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
            taskExecutor.setWaitForTasksToCompleteOnShutdown(true);
            taskExecutor.setAwaitTerminationSeconds(60);
            taskExecutor.setThreadNamePrefix("BCarLogo-Thread-");
            taskExecutor.initialize();
            return taskExecutor;
        }
    
    

     

    展开全文
  • 前提说明 为了确保服务不会被过多的http长连接压垮,我们需要对tomcat设定个最大连接,超过这个...达到保护自己的同时起到连接负载均衡的作用。 动手去做 一开始根据故障todoList提供的参数MaxKeepAliveReques...
  • java线程池合理设置最大线程数和核心线程数

    千次阅读 热门讨论 2020-10-10 17:13:31
    这个时候就需要使用多线程去处理。 一开始是这么配置的: @Configuration @EnableAsync(proxyTargetClass = true)//利用@EnableAsync注解开启异步任务支持 @ComponentScan({"com.ctfojt.auditbcarslogo.service"}) /...
  • 最大进程线程数 连接数

    千次阅读 2018-10-07 18:03:02
    ======================== ========================================================= ==============================================...吞吐量(TPS)、QPS、并发、响应时间(RT)概念 开发的原因,需要对...
  • 摘自: Java线程池的核心线程数和最大线程数总是容易混淆怎么办
  • 一 cpu个数、核数、线程数的关系 cpu个数:是指物理上,也及硬件...线程数:是同一时刻设备能并行执行的程序个数,线程数=cpu个数 * 核数 二 cpu线程数和Java多线程 首先明白几个概念: (1) 单个cpu线程在同...
  • Linux服务器 线程数和系统线程数

    千次阅读 2018-11-07 10:02:00
    1.查看服务器系统允许的最大线程数 ulimit -a 2. 修改配置文件 vi /etc/security/limits.d/90-nproc.conf 可以修改允许最大的线程数 3.查看当前系统比较好资源的线程 top -H 4.ps -ef|grep tomcat (查看tomcat...
  • Nachos实验实现线程id、限制线程数和更改调度算法(按优先级调度)
  • CPU核心数与线程数详解

    万次阅读 2020-03-29 21:58:42
    CPU就是中央处理单元,物理CPU就是CPU的硬件个(socket)。 核心: 一开始,每个物理 cpu 上只有一个核心(a single core),对操作系统而言,也就是同一时刻只能运行一个进程/线程。 为了提高性能,cpu ...
  • springboot内置tomcat调优并发线程数

    千次阅读 2020-12-03 11:47:57
    目录前言参数线程池核心线程数线程池最大线程数请求最大连接数accept-counttomcat线程池处理机制总结 前言 本文解析springboot内置tomcat调优并发线程数的一些参数,并结合源码进行分析 参数 线程池核心线程数 ...
  • 多线程-线程池(队列-最大线程数-核心线程数)

    千次阅读 热门讨论 2020-01-10 16:08:11
    java 多线程: 一般通过继承Thread类,实现Runnable接口,实现Callable接口,以及线程池。 这里主要是讲解线程池: 通过线程池主要是创建以下4种类型的线程池。 工具类 : Executors ExecutorService ...
  • 实际上并不能使用到多个CPU,那为什么官方的默认设置线程池中最大线程数会与CPU数相关? concurrent.futures的文档: > Changed in version 3.5: If max_workers is None or not given, it will default to **...
  • 一、CPU和运行内存的关系 CPU是负责运算和处理的,内存是交换数据的,没有内存,CPU就没法接收到数据...二、CPU个数、核心数、线程数 对cpu的描述有这几种:“双核”、“双核四线程”、“四核”、“四核四线程”、...
  • 往往在生产环境中,工作线程数,设置小了,无法充分利用CPU资源,性能会下降。设置大,线程上下文切换过于频繁(可以关注一下协程,如java库提供的Quasar fiber轻量级线程、kilim以及kotlin语言支持的协程。java线程...
  • 1.线程池和CPU核心的关系 一般说来,大家认为线程池的大小经验值应该这样设置:(其中N为CPU processors的个数) (1)如果是CPU密集型应用,则线程池大小设置为N+1(或者是N),线程的应用场景:主要是复杂算法 (2...
  • Docker环境undertow线程数不足问题探究

    千次阅读 2019-06-14 18:55:36
    Docker环境undertow线程数不足问题探究 背景 上篇Docker环境Spring Boot应用大量http请求超时,我们找到大量http请求超时原因:undertow的工作线程不足。 留下一些疑问:undertow默认配置是怎样的?为什么其他微服务...
  • 一, Sentinel基于并发线程数流控 采用基于线程数的限流模式后,我们不需要再显式地去进行线程池隔离,Sentinel 会控制访问该资源的线程数,超出的请求直接拒绝,直到堆积的线程处理完成。相当于是以资源为维度, ...
  • 查看java项目线程运行情况,以及总线程数 观看tomcat线程或者其他java程序线程数量以及运行情况,可以查看 jdk/bin/jvisualvm.exe当前文件是用来监控线程运行信息 有时候系统报异常:如下: java.lang....
  • tomcat设置线程数

    万次阅读 2019-02-28 10:54:11
    查看Tomcat线程数 1、Tomcat默认线程数200 2、修改server.xml文件,增加maxThreads、minSpareThreads、maxSpareThreads、acceptCount 3、参数解释 maxThreads=“1000” 最大并发数 minSpareThreads=“100”///初始...
  • 根据线程数设置公式 最大线程数就是性能最高线程数 因为此时性能已经是最高,再设置比他大的线程数反而性能变低,没有意义 估算最大流量,合理设置阻塞队列长度 核心线程数也是基于性能考虑 估算...
  • 当通过hystrix.threadpool.default.coreSize设置核心线程数量时 创建线程池时核心线程数和最大线程数都使用的它 当执行feign逻辑时 会判断当前线程数是否小于最大线程数 所以每次都会新建一个线程来执行网络请求 当...
  • python多线程semaphore实现线程数控制

    千次阅读 2019-01-27 16:23:26
    前面写过一篇关于python多线程的实现的文章, 但是效果不是最佳的,写法也不是很好。通过网上学习,也了解到了semaphore这个东西。 百度给的解释:Semaphore是一种在多线程环境下使用的设施,该设施负责协调各个...
  • 创建线程池有个CorePoolSize参数,指的是核心线程数,该参数应该设置为多大呢? CPU密集型 一般公式:CPU核数 + 1 通过以下代码动态获取CPU核数: Runtime.getRuntime().availableProcessors() IO密集型 (1)...
  • 线程池动态调整线程数

    千次阅读 2019-09-09 16:12:08
    领导要动态实时调整任务的线程数,这真的是个什么烂需求,线程数不是应该根据cpu资源来评估调的最优值吗,但是领导既然说了,硬着头皮也得弄啊,还是具体研究一下,不能做也得给出个理由啊... 按照JDK文档的描述, ...
  • ubuntu下查询CPU数,核心数,线程数

    千次阅读 2020-01-09 18:42:54
    ubuntu下查询CPU数,核心数,线程数 我们在选购电脑的时候,CPU是一个需要考虑到核心因素,因为它决定了电脑的性能等级。CPU从早期的单核,发展到现在的双核,多核。CPU除了核心数之外,还有线程数之说,下面笔者...
  • 查看CPU的核心数、线程数

    千次阅读 2019-07-08 10:27:33
    注意:设备管理器中的处理器数量为线程数: 因为Intel的超线程技术存在,它把一个CPU内核虚拟化为类似2个CPU内核,所以看到了4个处理器,线程越多,能并行同时处理的任务数也越多。 方法二:下载CPU-Z 工具...
  • c++ 获取cpu最大线程数

    千次阅读 2019-07-03 09:50:05
    #include <thread> std::thread::hardware_concurrency() //获取cpu最大线程数,超了会降低效率。

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,025,650
精华内容 410,260
关键字:

线程数