【问题标题】:Enforcing a unique id in a class在类中强制使用唯一 id
【发布时间】:2013-03-04 05:00:14
【问题描述】:

仅出于思考练习的目的,如何为给定类的每个实例强制执行属性的唯一性?

这里的唯一性可以定义为在单个 JVM 上和单个用户会话中。

这是在 Java 级别,与数据库无关,主要目的是验证是否发生了冲突。

显而易见的第一步是在类级别拥有一个静态属性。

  • 随着实例数量的增加,使用 ArrayList 或其他容器似乎不切实际。
  • 在类级别递增数字计数器似乎是一种最简单的方法,但 id 必须始终遵循 last-used-id。
  • 强制使用哈希或非数字 id 可能会有问题。
  • 可能需要考虑并发性。如果两个实例有可能同时获得一个 id,那么应该防止这种情况发生。

应该如何解决这个问题?可能已经存在哪些解决方案/方法?

【问题讨论】:

  • 如果不指定“唯一性”的范围,这个问题是无法回答的,这就是为什么我怀疑有人投票关闭。定义在空间和时间上的独特性是什么意思。 IE。在单个 JVM 调用中、跨所有空间和时间或介于两者之间的东西中是唯一的。
  • 感谢@JimGarrison 要求澄清。这里的唯一性是关于没有两个相同的值。问题是关于数值和非数值的情况。这是在同一个 JVM 的上下文中以及在桌面应用程序或网站上的用户会话期间(未定义持续时间,但假设它是典型的)。直觉说,可以通过重用“本地”方法来适应更大的范围,因此这是问题的重点。

标签: java uniqueidentifier


【解决方案1】:

如果您关心性能,这里有一个线程安全、快速(无锁)和无冲突的唯一 ID 生成版本

    public class Test {
        private static AtomicInteger lastId = new AtomicInteger();
        private int id;

        public Test() {
            id = lastId.incrementAndGet();
        }
...

【讨论】:

  • 如果不理解 OP 对唯一性的定义,这没有意义,他到目前为止还没有提供。
  • @JimGarrison 唯一性,因为没有两个值是相同的。问题是关于数字和非数字值。将添加到上面的问题。
  • 在什么情况下没有两个值相同?单个JVM?一个集群?所有时间的所有 JVM?
  • @JimGarrison 上面回复:)
【解决方案2】:

只需使用 Java http://docs.oracle.com/javase/6/docs/api/java/util/UUID.html 中的 UUID 类即可。在被检查的类中创建一个 UUID 类型的字段,并在构造函数中初始化该字段。

public class Test  {
   public UUID id;
   public Test() {
      id = UUID.randomUUID();
   }
}

当需要检测碰撞时,只需像这样比较对象的 UUID 的字符串表示 ...

Test testObject1 = new Test();
Test testObject2 = new Test();
boolean collision = testObject1.id.toString().equals(testObject2.id.toString());

或者更简单地使用UUID类中的compareTo()方法...

boolean collision = testObject2.id.compareTo(testObject1.id) == 0 ? true : false;

0 表示ids 相同。不相等时 +1 和 -1。

优点:普遍唯一(可以是基于时间的、随机的),因此应该处理线程问题(有人应该确认这一点......这是基于我所知道的最好的)。更多信息herehere

要使其线程安全,请参阅 SO is java.util.UUID thread safe? 上的这个问题

缺点:需要更改被检查类的结构,即必须在类本身的源中添加id 字段。这可能方便也可能不方便。

【讨论】:

  • 为什么要浪费时间转换成字符串表示? UUID 实现 equals()Comparable<UUID>
  • @vijay 感谢您添加声明以验证冲突 :)
  • 当你只能使用equals时,compareTo的目的是什么?
  • 我引用了另一个 SO question 来回答这个确切的问题。检查更新的答案...
  • @vijay 我认为 UUID 是不可变的。除非弄错了,否则这意味着您可以毫无问题地使用 equals。
