问题描述
在记录审计日志的时候,需要生成一个主键,做法是通过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) {
}
}
题目来源及自己的思路
- 保证AuditLogIdIncrease是单例的
- AuditLogIdIncrease.nextId()是线程安全的
你期待的结果是什么?
请问这种做法能不能够满足并发情况下获取id唯一(主要是单例和线程安全的设计是否?不知道能不能通过Atomic类来实现?
你这个单例模式实现是懒汉模式,不完全的线程安全吧,如果多个线程能够同时进入if (instance == null)且instance 为null,那就会有多个线程执行了instance = new AuditLogIdIncrease()
如果对主键字段的长度和类型没有限制的话,可以考虑使用uuid作为主键,不管是使用java.util.UUID提供的uuid生成方法UUID.randomUUID().toString()还是使用mybatis 的selectkey先从数据库查询出uuid作为主键使用都很方便的