在类中强制使用唯一的id

时间:2013-03-04 05:00:15

标签: java uniqueidentifier

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

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

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

第一个明显的步骤是在类级别拥有静态属性。

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

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

3 个答案:

答案 0 :(得分:4)

如果你关心性能,这里有一个线程安全,快速(无锁)和无碰撞版本的唯一id生成

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

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

答案 1 :(得分:3)

只需使用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());

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

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

0表示id是相同的。当它们不相等时+1和-1。

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

要使其成为线程安全的,请参阅SO is java.util.UUID thread safe?

上的此问题

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

答案 2 :(得分:2)

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

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

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

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

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

固定线程数

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

我知道有多少线程将使用Id生成,然后您可以使用值

生成ID
Id = I mod X + n*X

其中X是线程数,I是线程数,N是为每个Id生成递增的局部变量。此解决方案的代码非常简单,但必须与漏洞程序基础架构集成。

从素数

生成的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组。

要让线程保存Ids的生成,只需使用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))
}