【解决方案3】:

UUID是一个很好的解决方案,但是后端使用UUID.randomUUID()方法:

synchronized public void SecureRandom.nextBytes(byte[] bytes) 

所以这很慢:线程在每个 id 生成操作中锁定一个监视器对象。

AtomicInteger 更好,因为它在 CAS 操作中循环。但同样,对于每个 id 生成操作,都必须进行同步操作。

在下面的解决方案中,只有质数生成是同步的。同步在 volatile int 上,因此速度快且线程安全。有一组素数,在一次迭代中会生成许多 id。

固定线程数

编辑:固定线程数的解决方案

我知道有多少线程会使用 ID 生成,那么你可以生成带有值的 ID

Id = I mod X + n*X

其中 X 是线程数,I 是线程数,N 是一个局部变量,每次 Id 生成都会递增。这个方案的代码其实很简单,但是必须要和hole program基础设施集成。

从素数生成的 ID

这个想法是生成 id 作为素数的因子 id = p_1^f1 * p_2^f2 * p_2^f3 * ... * p_n^fn

我们在每个线程中使用不同的素数来在每个线程中生成不同的 id 集。

假设我们使用素数 (2,3,5),序列将是:

2, 2^2, 2^3, 2^4, 2^5,..., 2^64

然后,当我们看到将产生溢出时,我们将因子滚动到下一个素数:

3, 2*3 , 2^2*3, 2^3*3, 2^4*3, 2^5*3,..., 2^62*3

接下来

3^2, 2*3^2 , 2^2*3^2, .....

生成类

编辑:必须在 AtomicInteger 上生成引物顺序才能正确

IdFactorialGenerator 类的每个实例都会生成不同的 id 集。

要生成 Id 的线程保存生成,只需使用 ThreadLocal 来设置每个线程的实例。同步只在素数生成期间实现。

package eu.pmsoft.sam.idgenerator;

public class IdFactorialGenerator {
    private static AtomicInteger nextPrimeNumber = 0;

    private int usedSlots;
    private int[] primes = new int[64];
    private int[] factors = new int[64];
    private long id;
    public IdFactorialGenerator(){
        usedSlots = 1;
        primes[0] = Sieve$.MODULE$.primeNumber(nextPrimeNumber.getAndAdd(1));
        factors[0] = 1;
        id = 1;
    }

    public long nextId(){
        for (int factorToUpdate = 0; factorToUpdate < 64; factorToUpdate++) {
            if(factorToUpdate == usedSlots) {
                factors[factorToUpdate] = 1;
                primes[factorToUpdate] = Sieve$.MODULE$.primeNumber(nextPrimeNumber.getAndAdd(1));
                usedSlots++;
            }
            int primeToExtend = primes[factorToUpdate];
            if( primeToExtend < Long.MAX_VALUE / id) {
                // id * primeToExtend < Long.MAX_VALUE
                factors[factorToUpdate] = factors[factorToUpdate]*primeToExtend;
                id = id*primeToExtend;
                return id;
            } else {
                factors[factorToUpdate] = 1;
                id = 1;
                for (int i = 0; i < usedSlots; i++) {
                    id = id*factors[i];
                }
            }
        }
        throw new IllegalStateException("I can not generate more ids");
    }
}

为了获得质数,我使用问题 7 中提供的 scala 实现:http://pavelfatin.com/scala-for-project-euler/

object Sieve {

  def primeNumber(position: Int): Int = ps(position)

  private lazy val ps: Stream[Int] = 2 #:: Stream.from(3).filter(i =>
    ps.takeWhile(j => j * j <= i).forall(i % _ > 0))
}

【讨论】:

  • 谢谢。已添加您的代码,以防您的网站不可用。
猜你喜欢
  • 2016-06-09
  • 1970-01-01
  • 1970-01-01
  • 2014-06-01
  • 2023-03-28
  • 2018-05-04
  • 1970-01-01
  • 2018-09-16
相关资源
最近更新 更多