背景
最近有接触很多的中间件源码,注意到部分的循环都写成了for(;;),说实话以前也有注意过。在平常自己写业务代码很多死循环用的是while(true),有点沿袭写C语言时候用的while(1)。查找资料后发现这两种在某些情况下是有区别的,下面将详细分析。
验证
通过javap命令查看编译后的指令,来分析两种实现的不同。
源码
//LoopTestFor.java
public class LoopTestFor {
public static void main(String[] args) {
for(;;) {
System.out.println("OK");
}
}
}
//LoopTestWhile.java
public class LoopTestWhile {
public static void main(String[] args) {
while(true) {
System.out.println("OK");
}
}
}
反编译后的结果
$javap -c -v -l LoopTestWhile.class
Classfile /export/code/LoopTestWhile.class
Last modified 2021-2-24; size 445 bytes
MD5 checksum 754de9c534c56e5928411a5f3a804375
Compiled from "LoopTestWhile.java"
public class LoopTestWhile
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #6.#16 // java/lang/Object."":()V
#2 = Fieldref #17.#18 // java/lang/System.out:Ljava/io/PrintStream;
#3 = String #19 // OK
#4 = Methodref #20.#21 // java/io/PrintStream.println:(Ljava/lang/String;)V
#5 = Class #22 // LoopTestWhile
#6 = Class #23 // java/lang/Object
#7 = Utf8
#8 = Utf8 ()V
#9 = Utf8 Code
#10 = Utf8 LineNumberTable
#11 = Utf8 main
#12 = Utf8 ([Ljava/lang/String;)V
#13 = Utf8 StackMapTable
#14 = Utf8 SourceFile
#15 = Utf8 LoopTestWhile.java
#16 = NameAndType #7:#8 // "":()V
#17 = Class #24 // java/lang/System
#18 = NameAndType #25:#26 // out:Ljava/io/PrintStream;
#19 = Utf8 OK
#20 = Class #27 // java/io/PrintStream
#21 = NameAndType #28:#29 // println:(Ljava/lang/String;)V
#22 = Utf8 LoopTestWhile
#23 = Utf8 java/lang/Object
#24 = Utf8 java/lang/System
#25 = Utf8 out
#26 = Utf8 Ljava/io/PrintStream;
#27 = Utf8 java/io/PrintStream
#28 = Utf8 println
#29 = Utf8 (Ljava/lang/String;)V
{
public LoopTestWhile();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."":()V
4: return
LineNumberTable:
line 2: 0
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=1, args_size=1
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #3 // String OK
5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: goto 0
LineNumberTable:
line 6: 0
StackMapTable: number_of_entries = 1
frame_type = 0 /* same */
}
SourceFile: "LoopTestWhile.java"
$javap -c -v LoopTestFor.class
Classfile /export/code/LoopTestFor.class
Last modified 2021-2-24; size 441 bytes
MD5 checksum 02520f26cdea541889c75365f4504060
Compiled from "LoopTestFor.java"
public class LoopTestFor
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #6.#16 // java/lang/Object."":()V
#2 = Fieldref #17.#18 // java/lang/System.out:Ljava/io/PrintStream;
#3 = String #19 // OK
#4 = Methodref #20.#21 // java/io/PrintStream.println:(Ljava/lang/String;)V
#5 = Class #22 // LoopTestFor
#6 = Class #23 // java/lang/Object
#7 = Utf8
#8 = Utf8 ()V
#9 = Utf8 Code
#10 = Utf8 LineNumberTable
#11 = Utf8 main
#12 = Utf8 ([Ljava/lang/String;)V
#13 = Utf8 StackMapTable
#14 = Utf8 SourceFile
#15 = Utf8 LoopTestFor.java
#16 = NameAndType #7:#8 // "":()V
#17 = Class #24 // java/lang/System
#18 = NameAndType #25:#26 // out:Ljava/io/PrintStream;
#19 = Utf8 OK
#20 = Class #27 // java/io/PrintStream
#21 = NameAndType #28:#29 // println:(Ljava/lang/String;)V
#22 = Utf8 LoopTestFor
#23 = Utf8 java/lang/Object
#24 = Utf8 java/lang/System
#25 = Utf8 out
#26 = Utf8 Ljava/io/PrintStream;
#27 = Utf8 java/io/PrintStream
#28 = Utf8 println
#29 = Utf8 (Ljava/lang/String;)V
{
public LoopTestFor();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."":()V
4: return
LineNumberTable:
line 2: 0
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=1, args_size=1
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #3 // String OK
5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: goto 0
LineNumberTable:
line 6: 0
StackMapTable: number_of_entries = 1
frame_type = 0 /* same */
}
SourceFile: "LoopTestFor.java"
环境 jdk_1.8.0_201, javac 1.8.0_201
重点在如下部分:
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=1, args_size=1
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #3 // String OK
5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: goto 0
LineNumberTable:
其中8: goto 0即表示循环,回到第0行代码 0: getstatic。可以看到两种循环最终实现是一致的。这是为什么呢?因为编译器对此进行了优化,所以最终效果是一样的。
结论
可以看到默认在HotSpot下经过优化,两种循环的实现是一致的。但是功能组件可能有跨平台的情况,包括JUC中java.util.concurrent.ConcurrentMap#compute,保持兼容性更强的for(;;)是比较好的选择。根据解释while(true),在没有编译优化的情况下,会认为是一个表达式,而进行一次计算,导致实际操作指令比for(;;)多。当然我们从上也看到,一般场景,二者具体实现是一样的,没有区别。
以上就是本期的内容,以做备忘。