Hi-Lo Id发电机的改进

时间:2010-08-09 21:45:56

标签: java multithreading hilo

我有一个hi-lo Id生成器,我在多线程环境中使用它。每个线程每秒最多可调用100k次

我有一个相当好(和安全)的实现工作正常。 IdAllocator是获取下一批“ID”的对象。你可以假设这是线程安全的。我还将batchSize设置得相当高(100万)

private final IdAllocator idAllocator;
private final String idKey;
private final int batchSize;

private final Object lock = new Object();

private long currentId = 0;
private long nextFetchId = -1;

IdGeneratorImpl( IdAllocator idAllocator, String idKey, int batchSize )
{
    this.idAllocator = idAllocator;
    this.idKey = idKey;
    this.batchSize = batchSize;
}

public long getNextId()
{
    synchronized ( lock )
    {
        if ( currentId < nextFetchId )
        {
            long localCurrent = currentId;
            currentId++;
            return localCurrent;
        }

        currentId = idAllocator.allocateBatch( idKey, batchSize );
        nextFetchId = currentId + batchSize;

        return getNextId();
    }
}

目前我大部分时间,但并非总是如此,以无条件的方式使用它。但是将来它会被多个线程调用。

我已经考虑过为每个线程实例化一个这样的实例,这可能是最好的方法。然而,作为一种智力/学习经验,我想知道无论如何我可以改进这个实现,特别是当多个线程经常调用它时减少getNextId()中的潜在争用?

2 个答案:

答案 0 :(得分:0)

如果你看一下Hibernate TableHiLoGenerator,它在生成方法中使用一个简单的synchronized,这意味着多个线程将等待,以便它们中只有一个一次执行该方法。您对lock做了同样的事情(这有点多余 - synchronized方法也是如此)。所以我总结你的实现很好。

答案 1 :(得分:0)

我敢打赌,你过度优化了。批量大小为一百万意味着您在每百万个插入点击中数据库。如果您使用了一千个,那么您将看不到性能差异并浪费更少的ID。当然,longlong无关。

在每个线程中执行上述操作意味着每个线程浪费多达一百万个ID。我知道,AtomicLong没什么大不了的。

它也会不必要地增加启动成本,并可能留下很多线程本地垃圾。再一次,没什么大不了的,但有什么好处?像上面这样的短代码需要几纳秒,而数据库访问需要更多的数量级。所以没有任何争论可以让你失望。

但是,优化此方法的正确方法可能是getAndIncrement。它比同步具有更低的开销。当您超出nextFetchId时,请使用HiLoOptimizer并仅输入同步块。在这个块中,首先重新检查另一个线程是否完成了你要做的工作,如果没有,从数据库中获取下一个id。

更简单的方法是使用休眠buildscript { repositories { mavenCentral() maven { url "https://oss.sonatype.org/content/repositories/snapshots/" } jcenter() } dependencies { classpath 'com.android.tools.build:gradle:2.2.0' } 或其更好的变体。