問題來源
在閱讀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檢視生成的位元組碼(隻截取關鍵部分)
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicmbw5SN1EjNzMzMzUmN0ETL0UmYi1yMhNDNtYmMzQTL3MmZhBTN2IWL4MzM4IDN4YjM0IjNx8CXwETO0IjNxIzLcdmbw9CXxIDMy8CXw8CXlVXc1l3Lc12bj5yayFGbu5ibkN2Lc9CX6MHc0RHaiojIsJye.png)
測試代碼while
* while死循環測試
private static void m2() {
while (true){
結論
for死循環和while死循環編譯後的位元組碼(編譯器是可以做優化的),完全一模一樣,是以兩者在使用過程中,其實是沒有任何差別。看到這裡是不是有點生氣,但是又想問問什麼源碼那些大佬寫代碼基本上不用while(true),我想主要原因還是早期C語言中for(;;)循環和while(1)編譯生成的位元組碼不一樣,for(;;)生成的位元組碼明顯更加少,一定程度上能節省一些記憶體空間。是以很多java大佬,也是精通各種其他語言的,是以寫法習慣也就延續下來了吧。再者,我在查閱資料的時候也看到有筆者驗證早期的Java編譯器對for死循環編譯生成的位元組碼也是少于while死循環編譯後生成的位元組碼,可能随着編譯器優化能力不斷的增強,現在這兩者在目前廣泛使用的編譯器中已經沒有什麼差別了。