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

求教一个 Java 线程池的问题

  •  
  •   abc0123xyz · 2022-09-02 09:56:59 +08:00 · 3150 次点击
    这是一个创建于 592 天前的主题,其中的信息可能已经有所发展或是发生改变。

    ThreadPoolExecutor 创建线程池
    Callable 接口的实现类处理逻辑

    在实现类的 call()中,每次都要访问一个资源,这一步要耗时很久。持有这个资源的对象其实是可以复用的,怎么样才可以让线程本身持有这个资源?或者各位大佬给点其他思路

    第 1 条附言  ·  2022-09-02 10:51:45 +08:00
    补充一下
    整个处理逻辑是,从 mq 获取信息,然后丢入线程池处理,整个处理过程创建这个对象要 2s ,让该对象处理资源要 1s+
    然后通过 future.get()获取处理结果

    怎么样才可以让对象创建后可以被重复使用,并且处理过程中一旦抛出异常或超出一定时间就销毁重新 new
    25 条回复    2022-09-02 18:47:24 +08:00
    Bluelion
        1
    Bluelion  
       2022-09-02 10:05:07 +08:00
    ThreadLocal
    beetlerx
        2
    beetlerx  
       2022-09-02 10:05:47 +08:00
    那就放 threadLocal 里呗
    L0L
        3
    L0L  
       2022-09-02 10:15:42 +08:00
    1 、ThreadLocal 直接定义最简单;
    2 、或者使用自定义 ThreadFactory ,来自定义线程池内线程,构建时组合一个资源;
    3 、我看你的描述,这个对象如果是可以复用的,为什么不通过单例来做呢,简单又高效(注意:没有线程安全的问题情况下)。
    lmshl
        4
    lmshl  
       2022-09-02 10:20:27 +08:00
    放资源池不就行了,学 jdbc 的 connection pool 呗。
    shanghai1943
        5
    shanghai1943  
       2022-09-02 10:21:16 +08:00
    你是想在单个线程里复用还是线程池里复用?
    abc0123xyz
        6
    abc0123xyz  
    OP
       2022-09-02 10:28:04 +08:00
    @L0L #3 这个对象只能同时处理一个,多个线程同时调用的时候会其他的就要等待了。

    现在 call()中每次都新建是因为,这个对象不是很可靠,有时候会出问题,一旦出问题,这个对象可能就用不了了,所以需要销毁重新 new 。

    其实我是希望对象本身可以服用,处理过程中一旦抛出异常或超出一定时间,就把对象销毁重新 new
    abc0123xyz
        7
    abc0123xyz  
    OP
       2022-09-02 10:29:50 +08:00
    @lmshl #4
    @Bluelion #1
    @beetlerx #2
    感谢各位大佬
    我研究一下,平时一直都在增删改查,没处理过这种东西
    dqzcwxb
        8
    dqzcwxb  
       2022-09-02 10:31:22 +08:00
    用 Caffeine 做缓存全局共享前提是你的共享对象只读不写,ThreadLocal 存在父子线程传递问题要是你不传递你就得每个线程都去创建
    abc0123xyz
        9
    abc0123xyz  
    OP
       2022-09-02 10:32:20 +08:00
    @shanghai1943 #5
    都可以,线程池中复用应该更好
    ForkNMB
        10
    ForkNMB  
       2022-09-02 10:32:25 +08:00
    FastThreadLocal
    L0L
        11
    L0L  
       2022-09-02 10:54:54 +08:00
    @abc0123xyz
    本质上你这个资源是线程不安全的,你需要同步方法来保证执行过程中的安全;如果是我来写的话,我可能还是会优先使用单例。
    * 在获取单例的时候,校验下该对象是否可用;
    * 如果对象不可用的话会,重新构建一个单例对象出来,替换原本的对象;
    * 同时执行具体方法的时候,加上同步锁,保证当前只有一个线程执行具体的方法。

    实现起来方法多种多样,只要能满足需求,都是好的方法。
    timethinker
        12
    timethinker  
       2022-09-02 11:09:45 +08:00
    这个对象有没有可能创建多个实例,每一个线程使用自己所绑定的那一个?这样线程之间就不用竞争,也无需加锁。

    具体的做法就是,在线程池中的其中一个线程执行你的代码的时候:
    1 、从 ThreadLocal 取出这个对象。
    2 、如果不存在,实例化这个对象并对它进行初始化,然后再保存到 ThreadLocal 里面。
    3 、使用这个对象。

    这样当下一次这个线程再次执行时,就可以省略掉第二个步骤,对象就可以得到复用。

    至于你说的这个对象有可能会损坏,你可以创建一个类来包装并代理委托执行相关的方法,通过封装内部实际对象,就可以对异常进行处理,比如重新构造内部实际对象,这样外部的代码就可以不用关心内部的处理流程。但是如果异常是必须要在业务逻辑中进行处理的,就不能通过代理类对这些异常进行掩盖,以免导致意外的 BUG 。
    xsqfjys
        13
    xsqfjys  
       2022-09-02 11:14:46 +08:00
    好奇怪的场景,一个不可靠的对象可还行
    hidemyself
        14
    hidemyself  
       2022-09-02 11:22:39 +08:00
    apache commons pool2 看一下这个
    DavidDee
        15
    DavidDee  
       2022-09-02 11:32:29 +08:00
    比较好奇,这个对象是怎么变为不可靠的呢
    abc0123xyz
        16
    abc0123xyz  
    OP
       2022-09-02 11:35:27 +08:00
    @xsqfjys #13
    @DavidDee #15
    其实就 java 调用第三方工具,第三方卡死,所以这个对象就没用了,要重新 new 一个
    zmal
        17
    zmal  
       2022-09-02 12:02:12 +08:00
    这不就是连接池的适用场景么
    ma836323493
        18
    ma836323493  
       2022-09-02 15:09:53 +08:00
    看你说的像是多个线程复用, 那就个一个静态 引用 ,然后 用 lock 把线程中使用的地方 lock 起来
    urnoob
        19
    urnoob  
       2022-09-02 15:22:34 +08:00
    可参考 guava cache
    所有线程执行的 call 方法中都是从 cache 中取(“读”)这个实例。当没有的时候就,guava cache 会调用你在创建 cache 时定义的加载这个实例的方法。剩下的就是做好线程同步即可 避免重复加载即可。
    goldenAndGreen
        20
    goldenAndGreen  
       2022-09-02 15:22:34 +08:00
    apache object pool, 线程从池子里取对象
    nothingistrue
        21
    nothingistrue  
       2022-09-02 15:57:35 +08:00
    @abc0123xyz 你要多个线程对同一个对象互斥,又要这个对象依赖的第三方对象能被多个线程复用。这是要一个并发场景下的共享对象,一部分状态是隔离的,另一部分状态是公用的,这是不可能的。

    狗日的,我看你的描述头疼的很。

    首先,你到底有没有这样的数据或者业务要求:最多一个线程处理它,如果是多个线程同时处理的时候,必须排队。如果没有的话,就别提在关注这一点。我看你的业务场景压根不涉及并发加锁。
    nothingistrue
        22
    nothingistrue  
       2022-09-02 16:12:57 +08:00
    提醒你一点:你提交给执 Executors 的是一个实现 Callable 接口的对象,不是 Callable 类的对象,这个对象不只有 call() 方法,是可以定义其他字段的。

    所以你没必要在 call() 方法中去生成或申请那个资源,你可以在你实现 Callable 的类(别再用匿名类了)上定义一个字段来映射那个资源,然后在提交到 Executors 前就给它设值。这个设值过程,就跟线程池或异步执行器无关了,就是典型的单例模式。

    上面不会解决你想要的超时处理。
    nothingistrue
        23
    nothingistrue  
       2022-09-02 16:24:00 +08:00
    超时那个问题,把 future.get() ,换成:
    try {
    future.get(long timeout, TimeUnit unit);
    } catch (TimeoutException e){
    // 超时时候的处理,这里你想咋处理就咋处理,但是当前这个任务肯定是失败了。
    }
    corningsun
        24
    corningsun  
       2022-09-02 17:00:32 +08:00
    @abc0123xyz 重点描述下你的第三方工具到底是啥吧?什么协议,支持多少并发? QPS 之类的有限制吗?

    卡死的原因要找出来。

    然后再根据 kafka 数据量有多少,决定单线程跑,还是起多线程,要不要加限速
    chenshun00
        25
    chenshun00  
       2022-09-02 18:47:24 +08:00
    为啥第三方卡死了,你就把对象丢了呢,在这里做个熔断就好了吧,不就可以保持这个一直可用了么。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   1209 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 27ms · UTC 18:15 · PVG 02:15 · LAX 11:15 · JFK 14:15
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.