How to Prevent Coupon Over‑Issuing in High‑Concurrency Scenarios

The article analyzes why a coupon‑distribution feature can issue more coupons than available under heavy load, explains the root cause of concurrent stock deductions, and presents four practical solutions—including Java synchronized blocks, SQL row‑level locking, Redis distributed locks, and Redisson—to reliably prevent over‑issuance.

Java Backend Technology
Java Backend Technology
Java Backend Technology
How to Prevent Coupon Over‑Issuing in High‑Concurrency Scenarios

Problem Description

In a recent project we implemented a coupon‑receiving feature where each coupon has a total issuance quantity and a per‑user limit. When a user successfully claims a coupon, a record is written to a separate table (Table B).

Under high concurrency the stock can become negative, leading to over‑issuance.

Root Cause

When two threads simultaneously pass the availability check, both may decrement the stock, causing the stock to drop below zero.

Solutions

Solution 1 – Java synchronized

Wrap the entire coupon‑claiming logic in a synchronized(this) block to ensure only one thread executes the method at a time.

synchronized (this) {
    // business logic
    // check coupon, create record, reduce stock, insert record
}

Drawbacks: works only within a single JVM, introduces serialization, and can become a bottleneck in a clustered environment.

Solution 2 – SQL row‑level lock

Update the stock with a condition that stock > 0, letting InnoDB lock the row.

UPDATE coupon
SET stock = stock - 1
WHERE id = #{coupon_id} AND stock > 0;

Alternatively use an optimistic‑lock pattern with a version column to avoid ABA problems.

UPDATE product
SET stock = stock - 1, version = version + 1
WHERE id = 1 AND stock > 0 AND version = #{last_version};

Solution 3 – Redis distributed lock (SETNX)

Use SETNX to acquire a lock key, set an expiration, execute the business logic, then delete the key. To avoid accidental deletion, store the thread ID as the lock value and verify it before deletion.

String key = "lock:coupon:" + couponId;
String threadId = Thread.currentThread().getId() + "";
if (redis.setnx(key, threadId)) {
    redis.expire(key, 30, TimeUnit.MILLISECONDS);
    try {
        // business logic
    } finally {
        if (redis.get(key).equals(threadId)) {
            redis.del(key);
        }
    }
} else {
    // retry or spin
}

Use a Lua script for atomic check‑and‑delete:

if redis.call('get', KEYS[1]) == ARGV[1] then
    return redis.call('del', KEYS[1])
else
    return 0
end

Solution 4 – Redisson client

Configure Redisson and obtain an RLock for the coupon key. Redisson’s watchdog automatically renews the lock lease, eliminating manual expiration handling.

@Configuration
public class AppConfig {
    @Value("${spring.redis.host}") private String redisHost;
    @Value("${spring.redis.port}") private String redisPort;
    @Bean
    public RedissonClient redisson() {
        Config config = new Config();
        config.useSingleServer().setAddress("redis://" + redisHost + ":" + redisPort);
        return Redisson.create(config);
    }
}

public JsonData addCoupon(long couponId, CouponCategoryEnum category) {
    String key = "lock:coupon:" + couponId;
    RLock lock = redisson.getLock(key);
    lock.lock();
    try {
        // business logic
    } finally {
        lock.unlock();
    }
    return JsonData.buildSuccess();
}

All four approaches can prevent coupon over‑issuance; the choice depends on the system architecture and performance requirements.

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.

BackendjavaredismysqlCoupon
Java Backend Technology
Written by

Java Backend Technology

Focus on Java-related technologies: SSM, Spring ecosystem, microservices, MySQL, MyCat, clustering, distributed systems, middleware, Linux, networking, multithreading. Occasionally cover DevOps tools like Jenkins, Nexus, Docker, and ELK. Also share technical insights from time to time, committed to Java full-stack development!

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.