後端工程師的後端程式筆記(第二部)

後端 更新於 Oct 9, 2026

重點:執行緒池、非同步程式設計、快取、訊息佇列、分散式交易

目錄

  1. 執行緒池與 Executor 框架
  2. CompletableFuture 與非同步程式設計
  3. 快取策略
  4. 訊息佇列模式
  5. 分散式交易
  6. 協調用的工具類
  7. 優雅關閉
  8. 背壓處理
  9. 面試常見問題

執行緒池與 Executor 框架

1. ThreadPoolExecutor 細看

java
import java.util.concurrent.*;

public class ThreadPoolDemo {

    /**
     * ThreadPoolExecutor parameters:
     * - corePoolSize: minimum threads always kept alive
     * - maximumPoolSize: maximum threads allowed
     * - keepAliveTime: idle time before non-core threads terminate
     * - workQueue: queue to hold tasks before execution
     * - threadFactory: creates new threads
     * - handler: rejection policy when queue is full
     */
    public static ThreadPoolExecutor createCustomPool() {
        return new ThreadPoolExecutor(
            4,                              // corePoolSize
            8,                              // maximumPoolSize
            60L, TimeUnit.SECONDS,          // keepAliveTime
            new LinkedBlockingQueue<>(100), // workQueue with capacity
            new CustomThreadFactory("worker"),
            new ThreadPoolExecutor.CallerRunsPolicy() // rejection policy
        );
    }

    static class CustomThreadFactory implements ThreadFactory {
        private final String namePrefix;
        private final AtomicInteger counter = new AtomicInteger(0);

        CustomThreadFactory(String namePrefix) {
            this.namePrefix = namePrefix;
        }

        @Override
        public Thread newThread(Runnable r) {
            Thread t = new Thread(r, namePrefix + "-" + counter.incrementAndGet());
            t.setDaemon(false);
            t.setUncaughtExceptionHandler((thread, ex) -> {
                System.err.println("Thread " + thread.getName() + " failed: " + ex.getMessage());
            });
            return t;
        }
    }
}

2. 拒絕策略比較

策略 行為 適用場景
AbortPolicy 丟出 RejectedExecutionException 快速失敗,讓監控告警
CallerRunsPolicy 由呼叫端的執行緒自己執行該任務 背壓,把生產端拖慢
DiscardPolicy 安靜地丟掉任務 不關鍵的任務
DiscardOldestPolicy 丟掉佇列中最舊的任務 寧可要最新的資料
java
// Custom rejection handler with logging and metrics
public class LoggingRejectionHandler implements RejectedExecutionHandler {

    private final AtomicLong rejectedCount = new AtomicLong(0);

    @Override
    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
        rejectedCount.incrementAndGet();

        // Log details
        System.err.println("Task rejected. Pool size: " + executor.getPoolSize() +
                          ", Active: " + executor.getActiveCount() +
                          ", Queue size: " + executor.getQueue().size());

        // Option 1: Block and wait
        try {
            executor.getQueue().put(r);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new RejectedExecutionException("Interrupted while waiting", e);
        }

        // Option 2: Execute in caller thread (like CallerRunsPolicy)
        // if (!executor.isShutdown()) {
        //     r.run();
        // }
    }

    public long getRejectedCount() {
        return rejectedCount.get();
    }
}

3. 佇列型別怎麼選

佇列型別 行為 適用場景
LinkedBlockingQueue 無界或有界的 FIFO 通用
ArrayBlockingQueue 有界,底層是陣列 記憶體有限
SynchronousQueue 沒有容量,直接交棒 優先把執行緒開滿
PriorityBlockingQueue 依優先序排列 任務有優先順序
java
// SynchronousQueue: Creates new thread for each task (up to max)
// Good for short-lived tasks with unpredictable load
ThreadPoolExecutor cachedLikePool = new ThreadPoolExecutor(
    0, Integer.MAX_VALUE,
    60L, TimeUnit.SECONDS,
    new SynchronousQueue<>()
);

// Bounded queue: Predictable memory usage
ThreadPoolExecutor boundedPool = new ThreadPoolExecutor(
    4, 8,
    60L, TimeUnit.SECONDS,
    new ArrayBlockingQueue<>(100)
);

4. 監控執行緒池的健康狀況

java
public class ThreadPoolMonitor {

    private final ScheduledExecutorService monitor = Executors.newSingleThreadScheduledExecutor();

