如何为 UniqueId 编写一个 Java 序列生成器,它将在每天午夜自动重置为 '1'(基本序列号)
How to write a Java sequence generator for UniqueId, which will Auto-Reset to '1' (base seq. number) at midnight everyday
我们有一个 Spring 启动服务,我们每天都在其中接收文件,由于某些问题(在生产者身上),我们收到多个附加了相同名称和日期的文件。
新文件覆盖旧文件,为了处理它,我们想在每个文件名处附加一个序列(从 1 开始)。
但序列应在每天午夜自动重置为“1”。
任何人都可以建议 API 或重置序列的方法。
要生成自动序列,我们使用 AtomicSequenceGenerator ,但我们无法实现简单的自动重置逻辑。
public class AtomicSequenceGenerator implements SequenceGenerator {
private AtomicLong value = new AtomicLong(1);
@Override
public long getNext() {
return value.getAndIncrement();
}
}
您可以为您的生成器创建一个单例实例,它会在新日期过去后立即自行重置。
像这样:
public class AtomicSequenceGenerator implements SequenceGenerator {
// Private constructor in order to avoid the creation of an instance from outside the class
private AtomicSequenceGenerator(){}
private AtomicLong value = new AtomicLong(1);
@Override
public long getNext() {
return value.getAndIncrement();
}
// This is where the fun starts
// The T indicates some type that represents the file date
private static T prev_file_date = null;
private static AtomicSequenceGenerator instance = new AtomicSequenceGenerator();
public static synchronized long getNext(T file_date)
{
if ((prev_file_date == null) || (!prev_file_date.equals(file_date)))
{
instance.value.set(1);
prev_file_date = file_date;
}
return (instance.getNext());
}
}
不接收两次1:
public class AtomicSequenceGenerator implements SequenceGenerator {
private AtomicLong value = new AtomicLong(1);
private volatile LocalDate lastDate = LocalDate.now();
@Override
public long getNext() {
LocalDate today = LocalDate.now();
if (!today.equals(lastDate)) {
synchronized(this) {
if (!today.equals(lastDate)) {
lastDate = today;
value.set(1);
}
}
}
return value.getAndIncrement();
}
}
有点难看,所以试试单计数器:
public class AtomicSequenceGenerator implements SequenceGenerator {
private static long countWithDate(long count, LocalDate date) {
return (((long)date.getDayOfYear()) << (63L-9)) | count;
}
private static long countPart(long datedCount) {
return datedCount & ((1L << (63L-9)) - 1);
}
private static boolean dateChanged(long datedCount, LocalDate date) {
return (int)(datedCount >>> (63L-9)) != date.getDayOfYear();
}
private AtomicLong value = new AtomicLong(countWithDate(1, LocalDate.now()));
@Override
public long getNext() {
long datedCount = value.getAndIncrement();
LocalDate today = LocalDate.now();
if (dateChanged(dateCount, today)) {
long next = countWithDate(1L, today);
if (value.compareAndSet(datedCount+1, next)) {
datedCount = next;
} else {
datedCount = getNext();
}
}
return datedCount;
}
}
这使用 AtomicLong 并将年中的日期打包到计数器中。
- 一个拉下一个计数器。
- 如果日期发生变化则:
- 什么时候能定下第二天的1,就给。
- 如果不是,可能是更早柜台的人拿了 1,
然后我们需要再拍下一张。
根据@JoopEggen 的要求,他的第一个解决方案是我的版本:
public class AtomicSequenceGenerator implements SequenceGenerator {
private final Clock clock;
private final Object lock = new Object();
@GuardedBy("lock")
private long value;
@GuardedBy("lock")
private LocalDate today;
public AtomicSequenceGenerator(Clock clock) {
this.clock = clock;
synchronized (lock) {
value = 1;
today = LocalDate.now(clock);
}
}
@Override
public long getNext() {
synchronized (lock) {
LocalDate date = LocalDate.now(clock);
if (!date.equals(today)) {
today = date;
value = 1;
}
return value++;
}
}
}
主要区别是:
- 这只使用一个私人监视器来保护
LocalDate
和 value
。
value
现在是一个普通的 long,因为它有锁保护,它不再需要 AtomicLong
。
- 我注入一个
Clock
对象(为了更容易测试)
- 没有双重检查锁定。可以说双重检查锁定可以更快,但我不知道它是否真的需要,直到你做一些性能测试。
我们有一个 Spring 启动服务,我们每天都在其中接收文件,由于某些问题(在生产者身上),我们收到多个附加了相同名称和日期的文件。 新文件覆盖旧文件,为了处理它,我们想在每个文件名处附加一个序列(从 1 开始)。 但序列应在每天午夜自动重置为“1”。
任何人都可以建议 API 或重置序列的方法。
要生成自动序列,我们使用 AtomicSequenceGenerator ,但我们无法实现简单的自动重置逻辑。
public class AtomicSequenceGenerator implements SequenceGenerator {
private AtomicLong value = new AtomicLong(1);
@Override
public long getNext() {
return value.getAndIncrement();
}
}
您可以为您的生成器创建一个单例实例,它会在新日期过去后立即自行重置。
像这样:
public class AtomicSequenceGenerator implements SequenceGenerator {
// Private constructor in order to avoid the creation of an instance from outside the class
private AtomicSequenceGenerator(){}
private AtomicLong value = new AtomicLong(1);
@Override
public long getNext() {
return value.getAndIncrement();
}
// This is where the fun starts
// The T indicates some type that represents the file date
private static T prev_file_date = null;
private static AtomicSequenceGenerator instance = new AtomicSequenceGenerator();
public static synchronized long getNext(T file_date)
{
if ((prev_file_date == null) || (!prev_file_date.equals(file_date)))
{
instance.value.set(1);
prev_file_date = file_date;
}
return (instance.getNext());
}
}
不接收两次1:
public class AtomicSequenceGenerator implements SequenceGenerator {
private AtomicLong value = new AtomicLong(1);
private volatile LocalDate lastDate = LocalDate.now();
@Override
public long getNext() {
LocalDate today = LocalDate.now();
if (!today.equals(lastDate)) {
synchronized(this) {
if (!today.equals(lastDate)) {
lastDate = today;
value.set(1);
}
}
}
return value.getAndIncrement();
}
}
有点难看,所以试试单计数器:
public class AtomicSequenceGenerator implements SequenceGenerator {
private static long countWithDate(long count, LocalDate date) {
return (((long)date.getDayOfYear()) << (63L-9)) | count;
}
private static long countPart(long datedCount) {
return datedCount & ((1L << (63L-9)) - 1);
}
private static boolean dateChanged(long datedCount, LocalDate date) {
return (int)(datedCount >>> (63L-9)) != date.getDayOfYear();
}
private AtomicLong value = new AtomicLong(countWithDate(1, LocalDate.now()));
@Override
public long getNext() {
long datedCount = value.getAndIncrement();
LocalDate today = LocalDate.now();
if (dateChanged(dateCount, today)) {
long next = countWithDate(1L, today);
if (value.compareAndSet(datedCount+1, next)) {
datedCount = next;
} else {
datedCount = getNext();
}
}
return datedCount;
}
}
这使用 AtomicLong 并将年中的日期打包到计数器中。
- 一个拉下一个计数器。
- 如果日期发生变化则:
- 什么时候能定下第二天的1,就给。
- 如果不是,可能是更早柜台的人拿了 1, 然后我们需要再拍下一张。
根据@JoopEggen 的要求,他的第一个解决方案是我的版本:
public class AtomicSequenceGenerator implements SequenceGenerator {
private final Clock clock;
private final Object lock = new Object();
@GuardedBy("lock")
private long value;
@GuardedBy("lock")
private LocalDate today;
public AtomicSequenceGenerator(Clock clock) {
this.clock = clock;
synchronized (lock) {
value = 1;
today = LocalDate.now(clock);
}
}
@Override
public long getNext() {
synchronized (lock) {
LocalDate date = LocalDate.now(clock);
if (!date.equals(today)) {
today = date;
value = 1;
}
return value++;
}
}
}
主要区别是:
- 这只使用一个私人监视器来保护
LocalDate
和value
。 value
现在是一个普通的 long,因为它有锁保护,它不再需要AtomicLong
。- 我注入一个
Clock
对象(为了更容易测试) - 没有双重检查锁定。可以说双重检查锁定可以更快,但我不知道它是否真的需要,直到你做一些性能测试。