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

线程池在项目中怎么使用的疑惑

  •  
  •   dovme · 2019-06-05 11:35:14 +08:00 · 6716 次点击
    这是一个创建于 1998 天前的主题,其中的信息可能已经有所发展或是发生改变。
    在网上搜索,都只会讲类似使用

    ExecutorService s = Executors.newFixedThreadPool(5);

    这种方式来创建

    我想问的是: 在一个请求需要处理很复杂的运算时,使用线程池,

    那么是直接在方法里面 new 一个出来,使用完之后关闭掉?

    还是使用单例模式创建一个全局的线程池.

    我不是很理解这个

    如果说每次请求 new 一个线程池出来的话,

    在高并发下,是不是存在很多个线程池,那么内存应该会溢出的啊.

    但如果只创建一个线程池,

    在高并发下,无数的请求都排队使用那几个固定的线程,不是更慢了吗?

    java 里面每个 web 请求过来都是一个线程,不使用线程池的话,

    自己的线程处理自己的事情,比所有请求共用一个线程池快得多吧.

    我不知道我这么想对不对.但是我使用 apache 的 ab 同时请求 1000 次的话,

    不使用线程池比使用单例的线程池快很多很多.

    希望大佬来帮小弟解惑.
    20 条回复    2019-06-05 17:31:08 +08:00
    gosansam
        1
    gosansam  
       2019-06-05 11:48:51 +08:00   ❤️ 1
    #### 首先用 Spring 创建一个线程池的 Bean 例如 taskExecutor,需要异步处理的方法上添加 @Async("taskExecutor"),这样调用异步方法就会使用这个线程池。
    #### 线程池这个东西肯定不是需要就 new 一个,为什么使用线程池? 为了节省线程创建和销毁带来的消耗,每次 new 一个线程池比每次 new 一个线程开销更大。
    #### 高并发下,需要根据机器配置设计合理的线程池参数,使用线程池是为了异步部分请求,加速全局请求。
    dovme
        2
    dovme  
    OP
       2019-06-05 11:59:56 +08:00
    @gosansam #1 使用 @Async 注解 方法并发执行,还需要线程池吗?还是说防止异步线程太多,才使用线程池?
    dovme
        3
    dovme  
    OP
       2019-06-05 12:03:05 +08:00
    @dovme #2 所以说我理解错了,线程池一般都是异步执行,而不是同步对吗?
    pursuer
        4
    pursuer  
       2019-06-05 12:11:21 +08:00 via Android   ❤️ 1
    怕线程不够就不要创建固定线程池,用动态增长的线程池(好像叫 CachedThreadPool?)不就可以了
    cxtrinityy
        5
    cxtrinityy  
       2019-06-05 12:18:57 +08:00   ❤️ 1
    线程池也有很多种的,1L 也说了,主要是为了节省创建线程的开销,复用已创建线程,用的时候一般都是全局 new 1 个或者你有特殊需求 new N 个
    比如你举的例子,是固定线程数量为 5 的线程池,不管几个 runnable,callable 过来,同时跑的线程只有 5 个,其他的都得排队,还有 newCache (按需创建,默认线程数量上限整形上届,不保存固定数量线程,idle 线程存活 60 秒)、newSingle (单线程复用)等等,可以看看
    至于你说的 1000 个请求不使用线程池比较快,得具体情况具体分析,如果你用的是像你举的例子那样,那肯定是按需 new 线程快,毕竟线程池里只有 5 个线程在并发
    dovme
        6
    dovme  
    OP
       2019-06-05 12:40:47 +08:00
    @cxtrinityy #5 使用 newCache 这个创建的线程池,如果 1000 个请求过来,会创建 1000 个线程?目前 4 vCPU 8 GiB 内存这样的服务器,能承受多少线程而不出什么问题呢?
    chendy
        7
    chendy  
       2019-06-05 12:58:34 +08:00   ❤️ 1
    snappyone
        8
    snappyone  
       2019-06-05 13:19:06 +08:00   ❤️ 1
    @dovme 最佳线程数取决于你要处理的任务类型和你的 cpu 数量,简单的说 cpu 密集型任务就跟 cpu 数量差不多的线程数就可以了,而 io 密集型则线程可以设置比较大(具体多大需要测试才知道),线程创建太多会导致 cpu 上下文切换造成额外无必要的性能开销
    Beeethoven
        9
    Beeethoven  
       2019-06-05 13:39:00 +08:00   ❤️ 1
    我理解线程池并不是一个解决高并发的好方法。一般我都是在后台处理复杂数据时用的,接收到请求时,主线程处理简单的部分并返回结果,复杂的部分开个新线程扔过去慢慢处理。
    cxtrinityy
        10
    cxtrinityy  
       2019-06-05 13:40:14 +08:00 via Android   ❤️ 1
    @dovme 不一定会有 1000 个,因为如果某个线程执行完任务就会被复用
    至于内存方面,没有具体计算过,不过一个具体线程并不会占用太多内存,具体到 runnable 看实现,这些都可以通过类内定义的变量来计算的,不过 1000 个对象应该没什么压力
    至于 CPU,楼上说的挺清楚了
    f2ed
        11
    f2ed  
       2019-06-05 13:44:35 +08:00   ❤️ 1
    当然是创建 CPU 核心数量的线程数了
    Takamine
        12
    Takamine  
       2019-06-05 13:51:53 +08:00 via Android   ❤️ 1
    线程池是为了节省频繁创建的开销,另外一方面是对系统资源稳定的一种保护。
    一般线程数取计算密集型 N+1,IO 密集型 2N,然后再调整,可以查一下公式。
    简单计算就是 线程数= cpu 数 /( 1-阻塞率)。
    axbx
        13
    axbx  
       2019-06-05 14:07:55 +08:00   ❤️ 1
    一般用线程池是为了避免异步处理任务的时候重复创建线程的开销,使用 TheadPoolTaskExectour 来创建线程池 Bean,整个项目都用这个 Bean 创建线程。
    0xZhangKe
        14
    0xZhangKe  
       2019-06-05 14:10:12 +08:00   ❤️ 1
    首先线程池是用来解决线程共用问题的,此外不同的线程池解决的问题多少有些不同。
    面对这样的设定,我们可以设想一下不同的线程池可以解决什么样的问题。
    例如我们可以创建一个线程数固定为 1 的全局单利线程池,用来执行一些优先级实时性都不高,但可能个数较多的任务。比如定期收集日志并上传、获取设备内存等状态信息、检测并清理运行时的垃圾文件等等。
    同样,可以创建一个固定个数为 CPU 核心数的全局单利线程池,用来执行一些实时性要求较高,但也没这么高的任务,例如网络请求,网络请求框架 Volley 使用的就是个数为 4 的全局单利线程数组来执行网络请求。
    ligz
        15
    ligz  
       2019-06-05 14:24:36 +08:00   ❤️ 1
    谈谈我的理解。首先一般不使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方更加明确线程池的运行规则,规避资源耗尽的风险。

    其次,肯定不是需要的时候就 new 一个出来,而是通过全局配置的线程池,有这么个作用
    1. 降低资源消耗。 通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
    2. 提高响应速度。 当任务到达时,任务可以不需要的等到线程创建就能立即执行。

    你需要设定核心线程数和最大线程数,一般根据你 cpu 的核数和是 IO 型的任务还是 CPU 型的任务决定,不会无限制的创建线程的,多余的任务存储在你设置的队列里面,比如阻塞队列 BlockingQueue。

    真正执行计算逻辑的还是你操作系统的线程,当你的任务操作时间很短或者数量很少的时候,看不出什么区别,甚至会更慢。如果你没提前创建好线程池,线程池的创建时间可能比你执行那些请求的时间都长。
    shangfabao
        16
    shangfabao  
       2019-06-05 14:25:05 +08:00   ❤️ 1
    看你自己的使用场景了,大部分是固定几个线程池
    qiyuey
        17
    qiyuey  
       2019-06-05 14:29:52 +08:00   ❤️ 1
    线程池的问题在于你需要估计线程池的配置,注意是估计,应为线程池的配置其实是没办法准确计算的,需要通过压测来不断调整。即使配置相对合理之后,仍不能避免线程阻塞导致的线程切换的成本。不如直接用 Coroutines 和 Reactive。
    dovme
        18
    dovme  
    OP
       2019-06-05 14:41:57 +08:00
    @ligz #15
    @0xZhangKe #14
    @cxtrinityy #10
    @qiyuey #17
    @axbx #13
    @gosansam #1 谢谢大佬们详细的解答.非常感谢
    securityCoding
        19
    securityCoding  
       2019-06-05 15:06:56 +08:00   ❤️ 1
    spring 中的 taskExecutor bean 就是单例的,跟你自己创建差不多
    x7395759
        20
    x7395759  
       2019-06-05 17:31:08 +08:00
    Java 并发实战推荐给你
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2830 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 23ms · UTC 07:20 · PVG 15:20 · LAX 23:20 · JFK 02:20
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.