如何在并发情况下保证获取的id是唯一的?

问题描述

在记录审计日志的时候,需要生成一个主键,做法是通过sql查询最大主键然后+1作为主键,但是出现了主键冲突的问题目。现在尝试在插入之前把日志主键id生成好,做法如下:

相关代码

// service方法
@Service
public class AuditLogServiceImlp implements IAuditLogService{
    public long nextId(String sysId){
        return AuditLogIdIncrease.getInstance().nextId(sysId);
    }
}

// id生成方法
public class AuditLogIdIncrease {

    private static AuditLogIdIncrease instance;

    private final int threadIdBits = 4;// 线程号位数
    private final int maxRandomId = 9;// 随机数最大值

    private AuditLogIdIncrease() {
    }

    public static AuditLogIdIncrease getInstance() {
        if (instance == null) {
            instance = new AuditLogIdIncrease();
        }
        return instance;
    }

    /**
     * 获取下一个ID 线程安全
     * @param sysId
     * @return
     */
    public synchronized long nextId(int sysId) {
        return splice(sysId);
    }

    /**
     * 拼接id:时间戳+线程号+随机数+系统标识
     * @param sysId
     * @return
     */
    private long splice(int sysId) {
        String strLong = getCurrentTime() + getCurrentThreadId() + getRandomNum() + sysId;
        return Long.parseLong(strLong);
    }

    /**
     * 系统时间戳
     * @return
     */
    private long getCurrentTime() {
        return System.currentTimeMillis();
    }

    /**
     * 获取当前线程号
     * @return
     */
    private String getCurrentThreadId() {
        String strCurrentThreadId = String.format("%0" + threadIdBits + "d", Thread.currentThread().getId());
        if (strCurrentThreadId.length() > threadIdBits) {
            strCurrentThreadId = strCurrentThreadId.substring(0, threadIdBits);
        }
        return strCurrentThreadId;
    }

    /**
     * 获取随机数
     * @return
     */
    private int getRandomNum() {
        return new Random().nextInt(maxRandomId);
    }

    public static void main(String[] args) {

    }
}

题目来源及自己的思路

  1. 保证AuditLogIdIncrease是单例的
  2. AuditLogIdIncrease.nextId()是线程安全的

你期待的结果是什么?

请问这种做法能不能够满足并发情况下获取id唯一(主要是单例和线程安全的设计是否?不知道能不能通过Atomic类来实现?

阅读 3.1k
1 个回答
新手上路,请多包涵

你这个单例模式实现是懒汉模式,不完全的线程安全吧,如果多个线程能够同时进入if (instance == null)且instance 为null,那就会有多个线程执行了instance = new AuditLogIdIncrease()
如果对主键字段的长度和类型没有限制的话,可以考虑使用uuid作为主键,不管是使用java.util.UUID提供的uuid生成方法UUID.randomUUID().toString()还是使用mybatis 的selectkey先从数据库查询出uuid作为主键使用都很方便的

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题