    public void startMonitoring(ThreadPoolExecutor pool, long intervalMs) {
        monitor.scheduleAtFixedRate(() -> {
            System.out.println("=== Thread Pool Stats ===");
            System.out.println("Pool Size: " + pool.getPoolSize());
            System.out.println("Active Threads: " + pool.getActiveCount());
            System.out.println("Queued Tasks: " + pool.getQueue().size());
            System.out.println("Completed Tasks: " + pool.getCompletedTaskCount());
            System.out.println("Core Pool Size: " + pool.getCorePoolSize());
            System.out.println("Max Pool Size: " + pool.getMaximumPoolSize());
        }, 0, intervalMs, TimeUnit.MILLISECONDS);
    }

    public void stop() {
        monitor.shutdown();
    }
}

CompletableFuture 與非同步程式設計

1. CompletableFuture 的基本用法

java
import java.util.concurrent.*;

public class CompletableFutureDemo {

    private final ExecutorService executor = Executors.newFixedThreadPool(4);

    // Basic async execution
    public CompletableFuture<String> fetchDataAsync(String id) {
        return CompletableFuture.supplyAsync(() -> {
            // Simulate API call
            sleep(100);
            return "Data for " + id;
        }, executor);
    }

    // Chain transformations
    public CompletableFuture<Integer> processData(String id) {
        return fetchDataAsync(id)
            .thenApply(data -> data.toUpperCase())      // Transform
            .thenApply(data -> data.length());           // Transform again
    }

    // Chain async operations
    public CompletableFuture<String> fetchAndEnrich(String id) {
        return fetchDataAsync(id)
            .thenCompose(data -> enrichDataAsync(data)); // Async chain
    }

    // Combine two independent futures
    public CompletableFuture<String> fetchBothAndCombine(String id1, String id2) {
        CompletableFuture<String> future1 = fetchDataAsync(id1);
        CompletableFuture<String> future2 = fetchDataAsync(id2);

        return future1.thenCombine(future2, (data1, data2) -> {
            return data1 + " + " + data2;
        });
    }

    private CompletableFuture<String> enrichDataAsync(String data) {
        return CompletableFuture.supplyAsync(() -> "Enriched: " + data, executor);
    }

    private void sleep(long ms) {
        try { Thread.sleep(ms); } catch (InterruptedException e) { Thread.currentThread().interrupt(); }
    }
}

2. 錯誤處理

java
public class CompletableFutureErrorHandling {

    // Handle exceptions
    public CompletableFuture<String> fetchWithFallback(String id) {
        return fetchDataAsync(id)
            .exceptionally(ex -> {
                System.err.println("Error: " + ex.getMessage());
                return "Default Value";  // Fallback
            });
    }

    // Handle both success and failure
    public CompletableFuture<String> fetchWithHandle(String id) {
        return fetchDataAsync(id)
            .handle((result, ex) -> {
                if (ex != null) {
                    return "Error: " + ex.getMessage();
                }
                return "Success: " + result;
            });
    }

    // Recover with another async operation
    public CompletableFuture<String> fetchWithAsyncFallback(String id) {
        return fetchDataAsync(id)
            .exceptionallyCompose(ex -> {
                System.err.println("Primary failed, trying backup...");
                return fetchFromBackupAsync(id);
            });
    }

    // Timeout handling
    public CompletableFuture<String> fetchWithTimeout(String id) {
        return fetchDataAsync(id)
            .orTimeout(5, TimeUnit.SECONDS)
            .exceptionally(ex -> {
                if (ex instanceof TimeoutException) {
                    return "Timeout - using cached value";
                }
                throw new CompletionException(ex);
            });
    }

    private CompletableFuture<String> fetchDataAsync(String id) {
        return CompletableFuture.supplyAsync(() -> "Data: " + id);
    }

    private CompletableFuture<String> fetchFromBackupAsync(String id) {
        return CompletableFuture.supplyAsync(() -> "Backup Data: " + id);
    }
}

3. 平行執行的幾種寫法

java
public class ParallelExecutionPatterns {

    private final ExecutorService executor = Executors.newFixedThreadPool(10);

    // Wait for all futures to complete
    public CompletableFuture<List<String>> fetchAll(List<String> ids) {
        List<CompletableFuture<String>> futures = ids.stream()
            .map(this::fetchDataAsync)
            .collect(Collectors.toList());

        return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
            .thenApply(v -> futures.stream()
                .map(CompletableFuture::join)
                .collect(Collectors.toList()));
    }

    // Return first completed result
    public CompletableFuture<String> fetchFirst(List<String> ids) {
        List<CompletableFuture<String>> futures = ids.stream()
            .map(this::fetchDataAsync)
            .collect(Collectors.toList());

        return CompletableFuture.anyOf(futures.toArray(new CompletableFuture[0]))
            .thenApply(result -> (String) result);
    }

