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

并未做任何处理,但两次 toString 结果不一致,为啥?

  •  
  •   JinTianYi456 · 2022-07-13 10:56:56 +08:00 · 4781 次点击
    这是一个创建于 865 天前的主题,其中的信息可能已经有所发展或是发生改变。
    @Configuration
    @EnableAsync
    @Slf4j
    public class ExecutorConfig {
        @Bean("taskExecutor")
        public ThreadPoolTaskExecutor asyncServiceExecutor() {
            ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
            return executor;
        }
    }
    
    // test code
            var executor = executorConfig.asyncServiceExecutor();
            System.err.println(System.identityHashCode(executor));
            System.err.println(executor);
            System.err.println(System.identityHashCode(executor));
            System.err.println(executor);
    
    // output
    2016053161
    org.springframework.cloud.sleuth.instrument.async.LazyTraceThreadPoolTaskExecutor@62730eda
    2016053161
    org.springframework.cloud.sleuth.instrument.async.LazyTraceThreadPoolTaskExecutor@65d849e0
    
    35 条回复    2022-07-15 23:07:53 +08:00
    lancelee01
        1
    lancelee01  
       2022-07-13 11:35:10 +08:00
    用 Java 试了一下,是同一个对象。
    ```
    ExecutorService pool = new ThreadPoolExecutor(8, 8, 10, TimeUnit.SECONDS, new LinkedBlockingQueue<>());
    System.out.println(System.identityHashCode(pool));
    System.out.println(pool);
    System.out.println(System.identityHashCode(pool));
    System.out.println(pool);
    ```
    ```

    ```
    lancelee01
        2
    lancelee01  
       2022-07-13 11:39:51 +08:00
    @lancelee01
    1639705018
    java.util.concurrent.ThreadPoolExecutor@61bbe9ba[Running, pool size = 0, active threads = 0, queued tasks = 0, completed tasks = 0]
    1639705018
    java.util.concurrent.ThreadPoolExecutor@61bbe9ba[Running, pool size = 0, active threads = 0, queued tasks = 0, completed tasks = 0]
    JinTianYi456
        3
    JinTianYi456  
    OP
       2022-07-13 11:43:27 +08:00
    @lancelee01 #1 我也觉得是一样的,但你没按照我的环境复现,比如 @Configuration ExecutorConfig ,以及是 LazyTraceThreadPoolTaskExecutor 、、因为我也不知道是哪里影响到了 toString 的结果
    falsemask
        4
    falsemask  
       2022-07-13 12:13:10 +08:00
    @JinTianYi456 有没有可能和这个异步有关
    JinTianYi456
        5
    JinTianYi456  
    OP
       2022-07-13 12:43:46 +08:00
    @falsemask #4 只保留 @Configuration 也一样
    wolfie
        6
    wolfie  
       2022-07-13 13:49:06 +08:00
    你这被 sleuth 增强过吧,看看 LazyTraceThreadPoolTaskExecutor#toString 有没有自定义,或者初始化时候有没有使用匿名内部类覆盖。
    JinTianYi456
        7
    JinTianYi456  
    OP
       2022-07-13 13:54:09 +08:00
    @wolfie #6 看过了,没有(它这输出样式就基本断定 Object 里的
    Aruforce
        8
    Aruforce  
       2022-07-13 14:28:30 +08:00
    查下这个类是不是被 spring 加载的。。如果不是。。 @Bean 相关的的 methodInvokeInterceptor 是不生效的
    Aruforce
        9
    Aruforce  
       2022-07-13 14:29:13 +08:00
    @Aruforce 或者你看下 @Configuration proxyBeanMethods 默认行为是不是 true
    JinTianYi456
        10
    JinTianYi456  
    OP
       2022-07-13 14:36:09 +08:00
    @Aruforce #9 System.err.println(executor.getClass().getName());
    org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor$$EnhancerBySpringCGLIB$$b9d128b3
    Aruforce
        11
    Aruforce  
       2022-07-13 15:07:30 +08:00
    @JinTianYi456 System.err.println(executor.getClass().getClassLoader().getClass().getName());
    JinTianYi456
        12
    JinTianYi456  
    OP
       2022-07-13 15:09:30 +08:00
    @Aruforce #11 java.net.URLClassLoader
    zmal
        13
    zmal  
       2022-07-13 15:25:48 +08:00
    一模一样的代码试了下:

    1835794313
    org.springframework.cloud.sleuth.instrument.async.LazyTraceThreadPoolTaskExecutor@549ac12c
    1835794313
    org.springframework.cloud.sleuth.instrument.async.LazyTraceThreadPoolTaskExecutor@549ac12c
    BanGanExpert
        14
    BanGanExpert  
       2022-07-13 15:38:22 +08:00
    把你自己的 test 生成的 class 字节码反编译或者直接贴出来分析,这个明显属于异常行为了呀,现在只有的代码片段理论上看不可能出现这种行为
    BanGanExpert
        15
    BanGanExpert  
       2022-07-13 15:40:46 +08:00
    如果反编译分析出来没有问题,就必须自己一行行 debug 去看下 JVM 运行时的行为了
    ArthurTsang
        16
    ArthurTsang  
       2022-07-13 16:16:02 +08:00
    你的确是有 2 个 ThreadPoolTaskExecutor 对象吧? 一个是 @Bean, 一个是手动调用方法 new 出来的
    JinTianYi456
        17
    JinTianYi456  
    OP
       2022-07-13 16:23:39 +08:00
    @ArthurTsang #16 看 test code ,没有 2 个啊。再说 Configuration#proxyBeanMethods
    lancelee01
        18
    lancelee01  
       2022-07-13 17:07:20 +08:00
    @ArthurTsang 楼主用的的确没有用 Spring 管理的 bean ,而是通过手动调用 asyncServiceExecutor 方法创建的,但是两次打印的还是同一个对象
    lancelee01
        19
    lancelee01  
       2022-07-13 17:08:07 +08:00
    @JinTianYi456 改成 Spring-boot 环境两次还是一样,你是不是看错了啊。
    ApplicationContext context = SpringApplication.run(App.class);
    var pool = context.getBean(ExecutorConfig.class).asyncServiceExecutor();
    JinTianYi456
        20
    JinTianYi456  
    OP
       2022-07-13 17:13:20 +08:00
    @lancelee01 #18 是 spring 管理的,请看 Configuration#proxyBeanMethods 的说明
    2. 即使 `手动调用 asyncServiceExecutor` 我也只调用了一次啊
    Aruforce
        21
    Aruforce  
       2022-07-13 17:18:02 +08:00
    @JinTianYi456 看了下 sleuth 的源码... sleuth 做了线程池 bean name = taskExecutor 的 wrap 。
    看着像是 DefaultAsyncConfigurerSupport 这个类 做的业务逻辑。。
    lancelee01
        22
    lancelee01  
       2022-07-13 17:20:24 +08:00
    @JinTianYi456 你把你的工程打个包发下吧
    yhvictor
        23
    yhvictor  
       2022-07-13 17:39:32 +08:00
    查了下 source code: https://github.com/openjdk/jdk/blob/6e18883d8ffd9a7b7d495da05e9859dc1d1a2677/src/java.base/share/classes/java/lang/Object.java#L257

    估计是对象是一个对象,但是在两次之间发生了 field 的修改,导致 hashcode 不同
    siweipancc
        24
    siweipancc  
       2022-07-14 11:40:29 +08:00 via iPhone
    做个实验,Object.class.cast(executor) .toString()
    JinTianYi456
        25
    JinTianYi456  
    OP
       2022-07-14 21:17:28 +08:00
    JinTianYi456
        26
    JinTianYi456  
    OP
       2022-07-14 21:25:17 +08:00
    falsemask
        27
    falsemask  
       2022-07-14 23:14:58 +08:00
    @JinTianYi456 我在 toString()方法打了断点,发现这个线程池是通过代理生成的,最后的 toString()方法也是通过代理执行的,最终调用了 invoke0(method, obj, args),然后一个 native 方法,最后 Object 的 toString(),这里有个 args 对象,每次传的值都不相等,感觉可能是这里的问题(不过也不太确定,毕竟 toString 方法是没有参数的) https://s3.bmp.ovh/imgs/2022/07/14/751ee9ec7e911119.png
    redford42
        28
    redford42  
       2022-07-14 23:21:45 +08:00
    收藏了,debug 出来踢我一下
    falsemask
        29
    falsemask  
       2022-07-14 23:26:01 +08:00
    @falsemask 具体也不太确定,对 cglib 不熟,明天上班有空再调一下,或者有人调出来了,踢我一下
    zmal
        30
    zmal  
       2022-07-15 11:44:41 +08:00   ❤️ 1
    debug 了一下大概明白了。
    spring 中注入的 ThreadPoolTaskExecutor 只有一个。但引入了 sleuth 后,它劫持了 executor 的调用。executor 注入 Example.class 后每次调用 executor 内的方法时,都会用这个 executor 包裹一层 LazyTraceThreadPoolTaskExecutor 生成一个新的 LazyTraceThreadPoolTaskExecutor 对象。
    zmal
        31
    zmal  
       2022-07-15 11:51:23 +08:00
    这应该算是早期 LazyTraceThreadPoolTaskExecutor 的一个 bug 。
    用最新版的 sleuth 能看到里边多了个 cache ,保存了 ThreadPoolTaskExecutor 和 LazyTraceThreadPoolTaskExecutor 的映射,用来保证同一个 ThreadPoolTaskExecutor 只生成一个 LazyTraceThreadPoolTaskExecutor 。
    nothingistrue
        32
    nothingistrue  
       2022-07-15 12:28:21 +08:00
    既然是已经用了 Spring ,你好像还用了其他的 AOP ,那么 var executor = executorConfig.asyncServiceExecutor(); 弄出来的 executor ,就不一定是前面代码上的 “new ThreadPoolTaskExecutor();”。或者说,executor 对象的类型,不一定是 ThreadPoolTaskExecutor 。这时候用 ThreadPoolTaskExecutor 的 hashCode 和 toString 方法推断的测试结果,可能不是实际结果。
    JinTianYi456
        33
    JinTianYi456  
    OP
       2022-07-15 14:01:50 +08:00
    @nothingistrue #32 本问题和`那么 var executor = executorConfig.asyncServiceExecutor(); 弄出来的 executor ,就不一定是前面代码上的 “new ThreadPoolTaskExecutor();”`这里,是自己调用`executorConfig.asyncServiceExecutor()`还是 注入 bean `taskExecutor` 是没关系的。test code 里是和这没关系的。
    -----------------
    另外请看 Configuration#proxyBeanMethods 的说明,它就是同一个 bean (按默认配置的话
    JinTianYi456
        34
    JinTianYi456  
    OP
       2022-07-15 14:05:53 +08:00
    感谢 zmal , 大家看 #30 #31 即可,谢谢
    DonaldY
        35
    DonaldY  
       2022-07-15 23:07:53 +08:00
    @JinTianYi456

    #30 #31 这个在 sleuth 的 issue 里有对应的提问。

    但不能解释为什么 每次执行 System.err.println(executor); executor 都不同。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1430 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 26ms · UTC 17:03 · PVG 01:03 · LAX 09:03 · JFK 12:03
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.