Why Use Distributed Locks When Idempotency Already Prevents Duplicate Submissions?

Although idempotent APIs can block duplicate requests, they only guarantee result correctness, while distributed locks control the execution order; the article explains why both mechanisms are complementary and necessary in high‑concurrency scenarios such as stock deduction and money transfer.

Java Tech Enthusiast
Java Tech Enthusiast
Java Tech Enthusiast
Why Use Distributed Locks When Idempotency Already Prevents Duplicate Submissions?

Idempotency vs. Distributed Lock

Idempotency ensures result correctness ; a distributed lock controls the concurrent execution order . They are complementary, not interchangeable.

What Idempotency Prevents

Idempotency means that executing the same request once or many times yields the same effect. The most common implementation is an idempotent token: the client obtains a unique token, sends it with the request, and the server checks whether the token has been consumed.

public String submitOrder(String idempotentToken, OrderRequest request) {
    // 1. Check if token has been consumed
    boolean exists = redis.hasKey("idempotent:" + idempotentToken);
    if (exists) {
        return "重复提交,请勿重复操作";
    }
    // 2. Mark token as consumed
    redis.opsForValue().set("idempotent:" + idempotentToken, "1", 30, TimeUnit.MINUTES);
    // 3. Execute business logic
    orderService.createOrder(request);
    return "下单成功";
}

This works fine under low concurrency, but when two requests arrive within tens of milliseconds, both pass the token check before either marks the token, resulting in two orders – a classic check‑execute race condition.

Can Redis Atomic Operations Solve It?

Combining the check and mark into a single atomic operation using SETNX eliminates the race:

public String submitOrder(String idempotentToken, OrderRequest request) {
    // SETNX: set if absent, atomic
    Boolean success = redis.opsForValue()
        .setIfAbsent("idempotent:" + idempotentToken, "1", 30, TimeUnit.MINUTES);
    if (!success) {
        return "重复提交,请勿重复操作";
    }
    // Execute business logic
    orderService.createOrder(request);
    return "下单成功";
}

While SETNX prevents the token‑check race, it is essentially a minimal distributed lock; the same principle applies when a full‑featured lock is needed.

Scenario 1: Business Logic Requires Mutual Exclusion

Stock‑deduction example without a lock can cause overselling when 100 users simultaneously buy the last item.

public void deductStock(String skuId, int quantity) {
    int stock = stockMapper.getStock(skuId);
    if (stock < quantity) {
        throw new BizException("库存不足");
    }
    stockMapper.deductStock(skuId, quantity);
}

Idempotent tokens are irrelevant here because each request is distinct. Adding a distributed lock guarantees that only one thread executes the whole "check‑then‑deduct" flow at a time.

public void deductStock(String skuId, int quantity) {
    RLock lock = redisson.getLock("lock:stock:" + skuId);
    lock.lock();
    try {
        int stock = stockMapper.getStock(skuId);
        if (stock < quantity) {
            throw new BizException("库存不足");
        }
        stockMapper.deductStock(skuId, quantity);
    } finally {
        lock.unlock();
    }
}

Scenario 2: Idempotency Cannot Prevent Concurrent Data Corruption

In a transfer scenario, two independent transfers to the same account can interleave, causing lost money:

public void transfer(String from, String to, BigDecimal amount) {
    BigDecimal balance = accountMapper.getBalance(to);
    accountMapper.updateBalance(to, balance.add(amount));
}

Both transfers succeed, but the final balance is incorrect because the reads and writes overlap. A lock is required to serialize access to the account.

Scenario 3: In‑process synchronized Is Insufficient in a Microservice Cluster

synchronized

only protects threads within the same JVM. When the service runs on three machines behind Nginx, each JVM holds its own lock, so concurrent requests still corrupt shared data. A distributed lock solves the cross‑JVM mutual exclusion problem.

Combining Distributed Lock and Idempotency

A production‑grade order API should first acquire a distributed lock, then perform an idempotent check inside the lock:

public String submitOrder(String idempotentToken, OrderRequest request) {
    // First layer: distributed lock – only one request enters at a time
    RLock lock = redisson.getLock("lock:order:" + idempotentToken);
    boolean acquired = lock.tryLock(3, 10, TimeUnit.SECONDS);
    if (!acquired) {
        return "系统繁忙,请稍后重试";
    }
    try {
        // Second layer: idempotent check – ensure the operation is not repeated
        Boolean firstTime = redis.opsForValue()
            .setIfAbsent("idempotent:" + idempotentToken, "1", 30, TimeUnit.MINUTES);
        if (!firstTime) {
            return "订单已提交,请勿重复操作";
        }
        // Execute business logic
        orderService.createOrder(request);
        return "下单成功";
    } finally {
        lock.unlock();
    }
}

The lock protects the critical section from concurrent execution, while the idempotent token guards against duplicate processing if the lock expires (e.g., a long‑running SQL exceeds the lock’s TTL).

Conclusion

Idempotency guarantees that repeated calls produce the same result; a distributed lock guarantees that only one call manipulates shared state at a time. In high‑concurrency or multi‑node environments, both are required because each addresses a different aspect of correctness.

timeline illustration
timeline illustration
transfer race condition
transfer race condition
distributed synchronized limitation
distributed synchronized limitation
Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaconcurrencyRedisdistributed lockidempotencyRedisson
Java Tech Enthusiast
Written by

Java Tech Enthusiast

Sharing computer programming language knowledge, focusing on Java fundamentals, data structures, related tools, Spring Cloud, IntelliJ IDEA... Book giveaways, red‑packet rewards and other perks await!

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.