我有一个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()中的潜在争用?
答案 0 :(得分:0)
如果你看一下Hibernate TableHiLoGenerator
,它在生成方法中使用一个简单的synchronized
,这意味着多个线程将等待,以便它们中只有一个一次执行该方法。您对lock
做了同样的事情(这有点多余 - synchronized
方法也是如此)。所以我总结你的实现很好。
答案 1 :(得分:0)
我敢打赌,你过度优化了。批量大小为一百万意味着您在每百万个插入点击中数据库。如果您使用了一千个,那么您将看不到性能差异并浪费更少的ID。当然,long
与long
无关。
在每个线程中执行上述操作意味着每个线程浪费多达一百万个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'
}
或其更好的变体。