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

关于一段 Java 代码的疑问,求解答

  •  
  •   seedscoder · 2023-12-16 00:42:44 +08:00 · 1935 次点击
    这是一个创建于 372 天前的主题,其中的信息可能已经有所发展或是发生改变。

    我有如下一段 JAVA 代码,为什么 4 次都是输出 0 ?我第一反应应该是递增输出,求大佬们解答

    public class DemoApp {
    
        private static final AtomicInteger nextIndex = new AtomicInteger();
    
        public static final int VARIABLES_TO_REMOVE_INDEX = nextVariableIndex();
    
        public static int nextVariableIndex() {
            return nextIndex.getAndIncrement();
        }
    
    
        public static void main(String[] args) {
            System.out.println(VARIABLES_TO_REMOVE_INDEX);
            System.out.println(VARIABLES_TO_REMOVE_INDEX);
            System.out.println(VARIABLES_TO_REMOVE_INDEX);
            System.out.println(VARIABLES_TO_REMOVE_INDEX);
        }
    }
    
    

    上面这段代码其实摘抄自 netty InternalThreadLocalMap

    public final class InternalThreadLocalMap extends UnpaddedInternalThreadLocalMap {
        private static final ThreadLocal<InternalThreadLocalMap> slowThreadLocalMap =
                new ThreadLocal<InternalThreadLocalMap>();
        
        private static final AtomicInteger nextIndex = new AtomicInteger();
        
        
        // Internal use only.
        public static final int VARIABLES_TO_REMOVE_INDEX = nextVariableIndex();
    
        
        private static final int DEFAULT_ARRAY_LIST_INITIAL_CAPACITY = 8;
        
        
            public static int nextVariableIndex() {
            int index = nextIndex.getAndIncrement();
            if (index >= ARRAY_LIST_CAPACITY_MAX_SIZE || index < 0) {
                nextIndex.set(ARRAY_LIST_CAPACITY_MAX_SIZE);
                throw new IllegalStateException("too many thread-local indexed variables");
            }
            return index;
        }
    
    

    因为看到网上都说 InternalThreadLocalMap 存储元素的数组 0 号位置是一个类型为 set 的元素,所以对这段代码有点疑惑,那为什么 VARIABLES_TO_REMOVE_INDEX 不直接写 0 ?

    9 条回复    2023-12-22 15:33:27 +08:00
    geelaw
        1
    geelaw  
       2023-12-16 00:51:31 +08:00 via iPhone   ❤️ 1
    因为直接写 0 不会导致 nextIndex 增加到 1 ?
    revers
        2
    revers  
       2023-12-16 00:59:04 +08:00   ❤️ 1
    nextVariableIndex() 只在赋值的时候使用了一次,所有 VARIABLES_TO_REMOVE_INDEX 值为 0 ,但是 nextIndex 值变为了 1
    seedscoder
        3
    seedscoder  
    OP
       2023-12-16 00:59:49 +08:00
    @geelaw 我看网上大佬的源码分析文章,都是说数组 0 号位置元素的类型是 set ,看起来不需要增加 1 ;

    另外我问了一下 ChatGPT ,它的回答:


    ```

    VARIABLES_TO_REMOVE_INDEX 被定义为一个常量,其实质上是在类加载的时候通过 nextVariableIndex() 方法获取的值。在 Java 中,类加载时会执行静态代码块,这个时候 nextVariableIndex() 方法被调用,得到的结果会被赋值给 VARIABLES_TO_REMOVE_INDEX 。由于 VARIABLES_TO_REMOVE_INDEX 是一个 final 常量,它的值在类加载后就不会发生变化。

    这样设计的一个优势是,在运行时获取这个值只需要一次计算,而不必每次调用的时候都重新计算。这有助于提高性能,特别是在需要频繁访问这个索引的情况下。

    所以,尽管 nextVariableIndex() 方法被调用,但由于其结果在类加载时就确定了,并且是不可变的,因此被定义为一个常量。这样做的目的是为了提高效率,同时保证这个值在运行时是不可变的。
    ```



    那反正只运行一次,跟直接写 0 好像没多大区别?
    seedscoder
        4
    seedscoder  
    OP
       2023-12-16 01:01:37 +08:00
    @revers 好的,感谢大佬
    yumenawei
        5
    yumenawei  
       2023-12-16 07:09:33 +08:00 via Android
    @livid 很好奇三楼这种情况你怎么处理?
    xuanbg
        6
    xuanbg  
       2023-12-16 10:08:22 +08:00
    VARIABLES_TO_REMOVE_INDEX 初始化后就没被改变过
    L0L
        7
    L0L  
       2023-12-16 13:59:17 +08:00
    chatGPT:
    在你给出的 Java 代码片段中,VARIABLES_TO_REMOVE_INDEX 是一个静态常量,它在类加载时被初始化。静态常量初始化是在类首次被加载到 JVM 时完成的,且该初始化过程只会执行一次。

    java
    复制
    public static final int VARIABLES_TO_REMOVE_INDEX = nextVariableIndex();
    这行代码调用 nextVariableIndex() 方法,该方法通过 nextIndex.getAndIncrement() 获取当前的值并递增 nextIndex 。然而,VARIABLES_TO_REMOVE_INDEX 在初始化后就不会再改变,它保留了 nextVariableIndex() 方法第一次调用时的返回值,即 0 。

    当你在 main 方法中多次打印 VARIABLES_TO_REMOVE_INDEX 时,你实际上每次都在打印同一个被初始化时确定的常量值,即 0 。即使 nextIndex 的值在每次调用 nextVariableIndex() 时都会递增,VARIABLES_TO_REMOVE_INDEX 仍然保持不变,因为它是一个 final 常量。

    至于为什么 VARIABLES_TO_REMOVE_INDEX 不直接写成 0 ,而是通过调用方法来初始化,可能是因为 Netty 的设计者想要保持代码的一致性和可维护性。如果索引的生成逻辑需要变更,他们只需修改 nextVariableIndex() 方法的实现即可,而不需要去手动改动硬编码的值。此外,如果后续添加了新的索引,则可以确保 VARIABLES_TO_REMOVE_INDEX 的值是正确的,而不会与其他索引发生冲突。

    在 InternalThreadLocalMap 的实际使用上,将 VARIABLES_TO_REMOVE_INDEX 设置成一个通过递增得到的值,可能是为了确保这个索引是唯一的,并且如果 InternalThreadLocalMap 类中还有其他静态变量也通过 nextVariableIndex() 初始化,那么每个静态变量都将得到一个不同的索引值。

    最后,关于数组 0 号位置是一个类型为 set 的元素,这是 Netty 的内部实现细节,它可能是为了某种特定的优化或者内部使用的便捷性。由于 VARIABLES_TO_REMOVE_INDEX 是 public static final 的,假如它被设为了 0 ,那么它就不能代表特殊的内部状态或者其他索引了,因此可以推断 Netty 设计者有意将其设置为通过 nextVariableIndex() 方法动态生成的值。
    jaffe24
        8
    jaffe24  
       2023-12-16 23:55:37 +08:00
    这主要涉及到类加载机制
    dongisking
        9
    dongisking  
       2023-12-22 15:33:27 +08:00
    ```
    package ClassLoader;

    import java.util.concurrent.atomic.AtomicInteger;

    public class InternalThreadLocalMap {
    private static final AtomicInteger nextIndex = new AtomicInteger();

    public static final int VARIABLES_TO_REMOVE_INDEX = nextVariableIndex();

    public static int nextVariableIndex() {
    System.out.println("test");
    return nextIndex.getAndIncrement();
    }

    public static void main(String[] args) {
    System.out.println("33");
    System.out.println(VARIABLES_TO_REMOVE_INDEX);
    System.out.println(VARIABLES_TO_REMOVE_INDEX);
    System.out.println(VARIABLES_TO_REMOVE_INDEX);
    System.out.println(VARIABLES_TO_REMOVE_INDEX);
    }
    }

    ```
    输出
    test
    33
    0
    0
    0
    0
    VARIABLES_TO_REMOVE_INDEX 在类加载的时候已经初始化了,后面没有发生过变化了
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2967 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 34ms · UTC 13:12 · PVG 21:12 · LAX 05:12 · JFK 08:12
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.