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() 的特點
- 比 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));
}
}
}
}
實際應用場景
資料庫重連機制
@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()?
- 精度問題
// 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("執行緒被中斷了");
}
}
}
改進建議
- 修正原始程式碼的邏輯問題
// ❌ 原始程式碼有邏輯問題
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() 更準確
,但原始程式碼的單位轉換邏輯有問題,應該直接使用毫秒轉奈秒。