You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot中周期性检查资源锁:Executor/Scheduler替代Awaitility方案咨询

Using ScheduledExecutorService for Periodic Resource Lock Checks in Spring Boot

Great question! While Awaitility is super handy for testing scenarios, using Java's ScheduledExecutorService is absolutely a solid (and often more production-appropriate) alternative for this kind of polling logic in your Spring Boot app. It gives you granular control over task execution and plays nicely with Spring's managed beans.

Key Advantages Over Awaitility

  • Production-focused: Awaitility is designed for testing; ScheduledExecutorService is built for long-running production concurrency use cases.
  • Fine-grained control: You can explicitly manage task cancellation, thread pool sizing, and error handling tailored to your application's needs.
  • Spring integration: Easily configure it as a managed bean to leverage dependency injection and lifecycle management.

Example Implementation

First, let's define a thread-safe resource lock mechanism. We'll use AtomicBoolean for simplicity, but you could swap this out for ReentrantLock if you need more advanced locking features:

import java.util.concurrent.atomic.AtomicBoolean;
import org.springframework.stereotype.Component;

@Component
public class ResourceLockManager {
    private final AtomicBoolean isLocked = new AtomicBoolean(false);

    // Attempt to acquire the lock - returns true if successful
    public boolean tryLock() {
        return isLocked.compareAndSet(false, true);
    }

    // Release the lock (call this in a finally block after updates!)
    public void unlock() {
        isLocked.set(false);
    }

    // Check if the resource is currently locked
    public boolean isLocked() {
        return isLocked.get();
    }
}

Next, create a service that handles resource update notifications and uses ScheduledExecutorService for polling:

import org.springframework.stereotype.Service;
import org.springframework.stereotype.Component;
import javax.annotation.PreDestroy;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicReference;

@Service
public class ResourceUpdateService {
    private final ResourceLockManager lockManager;
    private final ScheduledExecutorService scheduler;
    private static final long POLL_INTERVAL = 100; // Poll every 100ms
    private static final long TIMEOUT = 1000; // Timeout after 1 second

    public ResourceUpdateService(ResourceLockManager lockManager) {
        this.lockManager = lockManager;
        // Configure a single-threaded scheduler (adjust pool size based on your workload)
        this.scheduler = Executors.newSingleThreadScheduledExecutor();
    }

    public void handleResourceUpdateNotification(String resourceId) {
        // Use AtomicReference to safely pass the scheduled task reference to the poll logic
        AtomicReference<ScheduledFuture<?>> scheduledTaskRef = new AtomicReference<>();

        // Define the polling task
        Runnable pollTask = () -> {
            if (!lockManager.isLocked()) {
                // Try to grab the lock
                if (lockManager.tryLock()) {
                    // Cancel the polling task since we've acquired the lock
                    ScheduledFuture<?> task = scheduledTaskRef.get();
                    if (task != null && !task.isDone()) {
                        task.cancel(false);
                    }
                    // Start the actual resource update
                    performResourceUpdate(resourceId);
                }
            }
        };

        // Schedule the periodic polling task (starts immediately)
        ScheduledFuture<?> scheduledTask = scheduler.scheduleAtFixedRate(
            pollTask, 0, POLL_INTERVAL, TimeUnit.MILLISECONDS
        );
        scheduledTaskRef.set(scheduledTask);

        // Schedule a timeout task to cancel polling if we can't get the lock in time
        scheduler.schedule(() -> {
            ScheduledFuture<?> task = scheduledTaskRef.get();
            if (task != null && !task.isDone()) {
                task.cancel(false);
                // Handle timeout (log, queue for retry, etc.)
                System.err.printf("Timeout waiting to acquire lock for resource: %s%n", resourceId);
            }
        }, TIMEOUT, TimeUnit.MILLISECONDS);
    }

    private void performResourceUpdate(String resourceId) {
        try {
            // Replace this with your actual resource update logic
            System.out.printf("Starting update for resource: %s%n", resourceId);
            Thread.sleep(500); // Simulate work
            System.out.printf("Completed update for resource: %s%n", resourceId);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            System.err.printf("Update for resource %s was interrupted%n", resourceId);
        } finally {
            // Always release the lock, even if an error occurs
            lockManager.unlock();
        }
    }

    // Gracefully shut down the scheduler when the Spring context closes
    @PreDestroy
    public void shutdownScheduler() {
        scheduler.shutdown();
        try {
            if (!scheduler.awaitTermination(1, TimeUnit.SECONDS)) {
                scheduler.shutdownNow();
            }
        } catch (InterruptedException e) {
            scheduler.shutdownNow();
        }
    }
}

Critical Implementation Notes

  • Thread Safety: The ResourceLockManager uses AtomicBoolean to ensure lock checks and acquisitions are thread-safe. For more complex scenarios (like lock reentrancy), use ReentrantLock.
  • Task Cancellation: We cancel the polling task immediately after acquiring the lock or hitting the timeout to avoid unnecessary resource usage.
  • Graceful Shutdown: The @PreDestroy method ensures the scheduler is shut down properly when the Spring application stops, preventing thread leaks.
  • Error Handling: The performResourceUpdate method uses a finally block to guarantee the lock is released, even if an exception occurs during the update.

Alternative: Spring's @Scheduled (For Recurring Tasks)

If you were dealing with recurring polling tasks instead of ad-hoc notification-triggered checks, Spring's @Scheduled annotation could be a simpler option. However, for your use case (polling initiated by a notification), ScheduledExecutorService offers far more flexibility.

Comparison to Awaitility

Awaitility's syntax is concise for testing, but ScheduledExecutorService gives you full control over the execution environment. For production code, it's the better choice because it's designed for long-running applications and integrates seamlessly with Spring's lifecycle management.

内容的提问来源于stack exchange,提问作者RaRa

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.13 08:14:18