天天看點

for(;;)和while(true)的差別

問題來源

在閱讀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);

比較

Java代碼在編譯後都會裝換為虛拟機可以識别的位元組碼,我們通過編譯器對兩者生成的位元組碼從原理是來觀察兩者的差別

測試代碼for

package com.liziba.jsw;

 * <p>

 *  for死循環測試

 * </p>

 *

 * @Author: Liziba

 * @Date: 2021/6/21 11:36

public class Test {

    private static void m1() {

通過 javap -v Test.class檢視生成的位元組碼(隻截取關鍵部分)

for(;;)和while(true)的差別

測試代碼while

 *  while死循環測試

    private static void m2() {

        while (true){

for(;;)和while(true)的差別

結論

for死循環和while死循環編譯後的位元組碼(編譯器是可以做優化的),完全一模一樣,是以兩者在使用過程中,其實是沒有任何差別。看到這裡是不是有點生氣,但是又想問問什麼源碼那些大佬寫代碼基本上不用while(true),我想主要原因還是早期C語言中for(;;)循環和while(1)編譯生成的位元組碼不一樣,for(;;)生成的位元組碼明顯更加少,一定程度上能節省一些記憶體空間。是以很多java大佬,也是精通各種其他語言的,是以寫法習慣也就延續下來了吧。再者,我在查閱資料的時候也看到有筆者驗證早期的Java編譯器對for死循環編譯生成的位元組碼也是少于while死循環編譯後生成的位元組碼,可能随着編譯器優化能力不斷的增強,現在這兩者在目前廣泛使用的編譯器中已經沒有什麼差別了。