How to Eliminate Duplicate Order Numbers in High‑Concurrency Java Systems

This article analyzes a real‑world incident where duplicate order IDs appeared under high concurrency, critiques the original Java implementation, demonstrates a concurrency test that exposed the flaw, and presents a revised, thread‑safe solution using AtomicInteger, Java 8 date‑time APIs, and IP‑based suffixes to guarantee uniqueness across multiple instances.

Programmer DD
Programmer DD
Programmer DD
How to Eliminate Duplicate Order Numbers in High‑Concurrency Java Systems
系统出现了两个一模一样的订单号,订单的内容却不是不一样的,而且系统在按照订单号查询的时候一直抛错,也没法正常回调,而且事情发生的不止一次,所以这次系统升级一定要解决掉。

Original Implementation

/**
 * OD单号生成
 * 订单号生成规则:OD + yyMMddHHmmssSSS + 5位数(商户ID3位+随机数2位) 22位
 */
public static String getYYMMDDHHNumber(String merchId) {
    StringBuffer orderNo = new StringBuffer(new SimpleDateFormat("yyMMddHHmmssSSS").format(new Date()));
    if (StringUtils.isNotBlank(merchId)) {
        if (merchId.length() > 3) {
            orderNo.append(merchId.substring(0, 3));
        } else {
            orderNo.append(merchId);
        }
    }
    int orderLength = orderNo.toString().length();
    String randomNum = getRandomByLength(20 - orderLength);
    orderNo.append(randomNum);
    return orderNo.toString();
}

/** 生成指定位数的随机数 **/
public static String getRandomByLength(int size) {
    if (size > 8 || size < 1) {
        return "";
    }
    Random ne = new Random();
    StringBuffer endNumStr = new StringBuffer("1");
    StringBuffer staNumStr = new StringBuffer("9");
    for (int i = 1; i < size; i++) {
        endNumStr.append("0");
        staNumStr.append("0");
    }
    int randomNum = ne.nextInt(Integer.valueOf(staNumStr.toString())) + Integer.valueOf(endNumStr.toString());
    return String.valueOf(randomNum);
}

Concurrency Test

public static void main(String[] args) {
    final String merchId = "12334";
    List<String> orderNos = Collections.synchronizedList(new ArrayList<String>());
    IntStream.range(0, 100).parallel().forEach(i -> {
        orderNos.add(getYYMMDDHHNumber(merchId));
    });
    List<String> filterOrderNos = orderNos.stream().distinct().collect(Collectors.toList());
    System.out.println("生成订单数:" + orderNos.size());
    System.out.println("过滤重复后订单数:" + filterOrderNos.size());
    System.out.println("重复订单数:" + (orderNos.size() - filterOrderNos.size()));
}

Result:

生成订单数:100
过滤重复后订单数:87
重复订单数:13

Problems Identified

The original algorithm relies on a two‑digit random number and millisecond precision. Under high concurrency the millisecond value is often identical across threads, and a two‑digit random suffix provides only 100 possibilities, leading to frequent collisions (13% in the test).

Improved Single‑Instance Solution

/** 订单号生成(NEW) **/
private static final AtomicInteger SEQ = new AtomicInteger(1000);
private static final DateTimeFormatter DF_FMT_PREFIX = DateTimeFormatter.ofPattern("yyMMddHHmmssSS");
private static ZoneId ZONE_ID = ZoneId.of("Asia/Shanghai");
public static String generateOrderNo() {
    LocalDateTime dataTime = LocalDateTime.now(ZONE_ID);
    if (SEQ.intValue() > 9990) {
        SEQ.getAndSet(1000);
    }
    return dataTime.format(DF_FMT_PREFIX) + SEQ.getAndIncrement();
}

This version removes the merchant ID, keeps only three millisecond digits, and uses an AtomicInteger (CAS) to guarantee unique sequential numbers within a single JVM instance.

Distributed‑Instance Solution (IP Suffix)

import org.apache.commons.lang3.RandomUtils;
import java.net.InetAddress;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

public class OrderGen2Test {
    private static ZoneId ZONE_ID = ZoneId.of("Asia/Shanghai");
    private static final AtomicInteger SEQ = new AtomicInteger(1000);
    private static final DateTimeFormatter DF_FMT_PREFIX = DateTimeFormatter.ofPattern("yyMMddHHmmssSS");

    public static String generateOrderNo() {
        LocalDateTime dataTime = LocalDateTime.now(ZONE_ID);
        if (SEQ.intValue() > 9990) {
            SEQ.getAndSet(1000);
        }
        return dataTime.format(DF_FMT_PREFIX) + getLocalIpSuffix() + SEQ.getAndIncrement();
    }

    private volatile static String IP_SUFFIX = null;
    private static String getLocalIpSuffix() {
        if (IP_SUFFIX != null) {
            return IP_SUFFIX;
        }
        try {
            synchronized (OrderGen2Test.class) {
                if (IP_SUFFIX != null) {
                    return IP_SUFFIX;
                }
                InetAddress addr = InetAddress.getLocalHost();
                String hostAddress = addr.getHostAddress();
                if (hostAddress != null && hostAddress.length() > 4) {
                    String ipSuffix = hostAddress.trim().split("\\.")[3];
                    if (ipSuffix.length() == 2) {
                        IP_SUFFIX = ipSuffix;
                        return IP_SUFFIX;
                    }
                    ipSuffix = "0" + ipSuffix;
                    IP_SUFFIX = ipSuffix.substring(ipSuffix.length() - 2);
                    return IP_SUFFIX;
                }
                IP_SUFFIX = RandomUtils.nextInt(10, 20) + "";
                return IP_SUFFIX;
            }
        } catch (Exception e) {
            System.out.println("获取IP失败:" + e.getMessage());
            IP_SUFFIX = RandomUtils.nextInt(10, 20) + "";
            return IP_SUFFIX;
        }
    }

    public static void main(String[] args) {
        List<String> orderNos = Collections.synchronizedList(new ArrayList<>());
        IntStream.range(0, 8000).parallel().forEach(i -> {
            orderNos.add(generateOrderNo());
        });
        List<String> filterOrderNos = orderNos.stream().distinct().collect(Collectors.toList());
        System.out.println("订单样例:" + orderNos.get(22));
        System.out.println("生成订单数:" + orderNos.size());
        System.out.println("过滤重复后订单数:" + filterOrderNos.size());
        System.out.println("重复订单数:" + (orderNos.size() - filterOrderNos.size()));
    }
}

/**
 订单样例:20082115575546011022
 生成订单数:8000
 过滤重复后订单数:8000
 重复订单数:0
**/

The IP suffix distinguishes containers running on the same host, ensuring uniqueness across multiple Docker instances while keeping the algorithm lightweight.

Key Recommendations

AtomicInteger provides lock‑free atomic increments; no explicit synchronization is needed.

Cache the IP suffix after the first successful lookup to avoid repeated network calls.

The presented approach is one possible solution; the final design should match the specific system architecture.

Always write and run concurrency tests before deploying changes to production.

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.

javaconcurrencyDistributedorder-numberUnique ID
Programmer DD
Written by

Programmer DD

A tinkering programmer and author of "Spring Cloud Microservices in Action"

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.