    // Collect results as they complete (with timeout per item)
    public List<String> fetchAllWithIndividualTimeout(List<String> ids, long timeoutMs) {
        List<CompletableFuture<String>> futures = ids.stream()
            .map(id -> fetchDataAsync(id)
                .orTimeout(timeoutMs, TimeUnit.MILLISECONDS)
                .exceptionally(ex -> "Failed: " + id))
            .collect(Collectors.toList());

        return futures.stream()
            .map(CompletableFuture::join)
            .collect(Collectors.toList());
    }

    // Limit concurrency with Semaphore
    public CompletableFuture<List<String>> fetchWithConcurrencyLimit(
            List<String> ids, int maxConcurrent) {

        Semaphore semaphore = new Semaphore(maxConcurrent);

        List<CompletableFuture<String>> futures = ids.stream()
            .map(id -> CompletableFuture.supplyAsync(() -> {
                try {
                    semaphore.acquire();
                    try {
                        return fetchDataSync(id);
                    } finally {
                        semaphore.release();
                    }
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    throw new CompletionException(e);
                }
            }, executor))
            .collect(Collectors.toList());

        return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
            .thenApply(v -> futures.stream()
                .map(CompletableFuture::join)
                .collect(Collectors.toList()));
    }

    private CompletableFuture<String> fetchDataAsync(String id) {
        return CompletableFuture.supplyAsync(() -> fetchDataSync(id), executor);
    }

    private String fetchDataSync(String id) {
        // Simulate work
        return "Data: " + id;
    }
}

快取策略

1. Cache-Aside 模式(延遲載入)

java
public class CacheAsidePattern {

    private final Cache<String, User> cache;
    private final UserRepository repository;

    public CacheAsidePattern(Cache<String, User> cache, UserRepository repository) {
        this.cache = cache;
        this.repository = repository;
    }

    public User getUser(String userId) {
        // 1. Check cache first
        User user = cache.get(userId);

        if (user != null) {
            return user;  // Cache hit
        }

        // 2. Cache miss - load from database
        user = repository.findById(userId);

        if (user != null) {
            // 3. Store in cache
            cache.put(userId, user);
        }

        return user;
    }

    public void updateUser(User user) {
        // 1. Update database first
        repository.save(user);

        // 2. Invalidate cache (or update)
        cache.invalidate(user.getId());

        // Alternative: Update cache
        // cache.put(user.getId(), user);
    }
}

2. Write-Through 模式

java
public class WriteThroughPattern {

    private final Cache<String, User> cache;
    private final UserRepository repository;

    public User getUser(String userId) {
        // Always read from cache (cache is source of truth for reads)
        User user = cache.get(userId);

        if (user == null) {
            user = repository.findById(userId);
            if (user != null) {
                cache.put(userId, user);
            }
        }

        return user;
    }

    public void saveUser(User user) {
        // Write to both cache and database synchronously
        repository.save(user);
        cache.put(user.getId(), user);
    }
}

3. Write-Behind(Write-Back)模式

java
public class WriteBehindPattern {

    private final Cache<String, User> cache;
    private final UserRepository repository;
    private final BlockingQueue<User> writeQueue = new LinkedBlockingQueue<>();
    private final ScheduledExecutorService writer = Executors.newSingleThreadScheduledExecutor();

    public WriteBehindPattern(Cache<String, User> cache, UserRepository repository) {
        this.cache = cache;
        this.repository = repository;

        // Start background writer
        writer.scheduleWithFixedDelay(this::flushWrites, 100, 100, TimeUnit.MILLISECONDS);
    }

    public void saveUser(User user) {
        // 1. Write to cache immediately
        cache.put(user.getId(), user);

        // 2. Queue for async database write
        writeQueue.offer(user);
    }

    private void flushWrites() {
        List<User> batch = new ArrayList<>();
        writeQueue.drainTo(batch, 100);  // Batch up to 100 items

        if (!batch.isEmpty()) {
            try {
                repository.saveAll(batch);  // Batch write to DB
            } catch (Exception e) {
                // Re-queue failed items
                writeQueue.addAll(batch);
            }
        }
    }

    public void shutdown() {
        writer.shutdown();
        flushWrites();  // Final flush
    }
}

4. 快取加上分散式鎖(避免快取擊穿)

java
public class CacheWithLock {

    private final Cache<String, User> cache;
    private final UserRepository repository;
    private final ConcurrentHashMap<String, ReentrantLock> locks = new ConcurrentHashMap<>();

    public User getUser(String userId) {
        // 1. Check cache
        User user = cache.get(userId);
        if (user != null) {
            return user;
        }

        // 2. Get lock for this specific key
        ReentrantLock lock = locks.computeIfAbsent(userId, k -> new ReentrantLock());

        lock.lock();
        try {
            // 3. Double-check cache after acquiring lock
            user = cache.get(userId);
            if (user != null) {
                return user;
            }

            // 4. Load from database
            user = repository.findById(userId);
            if (user != null) {
                cache.put(userId, user);
            }

            return user;
        } finally {
            lock.unlock();
        }
    }
}

