Intro LockSupport.parkNanos

Intro LockSupport.parkNanos

Intro LockSupport.parkNanos

Intro LockSupport.parkNanos

這行程式碼是在使用 LockSupport.parkNanos() 來實現執行緒的精確暫停,通常用於重試機制中。
程式碼分析
LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(retryMillisecond / 1000));
執行步驟:

retryMillisecond / 1000 - 將毫秒轉換為秒
TimeUnit.SECONDS.toNanos() - 將秒轉換為奈秒
LockSupport.parkNanos() - 暫停當前執行緒指定的奈秒數

LockSupport.parkNanos() 的特點

  1. 比 Thread.sleep() 更精確
    // Thread.sleep() - 最小單位是毫秒
    Thread.sleep(1000); // 暫停 1 秒

// LockSupport.parkNanos() - 最小單位是奈秒
LockSupport.parkNanos(1_000_000_000L); // 暫停 1 秒,更精確
2. 可以被中斷喚醒
public class RetryExample {

public void retryOperation() {
    int maxRetries = 5;
    long retryMillisecond = 1000; // 1 秒重試間隔
    
    for (int i = 0; i < maxRetries; i++) {
        try {
            performOperation();
            return; // 成功就退出
            
        } catch (Exception e) {
            if (i == maxRetries - 1) {
                throw e; // 最後一次重試失敗就拋出異常
            }
            
            System.out.println("第 " + (i+1) + " 次嘗試失敗," + 
                             retryMillisecond + "ms 後重試...");
            
            // 等待後重試
            LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(retryMillisecond));
        }
    }
}

}
實際應用場景

  1. 資料庫重連機制
    @Service
    public class DatabaseService {
    private static final long RETRY_INTERVAL_MS = 2000; // 2秒重試
    private static final int MAX_RETRIES = 3;

    public Connection getConnection() {
    for (int attempt = 1; attempt <= MAX_RETRIES; attempt++) {
    try {
    return DriverManager.getConnection(url, username, password);

    } catch (SQLException e) {
    if (attempt == MAX_RETRIES) {
    throw new RuntimeException(“無法連接資料庫,已重試 “ + MAX_RETRIES + “ 次”, e);
    }

    logger.warn(“資料庫連接失敗 (嘗試 {}/{}), {}ms 後重試”,
    attempt, MAX_RETRIES, RETRY_INTERVAL_MS);

    // 使用 LockSupport 精確等待
    LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(RETRY_INTERVAL_MS));
    }
    }
    return null; // 不會執行到這裡
    }

}
2. HTTP API 重試
@Component
public class HttpRetryClient {

public String callApi(String url, int maxRetries, long retryIntervalMs) {
    Exception lastException = null;
    
    for (int i = 0; i < maxRetries; i++) {
        try {
            return restTemplate.getForObject(url, String.class);
            
        } catch (RestClientException e) {
            lastException = e;
            
            if (i < maxRetries - 1) { // 不是最後一次嘗試
                logger.warn("API 調用失敗 (第 {}/{} 次), 錯誤: {}", 
                          i+1, maxRetries, e.getMessage());
                
                // 漸進式退避:每次重試間隔加倍
                long currentInterval = retryIntervalMs * (1L << i);
                LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(currentInterval));
            }
        }
    }
    
    throw new RuntimeException("API 調用失敗,已重試 " + maxRetries + " 次", lastException);
}

}
3. 分散式鎖重試
@Component
public class DistributedLockService {

public boolean acquireLock(String lockKey, long timeoutMs, long retryIntervalMs) {
    long startTime = System.currentTimeMillis();
    long endTime = startTime + timeoutMs;
    
    while (System.currentTimeMillis() < endTime) {
        if (tryLock(lockKey)) {
            return true; // 成功獲取鎖
        }
        
        // 檢查是否還有時間重試
        long remainingTime = endTime - System.currentTimeMillis();
        if (remainingTime <= retryIntervalMs) {
            break; // 剩餘時間不足以進行下一次重試
        }
        
        // 等待後重試
        LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(retryIntervalMs));
    }
    
