【问题标题】:Sequence Generator in Java for Unique IdJava中用于唯一ID的序列生成器
【发布时间】:2014-04-20 10:37:00
【问题描述】:

我打算编写一个序列生成器,它将被使用 在发布期间在我的 REST 资源实现类中生成 唯一身份。由于每个 post 请求都由单独的线程处理, 我使变量 volatile 和方法同步。 我没有选择使用序列或其他东西 传统的 RDBMS 提供。

public class SequenceGen {
    volatile static int n = 0;  
    public synchronized int nextNum(){
        return n++;
    }   
}

这是我目前所拥有的,并计划创建一个变量 我的 REST 实现中的 SequenceGen。我的实际问题是意志 它在某处断裂?我用两个线程测试,我没有看到 任何重复的值。

【问题讨论】:

  • 制作 nextNum 方法 static 以确保它。
  • 如果您的初始测试显示它有效并且您的逻辑表明它应该有效,通常您不应该再担心它并接受它(目前)有效。当它真正造成问题时,请担心它。
  • 为什么不直接使用AtomicInteger
  • 您的进程涉及多个 jvm?在您的 POC 中,我想您使用的是单个 jvm。
  • 该字段应该是私有的。 volatile 是多余的,因为您使用同步方法访问它。但我同意 fge:AtomicInteger 是一种更好、更安全、更快的解决方案。如果您计划拥有多个虚拟机,则应考虑使用 UUID(但您将获得 String,而不是 int)

标签: java multithreading sequence


【解决方案1】:

它会起作用,但是 AtomicInteger 是一个内置类型,非常适合您的用例。

AtomicInteger seq = new AtomicInteger();
int nextVal = seq.incrementAndGet();

【讨论】:

【解决方案2】:

您可以利用java.util.prefs.Preferences 将序列生成器的当前状态保存在磁盘上,并在以后再次使用它。

(另外,您可能需要使用多个序列生成器)

import java.lang.ref.SoftReference;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import java.util.prefs.Preferences;

public final class SequenceGenerator {

    private static final Preferences PREFS = Preferences.userNodeForPackage(SequenceGenerator.class);
    private static final AtomicLong SEQ_ID = new AtomicLong(Integer.parseInt(PREFS.get("seq_id", "1")));
    private static final Map<Long, SoftReference<SequenceGenerator>> GENERATORS = new ConcurrentHashMap<>();
    private static final SequenceGenerator DEF_GENERATOR = new SequenceGenerator(0L, Long.parseLong(PREFS.get("seq_0", "1")));

    static {
        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
            GENERATORS.values().stream()
                    .map(SoftReference::get)
                    .filter(seq -> seq != null && seq.isPersistOnExit())
                    .forEach(SequenceGenerator::persist);
            if (DEF_GENERATOR.isPersistOnExit()) {
                DEF_GENERATOR.persist();
            }
            PREFS.put("seq_id", SEQ_ID.toString());
        }));
    }

    private final long sequenceId;
    private final AtomicLong counter;
    private final AtomicBoolean persistOnExit = new AtomicBoolean();

    private SequenceGenerator(long sequenceId, long initialValue) {
        this.sequenceId = sequenceId;
        counter = new AtomicLong(initialValue);
    }

    public long nextId() {
        return counter.getAndIncrement();
    }

    public long currentId() {
        return counter.get();
    }

    public long getSequenceId() {
        return sequenceId;
    }

    public boolean isPersistOnExit() {
        return persistOnExit.get();
    }

    public void setPersistOnExit(boolean persistOnExit) {
        this.persistOnExit.set(persistOnExit);
    }

    public void persist() {
        PREFS.put("seq_" + sequenceId, counter.toString());
    }

    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        GENERATORS.remove(sequenceId);
        if (persistOnExit.get()) {
            persist();
        }
    }

    @Override
    public int hashCode() {
        return Long.hashCode(sequenceId);
    }

    @Override
    public boolean equals(Object obj) {
        return obj == this || obj != null && obj instanceof SequenceGenerator && sequenceId == ((SequenceGenerator) obj).sequenceId;
    }

    @Override
    public String toString() {
        return "{" +
                "counter=" + counter +
                ", seq=" + sequenceId +
                '}';
    }

    public static SequenceGenerator getDefault() {
        return DEF_GENERATOR;
    }

    public static SequenceGenerator get(long sequenceId) {
        if (sequenceId < 0) {
            throw new IllegalArgumentException("(sequenceId = " + sequenceId + ") < 0");
        }
        if (sequenceId == 0) {
            return DEF_GENERATOR;
        }
        SoftReference<SequenceGenerator> r = GENERATORS.computeIfAbsent(sequenceId, sid -> {
            try {
                return new SoftReference<>(new SequenceGenerator(sid, Long.parseLong(PREFS.get("seq_" + sid, null))));
            } catch (Throwable t) {
                return null;
            }
        });
        return r == null ? null : r.get();
    }

    public static SequenceGenerator create() {
        return create(1);
    }

    public static SequenceGenerator create(long initialValue) {
        long sequenceId = SEQ_ID.getAndIncrement();
        SequenceGenerator seq = new SequenceGenerator(sequenceId, Long.parseLong(PREFS.get("seq_" + sequenceId, "" + initialValue)));
        GENERATORS.put(sequenceId, new SoftReference<>(seq));
        return seq;
    }

}

【讨论】:

【解决方案3】:

如果您愿意将String 用于ID,而不是int,您可能需要考虑使用UUID(通用唯一标识符)。非常易于使用,顾名思义,它们是独一无二的。以下是如何生成的示例:

// the value of uuid will be something like '03c9a439-fba6-41e1-a18a-4c542c12e6a8'
String uuid = java.util.UUID.randomUUID().toString()

UUID 也提供比int 更好的安全性,因为对于整数,您只需将请求 ID 加 1 即可猜测下一个请求 ID,但 UUID 不是连续的,任何人猜测其他人的请求 ID 的机会都很大苗条。

【讨论】:

  • 虽然您的 cmets 是正确的,但使用 UUID 时性能会受到巨大影响
  • @IoannisDeligiannis - 请详细说明。它对性能有多大影响?
  • 在我的情况下,性能非常糟糕,所以我附加了一个分析器并意识到它实际上会命中 HDD。您始终可以将它添加到一个循环中,看看它对您的硬件有多慢。在下面的答案中,它是 1100 毫秒。 stackoverflow.com/questions/14532976/…
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-09-11
  • 2011-06-26
  • 1970-01-01
相关资源
最近更新 更多