for(;;)和while(true)的区别
海拥 人气:01、问题来源
在阅读Java
的JDK
源码时,发现大部分写源码的大佬多采用for(;;)
的方式来死循环,比如说AQS(AbstractQueuedSynchronizer)
中大量使用的自旋的方式获取共享状态。
/** * 通过“死循环”的方式来正确的添加节点 */ private Node enq(final Node node) { // 不断循环,直至CAS插入节点成功 for (;;) { Node t = tail; if (t == null) { // 当尾节点为null,此时需要初始化头节点和尾节点 if (compareAndSetHead(new Node())) tail = head; } else { // 插入节点前驱节点指向原先尾节点 node.prev = t; // CAS插入至同步队列的尾节点 if (compareAndSetTail(t, node)) { t.next = node; return t; } } } }
/** * “死循环”获取同步状态,并且当前仅当前驱节点是头节点是才能够尝试获取同步状态 */ final boolean acquireQueued(final Node node, int arg) { boolean failed = true; try { boolean interrupted = false; // 不断循环 for (;;) { // 获取当前节点的前驱节点,如果前驱节点为null将会抛出空指针异常 final Node p = node.predecessor(); // 如果当前节点的前驱节点是头节点,尝试获取同步状态 if (p == head && tryAcquire(arg)) { // 设置当前节点为头节点,并且将节点线程和节点的前驱节点置为null,help GC setHead(node); p.next = null; // help GC failed = false; return interrupted; } // 如果不符合条件,则判断当前节点前驱节点的waitStatus状态来决定是否需要挂起LockSupport.park(this); if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) interrupted = true; } } finally { // 失败则取消 if (failed) cancelAcquire(node); } }
2、比较
Java
代码在编译后都会装换为虚拟机可以识别的字节码,我们通过编译器对两者生成的字节码从原理是来观察两者的区别
2.1 测试代码for
package com.liziba.jsw; /** * <p> * for死循环测试 * </p> * * @Author: Liziba * @Date: 2021/6/21 11:36 */ public class Test { private static void m1() { for (;;) { } } }
通过 javap -v Test.class
查看生成的字节码(只截取关键部分)
2.2 测试代码while
package com.liziba.jsw; /** * <p> * while死循环测试 * </p> * * @Author: Liziba * @Date: 2021/6/21 11:36 */ public class Test { private static void m2() { while (true){ } } }
通过 javap -v Test.class
查看生成的字节码(只截取关键部分)
3、结论
for
死循环和while
死循环编译后的字节码(编译器是可以做优化的),完全一模一样,所以两者在使用过程中,其实是没有任何区别。看到这里是不是有点生气,但是又想问问什么源码那些大佬写代码基本上不用while(true)
,我想主要原因还是早期C语言中for(;;)
循环和while(1)
编译生成的字节码不一样,for(;;)
生成的字节码明显更加少,一定程度上能节省一些内存空间。所以很多java
大佬,也是精通各种其他语言的,因此写法习惯也就延续下来了吧。再者,我在查阅资料的时候也看到有笔者验证早期的Java
编译器对for
死循环编译生成的字节码也是少于while
死循环编译后生成的字节码,可能随着编译器优化能力不断的增强,现在这两者在目前广泛使用的编译器中已经没有什么区别了。
加载全部内容