    return false; // 超時未能獲取鎖
}

private boolean tryLock(String lockKey) {
    // 嘗試獲取分散式鎖的實現
    return redisTemplate.opsForValue().setIfAbsent(lockKey, "locked", Duration.ofMinutes(1));
}

}
為什麼不直接用 Thread.sleep()?

  1. 精度問題
    // Thread.sleep() - 毫秒精度,可能不夠精確
    Thread.sleep(100); // 100ms,但實際可能是 100-115ms

// LockSupport.parkNanos() - 奈秒精度,更準確
LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(100)); // 更接近實際 100ms
2. 響應中斷的方式不同
public class InterruptionExample {

public void usingThreadSleep() {
    try {
        Thread.sleep(5000); // 如果被中斷會拋出 InterruptedException
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt(); // 需要恢復中斷狀態
    }
}

public void usingLockSupport() {
    LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(5)); // 如果被中斷會直接返回
    
    if (Thread.interrupted()) {
        // 檢查是否被中斷
        System.out.println("執行緒被中斷了");
    }
}

}
改進建議

  1. 修正原始程式碼的邏輯問題
    // ❌ 原始程式碼有邏輯問題
    LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(retryMillisecond / 1000));
    // retryMillisecond 是毫秒,除以 1000 變成秒,再轉奈秒,相當於把精度降低了

// ✅ 正確的寫法
LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(retryMillisecond));
// 直接將毫秒轉為奈秒
2. 更完整的重試機制
public class ImprovedRetryMechanism {

public <T> T executeWithRetry(Supplier<T> operation, 
                             int maxRetries, 
                             long initialDelayMs,
                             boolean useExponentialBackoff) {
    Exception lastException = null;
    
    for (int attempt = 0; attempt < maxRetries; attempt++) {
        try {
            return operation.get();
            
        } catch (Exception e) {
            lastException = e;
            
            if (attempt == maxRetries - 1) {
                break; // 最後一次嘗試,不再等待
            }
            
            // 計算等待時間
            long delayMs = useExponentialBackoff 
                ? initialDelayMs * (1L << attempt)  // 指數退避
                : initialDelayMs;                   // 固定間隔
            
            logger.warn("操作失敗 (第 {}/{} 次嘗試), {}ms 後重試. 錯誤: {}", 
                      attempt + 1, maxRetries, delayMs, e.getMessage());
            
            // 精確等待
            LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(delayMs));
        }
    }
    
    throw new RuntimeException("操作失敗,已重試 " + maxRetries + " 次", lastException);
}

}

// 使用範例
public void example() {
ImprovedRetryMechanism retry = new ImprovedRetryMechanism();

String result = retry.executeWithRetry(
    () -> callRemoteService(),  // 要重試的操作
    3,                          // 最多 3 次
    1000,                       // 初始間隔 1 秒
    true                        // 使用指數退避
);

}
3. 加入可取消機制
public class CancellableRetry {

public <T> T executeWithRetry(Supplier<T> operation, 
                             int maxRetries, 
                             long retryIntervalMs,
                             AtomicBoolean cancelled) {
    for (int attempt = 0; attempt < maxRetries; attempt++) {
        // 檢查是否被取消
        if (cancelled.get()) {
            throw new RuntimeException("重試操作被取消");
        }
        
        try {
            return operation.get();
            
        } catch (Exception e) {
            if (attempt == maxRetries - 1) {
                throw new RuntimeException("重試失敗", e);
            }
            
            // 分段等待,以便及時響應取消
            long remainingDelay = retryIntervalMs;
            while (remainingDelay > 0 && !cancelled.get()) {
                long sleepTime = Math.min(remainingDelay, 100); // 每 100ms 檢查一次
                LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(sleepTime));
                remainingDelay -= sleepTime;
            }
        }
    }
    return null;
}

}
總結:這行程式碼的主要用途是在重試機制中提供精確的等待時間,比 Thread.sleep() 更準確
,但原始程式碼的單位轉換邏輯有問題,應該直接使用毫秒轉奈秒。