5. Refresh-Ahead 模式

java
public class RefreshAheadCache {

    private final Cache<String, CacheEntry<User>> cache;
    private final UserRepository repository;
    private final ExecutorService refreshExecutor = Executors.newFixedThreadPool(2);

    private static final long TTL_MS = 60_000;           // 60 seconds TTL
    private static final long REFRESH_THRESHOLD = 45_000; // Refresh at 75% of TTL

    static class CacheEntry<T> {
        final T value;
        final long createdAt;
        volatile boolean refreshing = false;

        CacheEntry(T value) {
            this.value = value;
            this.createdAt = System.currentTimeMillis();
        }

        boolean shouldRefresh() {
            return System.currentTimeMillis() - createdAt > REFRESH_THRESHOLD;
        }

        boolean isExpired() {
            return System.currentTimeMillis() - createdAt > TTL_MS;
        }
    }

    public User getUser(String userId) {
        CacheEntry<User> entry = cache.get(userId);

        if (entry == null || entry.isExpired()) {
            // Cache miss or expired - synchronous load
            return loadAndCache(userId);
        }

        // Trigger async refresh if approaching expiry
        if (entry.shouldRefresh() && !entry.refreshing) {
            entry.refreshing = true;
            refreshExecutor.submit(() -> {
                try {
                    loadAndCache(userId);
                } catch (Exception e) {
                    entry.refreshing = false;
                }
            });
        }

        return entry.value;
    }

    private User loadAndCache(String userId) {
        User user = repository.findById(userId);
        if (user != null) {
            cache.put(userId, new CacheEntry<>(user));
        }
        return user;
    }
}

訊息佇列模式

1. At-Least-Once 投遞加上冪等

java
public class AtLeastOnceConsumer {

    private final MessageQueue queue;
    private final IdempotencyStore idempotencyStore;
    private final OrderService orderService;

    public void processMessages() {
        while (true) {
            Message message = queue.receive();

            try {
                // Check if already processed
                if (idempotencyStore.isProcessed(message.getId())) {
                    queue.acknowledge(message);
                    continue;
                }

                // Process the message
                orderService.processOrder(message.getPayload());

                // Mark as processed BEFORE acknowledging
                idempotencyStore.markProcessed(message.getId());

                // Acknowledge to queue
                queue.acknowledge(message);

            } catch (Exception e) {
                // Don't acknowledge - message will be redelivered
                queue.nack(message);
            }
        }
    }
}

2. Transactional Outbox 模式

java
/**
 * Ensures message is sent if and only if database transaction commits.
 * Solves dual-write problem.
 */
public class TransactionalOutbox {

    private final DataSource dataSource;
    private final ScheduledExecutorService publisher = Executors.newSingleThreadScheduledExecutor();

    public TransactionalOutbox() {
        // Poll and publish outbox messages
        publisher.scheduleWithFixedDelay(this::publishPendingMessages, 100, 100, TimeUnit.MILLISECONDS);
    }

    @Transactional
    public void createOrder(Order order) {
        // 1. Save order to database
        saveOrder(order);

        // 2. Save event to outbox table (same transaction)
        OutboxEvent event = new OutboxEvent(
            UUID.randomUUID().toString(),
            "order.created",
            toJson(order),
            "PENDING"
        );
        saveOutboxEvent(event);

        // Transaction commits both or neither
    }

    private void publishPendingMessages() {
        List<OutboxEvent> pending = fetchPendingEvents(100);

        for (OutboxEvent event : pending) {
            try {
                // Publish to message queue
                messageQueue.publish(event.getTopic(), event.getPayload());

                // Mark as published
                markEventPublished(event.getId());

            } catch (Exception e) {
                // Will retry on next poll
                markEventFailed(event.getId());
            }
        }
    }

    // Database operations
    private void saveOrder(Order order) { /* ... */ }
    private void saveOutboxEvent(OutboxEvent event) { /* ... */ }
    private List<OutboxEvent> fetchPendingEvents(int limit) { /* ... */ return List.of(); }
    private void markEventPublished(String id) { /* ... */ }
    private void markEventFailed(String id) { /* ... */ }
}

3. 死信佇列(Dead Letter Queue)處理器

java
public class DeadLetterQueueHandler {

    private final MessageQueue mainQueue;
    private final MessageQueue dlq;
    private final int maxRetries = 3;

