V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
lsk569937453
V2EX  ›  Java

[ Java ]中的线程池工作原理,为什么不是先创建线程而是先往阻塞队列里塞任务?

  •  
  •   lsk569937453 · 243 天前 · 7804 次点击
    这是一个创建于 243 天前的主题,其中的信息可能已经有所发展或是发生改变。

    假设核心线程数为 n,最大线程数为 m 。线程池创建后,就提交了 n 个任务且这 n 个任务一直在执行,没有结束。此时再提交一个任务就会塞到任务队列里。我的疑问是新提交的这个任务为什么不是创建一个新的线程执行?。线程池不是应该首先要保证任务完成吗?

    现在的逻辑是"先判断任务队列是否满再判断是否达到最大线程数",这样设计有什么优点呢?

    第 1 条附言  ·  243 天前
    "先判断是否达到最大线程数在判断任务队列是否满了"为什么不可以?
    不要偏题了,请后面的同学回答的时候围绕这个问题。
    86 条回复    2024-05-06 17:59:59 +08:00
    cheng6563
        1
    cheng6563  
       243 天前   ❤️ 1
    会创建新的啊,直到执行中的线程达到 m 才会按策略或阻塞或报错。
    codegenerator
        2
    codegenerator  
       243 天前
    先创建线程如果线程执行完任务就会空闲,空闲等待一定时间就会销毁
    这样会有比较多的线程创建销毁
    BiChengfei
        3
    BiChengfei  
       243 天前   ❤️ 9
    例子 1:你去银行办业务,银行也只会在队伍超级长的时候,才会开新窗口,也不是只要开始排队就加窗口
    例子 2:小明 1 天能处理 5 个 BUG ,新的 BUG 会放在 TODO 队列,TODO 队列长度为 100 ,当陆续进来了 10 个 BUG ,你是等待小明慢慢处理完,还是再招一个人呢
    imokkkk
        4
    imokkkk  
       243 天前   ❤️ 4
    具体场景具体分析吧 像 tomcat 线程池就是先创建线程再往队列里丢 web 场景下解决的是大量请求、短连接的场景
    NizumaEiji
        5
    NizumaEiji  
       243 天前
    因为创建线程需要消耗系统资源,而系统的资源是有上限的。
    orzwalker111
        6
    orzwalker111  
       243 天前 via Android
    合理利用资源。这个得结合业务调整配置,假如配置成 n=5 、m=100 、queue=10 万,而侧重点是想让任务快速提交执行,这个配置可能有问题了
    luzemin
        7
    luzemin  
       243 天前
    就是这么一个设计,允许任务排队等一下,你非要说我一下也不想等,那你创建的时候把队列长度设置为 0 就好了。
    但 [新建普通线程,执行完又没任务,还要销毁] 这一系列操作带来的开销,可能还超过了 [暂时放在队列稍后就处理]
    lsk569937453
        8
    lsk569937453  
    OP
       243 天前
    @codegenerator
    ```
    先创建线程如果线程执行完任务就会空闲,空闲等待一定时间就会销毁
    这样会有比较多的线程创建销毁
    ```
    既然你喜欢举例,那我就举一个例子。

    我们假设线程池的前 n 个任务非常耗时,一直执行不完
    "先判断任务队列是否满再判断是否达到最大线程数":如果任务队列长度是 10w ,那么后续提交的 10w 个任务(执行耗时很短),在任务对列满之前无法执行,需要一直等待在队列中。
    lsk569937453
        9
    lsk569937453  
    OP
       243 天前
    @orzwalker111 我使用线程池当然是通过并发让任务快速执行。难道除了"让任务快速提交执行"之外还有其他的选项吗?
    TuringHero
        10
    TuringHero  
       243 天前
    「线程池不是应该首先要保证任务完成吗?」
    并不是。我理解线程池就是为了在一定情况下让任务 等待 / 拒绝 从而避免创建过多线程
    veapon
        11
    veapon  
       243 天前   ❤️ 1
    因为入队列不需要锁,而创建线程需要锁,这样设计是为了尽量减少创建线程导致的频繁获取锁操作。
    muyiluop
        12
    muyiluop  
       243 天前
    ”我使用线程池当然是通过并发让任务快速执行“,那你就设置队列为 0 。线程池目的就是为了复用已经创建的线程减少线程创建的开销,你想的并发让任务快速执行,可以通过多线程来操作,但这不是线程池的目的。
    lsk569937453
        13
    lsk569937453  
    OP
       243 天前
    @veapon 请问我创建线程的时候获取锁耗时 1ms 。而你往阻塞队列塞任务不需要锁,但你任务入队了又不能执行,所以你讨论耗时有什么用呢。
    j1132888093
        14
    j1132888093  
       243 天前
    @lsk569937453 #8 你说的这个场景是少数情况,而且完全可以增大核心线程数或者减少队列长度来实现,线程池是为了减少开销,直接去判断最大线程数只会增加创建和销毁线程的开销
    salmon5
        15
    salmon5  
       243 天前
    这个逻辑其实不合理。计算资源可以扩容解决。
    leonshaw
        16
    leonshaw  
       243 天前
    @muyiluop 队列为 0 会导致任务超过最大线程数时直接被拒绝
    salmon5
        17
    salmon5  
       243 天前   ❤️ 1
    这个逻辑有点落后了,10-20 年前的思维,现在已经不是物理机时代(资源需要几天才能扩容到位:采购新物理服务器、CPU 、内存)。
    hellojukay
        18
    hellojukay  
       243 天前   ❤️ 1
    线程池就是用来控制线程数量不暴涨的,如果系统上任务太多,每个任务都一个线程,那么如果突然来了大量任务,系统上线程数量就会暴涨,操作系统调度的基本单位是线程,那么操作系统大量时间都花在调度线程上,效率非常低,所有任务都不能得到高效的执行。

    线程池保证了任务并发的数量,保证每个任务都能获得有效的 CPU 时间片段。
    veapon
        19
    veapon  
       243 天前
    @ lsk569937453 请问我创建线程的时候获取锁耗时 1ms 。而你往阻塞队列塞任务不需要锁,但你任务入队了又不能执行,所以你讨论耗时有什么用呢。
    ----
    你理解的锁问题只有耗时?
    InkStone
        20
    InkStone  
       243 天前
    避免频繁的线程创建销毁呗。
    lyxeno
        21
    lyxeno  
       243 天前
    我的理解是资源有限的情况,无限增加并行的线程并不会使得任务处理速度更快,反倒会因为线程上下文切换和线程的创建销毁导致额外的开销。

    使用线程池一个很重要的原因就是节省资源。你如果不需要节省资源,直接自己创建线程不就好了。
    lsk569937453
        22
    lsk569937453  
    OP
       243 天前   ❤️ 2
    @veapon 有什么就说什么,如果你不懂可以不说。做技术的不要拿反问句回答别人。
    salmon5
        23
    salmon5  
       243 天前
    tomcat 的线程池逻辑就是合理的:核心线程不够了,直接申请新创建线程。而不是先扔到队列。
    LiaoMatt
        24
    LiaoMatt  
       243 天前
    我觉得是因为任务应该很快就可以被处理, 在队列里留存的时间很短, 所以可以先不创建新的线程, 直到队列满了表现出线程池的计算能力真的不够用了, 再启动线程去消费队列, Doug Lea 觉得这样处理适用于大部分场景吧
    salmon5
        25
    salmon5  
       243 天前
    @salmon5 #17 ,假如一台 CPU 64C 混部的机器,CPU 只用了 20C ,还有 44C ;
    1 个 Java 核心线程是 10 ,放着 44C 不用,不够了居然放到了队列,这种线程池逻辑明显落后了。
    codegenerator
        26
    codegenerator  
       243 天前
    @lsk569937453 很简单你增加 n 到 m 就解决了
    whileFalse
        27
    whileFalse  
       243 天前 via Android
    因为它就是这么设计的,最大线程数相比核心线程数多出来的那一点是为了给任务队列兜底的
    如果你不喜欢,可以把核心线程数设为和最大线程数一致,应该就可以满足你的需求了
    veapon
        28
    veapon  
       243 天前
    还有个场景,当任务超过 n 时,如何保证任务按指定规则创建线程执行?这个是不是也是用队列的一个场景。
    muyiluop
        29
    muyiluop  
       243 天前
    @salmon5 #25 为啥创建的时候不直接创建一个核心线程数是 64 的呢。就像我们目前基本都是核心线程数直接是 cpu 核数。
    salmon5
        30
    salmon5  
       243 天前
    “先判断是否达到最大线程数在判断任务队列是否满了”,我推测 因为这个逻辑是 199 几年或者 200 几年弄的,那时候云计算或者硬件资源都相当有限,所有这个逻辑有点落后了。
    leonshaw
        31
    leonshaw  
       243 天前
    按 op 的需求想一下如何实现,感觉在提交任务时并不能很容易地判断是不是所有线程都繁忙。
    salmon5
        32
    salmon5  
       243 天前
    @muyiluop 混合部署省钱,64C256G/128G 可以部署十几、几十个微服务,不是每一个服务都每一刻这么大并发,需要的时候就申请,很多服务 tps 个位数,初始线程不需要那么多。(但是你核心线程不够了,可以马上申请创建,而不是弄个队列,自废武功)
    whileFalse
        33
    whileFalse  
       243 天前 via Android
    另外 lz 的问题,就算应用已经启用了最大线程数,这些线程都被占满了怎么办?我不是杠,而是如果你的有两种任务,一种很快另一种几乎无法完成,那么不管你有多少线程很快都会被后者占满

    这是你架构设计的问题,长时间的工作不要和其他工作共享线程池,你可以考虑把这些很慢的工作抽离出来放到单独的 job 服务里面,然后按照需求调整“快任务”服务和“慢任务”服务的数量
    whileFalse
        34
    whileFalse  
       243 天前 via Android
    @salmon5 那你能解释一下你这个核心线程到底有什么用吗
    LiaoMatt
        35
    LiaoMatt  
       243 天前   ❤️ 1
    假设的有十个并发任务需要处理, 每个任务需要 10ms, 当前核心线程池是 5, 用队列的理想时间是 20ms, 不需要创建销毁线程, 如果立马增加 5 个线程, 处理的理想时间大概是 10ms, 空闲后需要销毁, 如果类似的场景很多, 就需要频繁的创建销毁, 用队列可以做到削峰平谷, 避免以上场景造成的线程创建销毁的开销
    salmon5
        36
    salmon5  
       243 天前
    合理:核心线程---->不够了创建新线程--->队列( Tomcat )
    不合理:核心线程---->队列满了--->创建新线程( Java )
    salmon5
        37
    salmon5  
       243 天前
    @LiaoMatt #35 这个貌似 Java 的又合理了。
    iosyyy
        38
    iosyyy  
       243 天前   ❤️ 7
    线程池要保证的不是任务的完成而是任务的调度 保证大部分任务都能完成并且给予一定的容错才是线程池应该做的
    所以为什么不能先达到最大线程已经很明显了 先达到最大线程不满足调度的需求这部分最大线程的资源被白白浪费掉了

    另外建议阅读提问的艺术你这是提问的语气?
    Rickkkkkkk
        39
    Rickkkkkkk  
       243 天前
    要不发邮件问问作者
    perbugwei
        40
    perbugwei  
       243 天前
    @TuringHero
    我觉得这个是正解,这种池化的思想 是为了解决创建连接的开销,线程池和数据库连接池,都是为了避免频繁的销毁和创建。所以先放到队列中,然后再判断是否到最大线程数,是为了延迟创建线程。op 的“保证任务完成 ”,这个不是线程池做的事,是想要通过多线程的方式来实现,这个涉及到具体的业务场景了,需要开发的时候具体处理的。
    iosyyy
        41
    iosyyy  
       243 天前
    @iosyyy 提个问搞的跟你是我 leader 一样这年头真是什么人都有..
    另外单纯讨论问题 这个问题本质上是 java 实现线程池时的取舍

    你能提出这个问题代表你本质上并没有理解什么是核心线程 什么是最大线程
    最大线程是容错 而核心线程才是真正的执行者

    你把最大线程理解为了执行者而不是容错
    MoYi123
        42
    MoYi123  
       243 天前
    “线程池不是应该首先要保证任务完成吗?”

    不是, 线程池主要是为了省资源, 减少创建/销毁线程的次数.
    你这样做明显会增加创建/销毁线程的次数.
    kenvix
        43
    kenvix  
       243 天前
    你这不是显然的? CPU 密集型,超过 CPU 核数的线程没有任何意义。IO 密集型,用虚拟线程去。
    whileFalse
        44
    whileFalse  
       243 天前 via Android
    @salmon5 你的这个合理在哪儿?核心线程数的意义是什么?
    salmon5
        45
    salmon5  
       243 天前
    @whileFalse 我想了下,还是直接创建新线程合理,核心线程都在用了,就别到队列等核心线程了,现在云计算扩容很快。核心线程不够用,就直接创建新线程。最终不够就横向+纵向扩容。
    如果扔到队列,等核心线程,结果就是”服务慢、服务器空闲“。
    burymme11
        46
    burymme11  
       243 天前   ❤️ 1
    新提交的这个任务为什么不是创建一个新的线程执行?线程池不是应该首先要保证任务完成吗?
    当然不是了,线程池的核心一直就是维护好线程资源,在任务执行效率和资源消耗两者之间做优先级考虑,Doug Lee 选了后者,当时全世界的 Java 大牛们,也认可了他的选择。
    PS:线程池的源码是 Doug Lee 写的,距今估计快 20 年了,那个时候的硬件条件和现在根本没法比,我也觉得这个设计目前来看有点小瑕疵,有点落后时代了。

    "先判断是否达到最大线程数在判断任务队列是否满了"为什么不可以?
    当然可以,你完全可以自己实现一个。
    LiaoMatt
        47
    LiaoMatt  
       243 天前
    @salmon5 Tomcat 很多其实是 IO 密集型请求, 为了降低响应时间, 这么做可以理解, 因为客户端还等着呢, 每个场景有自己测重点; JUC 牺牲一点实时性, 减少系统资源消耗, 在异步执行实时性要求不高的任务, 这个设计挺好的
    dode
        48
    dode  
       243 天前
    你可以配置线程池的最大工作线程为设备线程数量 m ,

    有的线程池场景是业务资源不够,限制处理速度
    有的线程池是存计算可以把设备 CPU 使用达到 100%
    q727729853
        49
    q727729853  
       243 天前
    菜鸟的个人理解,不对勿喷。
    tomcat 是用来处理请求的,我们不应该让此成为我们系统的瓶颈,因此应该尽最大努力去执行。
    java 中的线程池更强调的灵活配置,更方便各个场景的使用。(你可以通过设置队列类型、拒绝策略来实现)

    至于附言中说的可不可以。那当然是可以。
    最后,问:难道我们不能通过修改线程池的配置,来达到类似 tomcat 线程池的效果吗?(注意哦,是类似)
    whileFalse
        50
    whileFalse  
       243 天前 via Android
    @salmon5 大哥你先动动脑子,如果你直接略过“核心线程”配置不断创建新线程,是不是说明你的配置数值设置的不合理呢?
    TtTtTtT
        51
    TtTtTtT  
       243 天前   ❤️ 1
    个人的猜测,是因为根据线程池目前的设计,当线程池扩张到最大线程数 m 后,从 m 减小到 n 是很困难的。因此,在扩张线程数上,采取了更为谨慎的策略。

    根据实践,当线程数扩张到最大线程数后,少量的任务就可以满足 Keep Alive Time 。只有在线程池完全不再有新的任务时,才能回缩到核心线程数。
    kneo
        52
    kneo  
       243 天前 via Android
    你问的没错,这个策略是有点奇葩。

    core size 的设置更像是为 CPU 类型的计算服务的,所以满了就不创建。但是又设置了个 max size ,然后又只有在队列满了的时候才工作……

    因为这个队列就是给你存等待任务的,队列满了它看实在不行了,存不下了,才会不情愿的给你分配线程……

    所以,根据你的需求,你用 fixed thread pool 就好了(其实就是 core size 和 max size 调成一样的了)。
    justplaymore
        53
    justplaymore  
       243 天前
    java 线程池设计是分为任务管理和线程管理,典型的生产者消费者模式,由任务队列生产消息,由线程池消费消息。

    “新提交的这个任务为什么不是创建一个新的线程执行?”

    当发生大量并发提交任务时,使用队列可以做到无锁化,而如果直接使用线程池需要进行加锁。
    由任务队列统一接管任务提交,由线程池控制从任务队列中取任务,将任务队列和线程池解耦,他们之间只通过消息进行交互。
    admol
        54
    admol  
       243 天前
    问的问题有点奇怪。

    这个队列不就是"池"吗?

    既然是池,是不是要判断池子满了没满?

    "先判断任务队列是否满再判断是否达到最大线程数"

    这里的满,不是指一个池子最上面边缘的刻度,可能是边缘下面一点。就比如水瓶满 500ml ,但是实际可能水并没有装到瓶口口沿,这样设计估计也是为了留出一定的膨胀空间(代码就是留出一定的更大并发能力)。

    满就是线程池设计的标准并发能力。
    最大线程数是线程池的最大并发能力。
    cybort
        55
    cybort  
       243 天前 via Android
    因为队列没满说明当前的线程处理能力能 hold 住你丢任务的速度。
    ma836323493
        56
    ma836323493  
       243 天前
    先判断是否达到最大线程数在判断任务队列是否满了"为什么不可以?

    你想这样做, 为什么不把核心线程设置大一点呢?
    Plutooo
        57
    Plutooo  
       243 天前
    Java 历史包袱太重
    wetalk
        58
    wetalk  
       243 天前
    OP 需要的是这个 org.apache.tomcat.util.threads.ThreadPoolExecutor ,和 java.util.concurrent.ThreadPoolExecutor 最大区别在于,优先打满 maximumPoolSize ,至于为何如此设计,就藏在类的 doc 文档中
    7911364440
        59
    7911364440  
       243 天前
    创建线程成本比较高,有可能创建新线程的过程中已经有任务执行完成了,这样新创建的线程就是多余的了
    gongxuanzhang
        60
    gongxuanzhang  
       243 天前
    根据业务设置不同的参数就可以达到目的,你把线程池看成一个 MQ,第一要务是异步,而不是快速解决. 没有人能保证你任何任务快速解决
    trzzzz
        61
    trzzzz  
       243 天前
    @lsk569937453 那是你逻辑设计有问题。哥们
    wqhui
        62
    wqhui  
       243 天前
    保证任务完成跟用最快速度完成任务不是一件事,保证任务完成是任务不丢,用最快速度完成任务是尽最大可能抢占资源,哪怕把别的任务干停也不无所谓。
    新提交任务直接优先创建新线程执行,目的是为了尽可能快的处理掉提交的任务,但代价是可能挤压其他线程的运行,因为应用中一般都不止一个线程池,所有线程池都优先创建线程,即线程池线程数很容易就会大幅超过实际并行数,回到了用线程池前的情况。
    比如总共支持 10 线程并行,有 5 个线程池,每个池 max thread 是 5,core thread 是 1 ,每个池子打满有 25 线程,优先新线程执行策略,只需要 10 个任务就吃满并行了;优先入队策略,首先得把队列打满,可能是 1w 个任务,才会出现应用有 10 线程在跑,相对并行数来讲内存队列存储任务明显不值钱且易扩容

    先判断是否达到最大线程数在判断任务队列是否满了,这种需要在处理 IO 任务或者应用总线程池数非常少的时候才行
    ZeawinL
        63
    ZeawinL  
       243 天前 via iPhone
    如果有这类需求,可以使用 SynchronousQueue 虚拟队列
    yidinghe
        64
    yidinghe  
       243 天前
    我看了一下基本上都没回答到点子上。

    这个问题的起因在于 Worker 线程的工作模式:循环从队列中取任务,取不到任务就终止。取任务这里会有 keepAliveTime 的超时设定,也就是说,在没有任务的情况下,线程依然会保持 keepAliveTime 这么长时间。

    这就是关键!请问,当线程数达到 corePoolSize 后,再提交新的任务时,是不是一定要马上创建新的线程???

    答案是否定的!

    为什么?因为此时 corePoolSize 个线程并非都是忙的,可能存在空闲的线程。这个时候我们要做的,是把这个可能存在的空闲线程利用起来,而如果不这么做而是创建新的线程,就是浪费系统资源。

    怎么利用起来呢?答案就是将任务丢进队列。此时正在取任务的核心线程就会马上取到这个任务了。
    yty2012g
        65
    yty2012g  
       243 天前   ❤️ 3
    1 、ThreadPoolExecutor 的行为是,如果线程数<核心线程数,直接创建,否则丢队列,然后如果小于最大线程数就创建线程。
    2 、基于第一条,所以 JDK 的线程池设计原则中是包含了 Core (重要的)线程和其他线程之分的,因为线程的创建是重操作。
    3 、基于 OP 的表述,似乎是任务是平等的,没有重要的任务和不重要的任务之分,进而也没有重要的核心线程和一般的其他线程之分,所以认为应该先创建线程而非塞队列,那么这个和 JDK 的设计原则是不太一样的
    4 、Tomcat 线程池的设计原则就是任务是平等的,因此 Tomcat 线程池的策略是先创建线程到最大线程,而后才会塞入队列
    fcten
        66
    fcten  
       243 天前   ❤️ 2
    1. 使用线程池的目的是为了减少线程的创建和销毁,而不是为了保证任务完成。
    任务是否能完成受很多因素影响,瓶颈不一定在线程数量上。试图去保证任务一定完成是不切实际的,盲目创建新线程可能会让系统加速崩溃。

    2. "先判断是否达到最大线程数在判断任务队列是否满了"为什么不可以?
    如果使用这样的策略的话,提前创建好最大的线程数量(核心线程数=最大线程数)会比让线程池去动态创建和销毁线程更高效。动态销毁线程的唯一优势大概是节省一点点内存。

    3. "先判断任务队列是否满再判断是否达到最大线程数",这样设计有什么优点呢?
    减少了线程的创建和销毁。当请求量只是在短时间内超出处理能力的情况下,可以避免创建新线程。
    imaple
        67
    imaple  
       243 天前
    根本原因是无限的开线程不仅不会加快系统, 反而会增加线程切换的开销;
    zhangjiashu2023
        68
    zhangjiashu2023  
       243 天前
    @cheng6563 额不对把,是先创建到核心线程数量然后加入队列,队列满了再创建线程到最大线程数量
    zerolinck
        69
    zerolinck  
       243 天前
    不是不可以这么做,而是当前的设计更简洁一些,创建线程相关的工作再从任务队列中取任务时已经有了,在添加任务的阶段增加这些步骤的话,增加了系统复杂度。
    <img src="https://imgse.com/i/pkiWV8P">
    zerolinck
        70
    zerolinck  
       243 天前
    pegasusz
        71
    pegasusz  
       243 天前
    @BiChengfei 精辟
    nothingistrue
        72
    nothingistrue  
       243 天前
    "先判断是否达到最大线程数在判断任务队列是否满了"为什么不可以?

    为什么要可以?你定义的 n < m ,那不就是打算让它再非繁忙情况下只运行 n 个线程吗?你现在又让它在刚 n+1 个任务就开新线程,这在现实中就是瞎指挥的「领导」。
    nothingistrue
        73
    nothingistrue  
       243 天前
    看了楼主后面的回复,我上面的回复应该是白瞎了。

    追本溯源,Java 的线程池,其设计目的不是为了线程池,它是异步执行器的无奈实现。Java 内置的线程池,你只能把它当作异步执行器来看。而异步执行器,它只保证新任务能被快速接受——以不阻塞任务的提交者,不保证新任务被立刻执行——这很可能导致后续任务提交被阻塞。如果你要的是类似连接池那样的同步调用场景的池子,你得自己设计非异步执行器的线程池。

    另外,即时是常规线程池,你也没办法保证任务被立即执行,因为线程启动不等于线程里面的任务开始执行。
    hapeman
        74
    hapeman  
       242 天前   ❤️ 1
    线程池的本质是为了减少创建和销毁线程的资源损耗,非核心线程创建执行完任务后会在一定时间后被销毁,这是需要消耗系统资源的

    Java 线程池:当核心线程满了之后将新提交的任务放入等待队列中而非直接创建一个非核心线程执行任务,我认为这是基于“核心线程能够在较短的时间内完成任务并继续执行等待队列中的任务”这一想法的

    你说的核心线程满了之后直接创建非核心线程去执行任务,可以参考 Tomcat 中的线程池

    其中的考量也可能和任务类型有关,Tomcat 主要是 IO 密集型任务,Java 更多的是 CPU 密集型任务
    future0906
        75
    future0906  
       242 天前   ❤️ 1
    1. 可不可以这样设计:可以。
    2. 为什么不这样设计:楼上已经回答你了,避免频繁创建销毁线程导致的 overhead ,尤其是边缘情况。
    3. 我想要线程池这样设计:可以,请自行实现
    4. 我反问楼主,如果耗时长的任务超过最大线程怎么办?线程池怎么知道每个任务的耗时?
    cheng6563
        76
    cheng6563  
       242 天前
    @zhangjiashu2023 你说的对,看来是我搞错了。那我也有和楼主一样的疑惑了。
    Narcissu5
        77
    Narcissu5  
       242 天前
    《 Java Performace 》里面讲得很清楚了,计算密集型和 IO 密集型对线程池的要求是完全不同的,问题是线程池本身并不知道当前任务的特性,如果线程池里面是 IO 密集型,增加更多线程支持增加上下文切换的开销而已。这种方式是一种折中的方式,你需要自己根据任务特点设置参数
    flyingpot
        78
    flyingpot  
       242 天前 via iPhone   ❤️ 1
    不清楚为啥这么设计,不过 ES 使用了先加线程再放队列的线程池,只需要实现一个自定义的 queue 就行,类名叫 ExecutorScalingQueue
    jimrok
        79
    jimrok  
       242 天前
    首先你的说法是没有问题的,这个问题是 java 的问题,如果你学习过其他语言,如 erlang ,它是可以马上创建新的线程去执行的。erlang 可以轻松并发几百万个线程。其实 erlang 这个不叫线程,叫协程。java 做不到是因为线程太昂贵了,这个资源创建时候,需要准备部分线程栈存放线程的元数据,大概要消耗一小部分内存,同时要映射到操作系统的线程上,如果经常这样做,开销是非常高的,可能会超过你执行线程的任务。所以,现在 java 的模式是做一个线程池,通过一个队列来接受任务,避免反复创建线程的开销。
    jimrok
        80
    jimrok  
       242 天前   ❤️ 1
    Java 的这个线程池设计问题可以追朔到 java 语言创立之初的作用,最早 java 是想提供给智能硬件编程用的,没有考虑服务器大并发的使用场景,后续的 golang 还有 erlang 的并发模型都和 java 不一样,只能说 java 用在服务器上编程大大超出了创建者的预期。每年都有新的技术出现,开发者可能疲于追求新的技术,而忘记了探讨一项技术的本源是什么,上面太多的回答是从使用的角度解释的,可能从业这么长时间,也没有去了解过这些技术是怎么演进的。我想,可能参与这个过程的老师傅们已经下岗了。
    Karte
        81
    Karte  
       242 天前
    为什么这人提问有种高高在上的感觉?
    cheng6563
        82
    cheng6563  
       242 天前
    @jimrok 这问题现在看来确实很奇怪,和类似的其他场景比如连接池规则都不一样,而且创建连接的消耗可比创建线程高多了。
    jimrok
        83
    jimrok  
       242 天前
    @cheng6563 线程池的作用还是为了减少创建线程的开销,在 linux 下是存在用户态和核心态的指令差别的,核心态运行在 cpu 0 的指令级别上,是一种高优先级的程序指令,通常叫系统调用,这些指令能干很危险的操作,例如对物理内存的访问。而用户态只能看到虚拟内存了,这就倒是创建线程时候,会在两个状态的代码之间来回切换。当你做高并发的应用时,例如 1000req/s 的操作时,你就不能忽视这种开销了。你可以做个实验,当你创建 100 万个 thread 对象,即便都在线程池中什么任务都不干,机器已经卡的不行了。
    Aresxue
        84
    Aresxue  
       235 天前
    因为线程是非常宝贵的资源,这个设计主要就是保证线程资源的最大化利用。

    在更早的时候操作系统的可支撑线程数是非常有限的,单机的性能也很弱,但随着时代的发展其实这个思路已经并没有那么适用了,在实际中很多都是把核心数和最大线程数设为一致。更有甚者 tomcat 直接修改了这一策略,在没有可用线程时它会优先开启一个新的线程直至最大线程数,然后才会堆积请求。如今 tomcat 的这种思路更为流行比如 Druid 的连接池中的最小连接数和最大连接数也是这个逻辑。

    当然随着虚拟线程的流行,线程的限制将会荡然无存,线程池也会逐步退出历史舞台,最终只服务于很少的一些场景。
    Richared
        85
    Richared  
       234 天前
    因为 Java 线程一比一啊,资源昂贵。比如你一个月 10w 块,是排队等等让你搬砖还是再招一个 10w 块的帮你?我理解是这样的。
    Richared
        86
    Richared  
       234 天前
    @salmon5 可以直接创建核心线程 64 的啊。我遇到过线程池参数直接就是机器核心数的。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   4747 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 27ms · UTC 09:47 · PVG 17:47 · LAX 01:47 · JFK 04:47
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.