如何为 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++;
        }
    }
}

主要区别是:

  • 这只使用一个私人监视器来保护 LocalDatevalue
  • value 现在是一个普通的 long,因为它有锁保护,它不再需要 AtomicLong
  • 我注入一个 Clock 对象(为了更容易测试)
  • 没有双重检查锁定。可以说双重检查锁定可以更快,但我不知道它是否真的需要,直到你做一些性能测试。