    public void processWithDLQ() {
        while (true) {
            Message message = mainQueue.receive();

            try {
                processMessage(message);
                mainQueue.acknowledge(message);

            } catch (RetryableException e) {
                int retryCount = message.getRetryCount();

                if (retryCount < maxRetries) {
                    // Requeue with incremented retry count
                    message.setRetryCount(retryCount + 1);
                    message.setNextRetryTime(calculateBackoff(retryCount));
                    mainQueue.requeue(message);
                } else {
                    // Move to DLQ
                    dlq.publish(message);
                    mainQueue.acknowledge(message);
                }

            } catch (NonRetryableException e) {
                // Immediately move to DLQ
                dlq.publish(message);
                mainQueue.acknowledge(message);
            }
        }
    }

    private long calculateBackoff(int retryCount) {
        // Exponential backoff: 1s, 2s, 4s, 8s...
        return System.currentTimeMillis() + (1000L * (1 << retryCount));
    }

    private void processMessage(Message message) throws RetryableException, NonRetryableException {
        // Business logic
    }
}

4. Consumer group 與分區分配

java
public class PartitionedConsumer {

    private final int consumerId;
    private final int totalConsumers;
    private final MessageQueue queue;

    public PartitionedConsumer(int consumerId, int totalConsumers) {
        this.consumerId = consumerId;
        this.totalConsumers = totalConsumers;
    }

    public void consume() {
        while (true) {
            Message message = queue.receive();

            // Check if this message belongs to this consumer
            int partition = getPartition(message.getKey());

            if (partition == consumerId) {
                processMessage(message);
                queue.acknowledge(message);
            } else {
                // Requeue for correct consumer (or skip if using proper partitioning)
                queue.requeue(message);
            }
        }
    }

    private int getPartition(String key) {
        // Consistent hash to determine partition
        return Math.abs(key.hashCode() % totalConsumers);
    }

    private void processMessage(Message message) {
        // Process message
    }
}

分散式交易

1. Saga 模式(Choreography 編舞式)

java
/**
 * Each service publishes events, others react.
 * Compensating transactions for rollback.
 */
public class OrderSagaChoreography {

    // Order Service
    public void createOrder(Order order) {
        saveOrder(order, "PENDING");
        publishEvent(new OrderCreatedEvent(order));
    }

    public void handlePaymentCompleted(PaymentCompletedEvent event) {
        updateOrderStatus(event.getOrderId(), "PAYMENT_COMPLETED");
        publishEvent(new OrderPaidEvent(event.getOrderId()));
    }

    public void handlePaymentFailed(PaymentFailedEvent event) {
        // Compensating transaction
        updateOrderStatus(event.getOrderId(), "CANCELLED");
    }

    // Payment Service
    public void handleOrderCreated(OrderCreatedEvent event) {
        try {
            processPayment(event.getOrder());
            publishEvent(new PaymentCompletedEvent(event.getOrderId()));
        } catch (Exception e) {
            publishEvent(new PaymentFailedEvent(event.getOrderId(), e.getMessage()));
        }
    }

    // Inventory Service
    public void handleOrderPaid(OrderPaidEvent event) {
        try {
            reserveInventory(event.getOrderId());
            publishEvent(new InventoryReservedEvent(event.getOrderId()));
        } catch (Exception e) {
            // Compensate: refund payment
            publishEvent(new InventoryFailedEvent(event.getOrderId()));
        }
    }
}

2. Saga 模式(Orchestrator 編排式)

java
/**
 * Central orchestrator controls the saga flow.
 * Easier to understand and debug.
 */
public class OrderSagaOrchestrator {

    private final OrderService orderService;
    private final PaymentService paymentService;
    private final InventoryService inventoryService;
    private final ShippingService shippingService;

    public OrderResult executeOrderSaga(OrderRequest request) {
        String sagaId = UUID.randomUUID().toString();
        SagaState state = new SagaState(sagaId);

        try {
            // Step 1: Create order
            Order order = orderService.createOrder(request);
            state.setOrderId(order.getId());
            state.addCompensation(() -> orderService.cancelOrder(order.getId()));

            // Step 2: Process payment
            PaymentResult payment = paymentService.processPayment(order);
            state.setPaymentId(payment.getId());
            state.addCompensation(() -> paymentService.refund(payment.getId()));

            // Step 3: Reserve inventory
            InventoryReservation reservation = inventoryService.reserve(order);
            state.setReservationId(reservation.getId());
            state.addCompensation(() -> inventoryService.release(reservation.getId()));

            // Step 4: Schedule shipping
            Shipment shipment = shippingService.schedule(order);
            state.setShipmentId(shipment.getId());

            // All steps succeeded
            orderService.confirmOrder(order.getId());
            return OrderResult.success(order.getId());

        } catch (Exception e) {
            // Execute compensations in reverse order
            state.compensate();
            return OrderResult.failure(e.getMessage());
        }
    }

    static class SagaState {
        private final String sagaId;
        private final Deque<Runnable> compensations = new ArrayDeque<>();
        private String orderId;
        private String paymentId;
        private String reservationId;
        private String shipmentId;

        SagaState(String sagaId) {
            this.sagaId = sagaId;
        }

        void addCompensation(Runnable compensation) {
            compensations.push(compensation);
        }

        void compensate() {
            while (!compensations.isEmpty()) {
                try {
                    compensations.pop().run();
                } catch (Exception e) {
                    // Log and continue with other compensations
                    System.err.println("Compensation failed: " + e.getMessage());
                }
            }
        }

        // Setters omitted for brevity
        void setOrderId(String id) { this.orderId = id; }
        void setPaymentId(String id) { this.paymentId = id; }
        void setReservationId(String id) { this.reservationId = id; }
        void setShipmentId(String id) { this.shipmentId = id; }
    }
}

3. 兩階段提交(2PC)模擬

java
/**
 * Coordinator-based 2PC implementation.
 * Note: Rarely used in practice due to blocking nature.
 */
public class TwoPhaseCommitCoordinator {

    private final List<Participant> participants;

    public boolean executeTransaction(TransactionContext context) {
        String txId = UUID.randomUUID().toString();

        // Phase 1: Prepare
        boolean allPrepared = true;
        for (Participant participant : participants) {
            try {
                boolean prepared = participant.prepare(txId, context);
                if (!prepared) {
                    allPrepared = false;
                    break;
                }
            } catch (Exception e) {
                allPrepared = false;
                break;
            }
        }

        // Phase 2: Commit or Rollback
        if (allPrepared) {
            for (Participant participant : participants) {
                participant.commit(txId);
            }
            return true;
        } else {
            for (Participant participant : participants) {
                participant.rollback(txId);
            }
            return false;
        }
    }

    interface Participant {
        boolean prepare(String txId, TransactionContext context);
        void commit(String txId);
        void rollback(String txId);
    }
}

協調用的工具類

1. CountDownLatch — 等 N 件事發生

java
public class CountDownLatchDemo {

    /**
     * Wait for multiple services to initialize before starting main application.
     */
    public void initializeServices() throws InterruptedException {
        int serviceCount = 3;
        CountDownLatch latch = new CountDownLatch(serviceCount);

        // Start services in parallel
        new Thread(() -> {
            initDatabase();
            latch.countDown();
        }).start();

        new Thread(() -> {
            initCache();
            latch.countDown();
        }).start();

        new Thread(() -> {
            initMessageQueue();
            latch.countDown();
        }).start();

        // Wait for all services
        boolean completed = latch.await(30, TimeUnit.SECONDS);

        if (completed) {
            System.out.println("All services initialized!");
        } else {
            throw new RuntimeException("Service initialization timeout");
        }
    }

    private void initDatabase() { sleep(1000); }
    private void initCache() { sleep(500); }
    private void initMessageQueue() { sleep(800); }
    private void sleep(long ms) { try { Thread.sleep(ms); } catch (InterruptedException e) {} }
}

2. CyclicBarrier — 同步會合點

java
public class CyclicBarrierDemo {

    /**
     * All threads wait at barrier, then proceed together.
     * Useful for parallel algorithms with phases.
     */
    public void parallelMatrixComputation() {
        int threadCount = 4;
        CyclicBarrier barrier = new CyclicBarrier(threadCount, () -> {
            System.out.println("All threads completed phase, merging results...");
        });

        for (int i = 0; i < threadCount; i++) {
            final int threadId = i;
            new Thread(() -> {
                try {
                    // Phase 1: Compute partial result
                    System.out.println("Thread " + threadId + " computing...");
                    Thread.sleep(100 * threadId);

                    // Wait for all threads
                    barrier.await();

                    // Phase 2: All threads proceed together
                    System.out.println("Thread " + threadId + " proceeding to phase 2");

                } catch (Exception e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}

3. Semaphore — 限制同時存取數

java
public class SemaphoreDemo {

    /**
     * Connection pool with limited connections.
     */
    private final Semaphore connectionSemaphore;
    private final Queue<Connection> connectionPool;

    public SemaphoreDemo(int maxConnections) {
        this.connectionSemaphore = new Semaphore(maxConnections);
        this.connectionPool = new ConcurrentLinkedQueue<>();

        // Pre-create connections
        for (int i = 0; i < maxConnections; i++) {
            connectionPool.offer(createConnection());
        }
    }

    public Connection acquireConnection(long timeout, TimeUnit unit)
            throws InterruptedException, TimeoutException {

        if (!connectionSemaphore.tryAcquire(timeout, unit)) {
            throw new TimeoutException("Could not acquire connection");
        }

        Connection conn = connectionPool.poll();
        if (conn == null || !conn.isValid()) {
            conn = createConnection();
        }
        return conn;
    }

    public void releaseConnection(Connection conn) {
        if (conn != null) {
            connectionPool.offer(conn);
            connectionSemaphore.release();
        }
    }

    private Connection createConnection() {
        return new Connection();
    }
}

4. Phaser — 有彈性的 barrier

java
public class PhaserDemo {

    /**
     * Dynamic number of parties, multiple phases.
     */
    public void dynamicParallelProcessing() {
        Phaser phaser = new Phaser(1); // Register self

        for (int i = 0; i < 3; i++) {
            final int taskId = i;
            phaser.register(); // Register new party

            new Thread(() -> {
                for (int phase = 0; phase < 3; phase++) {
                    System.out.println("Task " + taskId + " executing phase " + phase);

                    // Wait for others to complete this phase
                    phaser.arriveAndAwaitAdvance();
                }

                // Deregister when done
                phaser.arriveAndDeregister();
            }).start();
        }

        // Wait for all tasks to complete
        while (!phaser.isTerminated()) {
            phaser.arriveAndAwaitAdvance();
        }
    }
}

優雅關閉

1. ExecutorService 的優雅關閉

java
public class GracefulShutdownDemo {

    private final ExecutorService executor = Executors.newFixedThreadPool(10);
    private volatile boolean shuttingDown = false;

    public void shutdown() {
        shuttingDown = true;

        // Stop accepting new tasks
        executor.shutdown();

        try {
            // Wait for existing tasks to complete
            if (!executor.awaitTermination(30, TimeUnit.SECONDS)) {
                System.err.println("Forcing shutdown...");

                // Force shutdown
                List<Runnable> pendingTasks = executor.shutdownNow();
                System.err.println("Pending tasks: " + pendingTasks.size());

                // Wait again
                if (!executor.awaitTermination(10, TimeUnit.SECONDS)) {
                    System.err.println("Executor did not terminate");
                }
            }
        } catch (InterruptedException e) {
            executor.shutdownNow();
            Thread.currentThread().interrupt();
        }
    }

    public void submitTask(Runnable task) {
        if (shuttingDown) {
            throw new RejectedExecutionException("Service is shutting down");
        }
        executor.submit(task);
    }
}

2. Spring Boot 的優雅關閉

java
@Component
public class GracefulShutdownHandler implements ApplicationListener<ContextClosedEvent> {

    private final ExecutorService executor;
    private final AtomicInteger activeRequests = new AtomicInteger(0);

    @Override
    public void onApplicationEvent(ContextClosedEvent event) {
        System.out.println("Shutdown initiated, waiting for active requests...");

        // Wait for active requests to complete
        int maxWaitSeconds = 30;
        int waited = 0;

        while (activeRequests.get() > 0 && waited < maxWaitSeconds) {
            try {
                Thread.sleep(1000);
                waited++;
                System.out.println("Active requests: " + activeRequests.get());
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                break;
            }
        }

        // Shutdown executor
        executor.shutdown();
        try {
            executor.awaitTermination(10, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            executor.shutdownNow();
        }

        System.out.println("Shutdown complete");
    }

    public void trackRequest() {
        activeRequests.incrementAndGet();
    }

    public void completeRequest() {
        activeRequests.decrementAndGet();
    }
}

背壓處理

1. 有界佇列加上阻塞

java
public class BackpressureWithBoundedQueue {

    // Bounded queue - producer blocks when full
    private final BlockingQueue<Task> queue = new ArrayBlockingQueue<>(100);

    public void produce(Task task) throws InterruptedException {
        // Blocks if queue is full - natural backpressure
        queue.put(task);
    }

    public void produceWithTimeout(Task task, long timeout, TimeUnit unit)
            throws InterruptedException {

        if (!queue.offer(task, timeout, unit)) {
            throw new RejectedExecutionException("Queue full, try again later");
        }
    }

    public void consume() throws InterruptedException {
        Task task = queue.take(); // Blocks if empty
        process(task);
    }

    private void process(Task task) { }
}

2. 以速率為基礎的背壓

java
public class RateBasedBackpressure {

    private final RateLimiter rateLimiter;
    private final BlockingQueue<Task> queue;
    private final ExecutorService executor;

    public RateBasedBackpressure(int maxRatePerSecond, int queueCapacity) {
        this.rateLimiter = new TokenBucketRateLimiter(maxRatePerSecond, maxRatePerSecond);
        this.queue = new ArrayBlockingQueue<>(queueCapacity);
        this.executor = Executors.newFixedThreadPool(4);

        startConsumer();
    }

    public boolean submit(Task task) {
        // Check rate limit
        if (!rateLimiter.tryAcquire()) {
            return false; // Rate limited
        }

        // Check queue capacity
        return queue.offer(task);
    }

    private void startConsumer() {
        executor.submit(() -> {
            while (!Thread.currentThread().isInterrupted()) {
                try {
                    Task task = queue.take();
                    process(task);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    break;
                }
            }
        });
    }

    private void process(Task task) { }
}

3. Reactive 的背壓(觀念)

java
/**
 * Reactive Streams backpressure with request-based flow control.
 */
public class ReactiveBackpressureExample {

    // Subscriber requests N items at a time
    public void demonstrateBackpressure() {
        // Conceptual - using Project Reactor/RxJava syntax

        /*
        Flux.range(1, 1000)
            .onBackpressureBuffer(100)      // Buffer up to 100
            .onBackpressureDrop(i -> log("Dropped: " + i))  // Drop if buffer full
            .publishOn(Schedulers.parallel())
            .subscribe(new BaseSubscriber<Integer>() {
                @Override
                protected void hookOnSubscribe(Subscription s) {
                    request(10); // Request initial 10 items
                }

                @Override
                protected void hookOnNext(Integer value) {
                    process(value);
                    request(1); // Request next item after processing
                }
            });
        */
    }
}

面試常見問題

Q1:驚群(thundering herd)問題你會怎麼處理?

答:

  1. 避免快取擊穿:用分散式鎖或 computeIfAbsent
  2. 把快取過期時間錯開:TTL 加上隨機抖動
  3. 請求合併:多個相同請求共用一次後端呼叫
  4. 斷路器:避免連鎖失敗
java
// Request coalescing example
ConcurrentHashMap<String, CompletableFuture<Data>> inFlightRequests = new ConcurrentHashMap<>();

public CompletableFuture<Data> getData(String key) {
    return inFlightRequests.computeIfAbsent(key, k -> {
        return fetchFromBackend(k)
            .whenComplete((result, ex) -> inFlightRequests.remove(k));
    });
}

Q2:Saga 的 Choreography 和 Orchestration 差在哪?

面向 Choreography 編舞式 Orchestration 編排式
協調方式 去中心化(靠事件) 集中(由 orchestrator 主導)
耦合 鬆 對 orchestrator 較緊
複雜度 事件流會變複雜 流程控制較單純
除錯 較難(分散各處) 較容易(只有一個點)
單點故障 沒有 有(orchestrator)

Q3:怎麼做到 exactly-once 處理?

**答:**真正的 exactly-once 很難。改用冪等:

  1. 冪等操作:把操作設計成可以安全重複執行
  2. 冪等鍵:記下已處理過的 request ID
  3. Transactional outbox:原子地同時更新資料庫與 outbox
java
// At-least-once delivery + idempotent processing = effectively exactly-once
public void processMessage(Message msg) {
    if (isAlreadyProcessed(msg.getId())) {
        return; // Skip duplicate
    }

    // Process idempotently (use upsert, conditional updates)
    processIdempotently(msg);

    markAsProcessed(msg.getId());
}

Q4:CountDownLatch 和 CyclicBarrier 該用哪個?

特性 CountDownLatch CyclicBarrier
可重複使用 不行(一次性) 可以(能 reset)
用途 等 N 件事發生 反覆同步 N 條執行緒
動作 倒數到 0 在 barrier 前等待
例子 等各服務啟動完成 平行演算法的各個階段

Q5:執行緒池要怎麼避免記憶體洩漏?

  1. 清掉 ThreadLocal:用完一定要 remove
  2. 有界佇列:work queue 要設上限
  3. 正確關閉:呼叫 shutdown() 並 awaitTermination()
  4. 拒絕策略:被拒絕的任務要好好處理
  5. 監控佇列長度:佇列一直長就要告警
java
// ThreadLocal cleanup
public void processInPool(Task task) {
    executor.submit(() -> {
        try {
            threadLocalContext.set(createContext());
            process(task);
        } finally {
            threadLocalContext.remove(); // IMPORTANT!
        }
    });
}

總結:模式選擇指南

問題 模式
多個互不相干的任務要平行跑 CompletableFuture.allOf()
誰先回來就用誰 CompletableFuture.anyOf()
限制並發數 Semaphore
等事件發生 CountDownLatch
分階段同步 CyclicBarrier
動態協調 Phaser
分散式交易 Saga(Orchestrator)
事件驅動的交易 Saga(Choreography)
快取沒中時怎麼辦 Cache-Aside + 鎖
寫入效能 Write-Behind
訊息可靠性 At-least-once + 冪等
失敗訊息怎麼辦 死信佇列

參考資料