Я пытаюсь реализовать собственный генератор последовательностей для Hibernate. Готовый вариант поставляется с синхронизированным методом, и это вызывает слишком много конфликтов в моем приложении (несколько потоков, параллельно вставляющих данные в базу данных Oracle).
Я думал, что попробую StampedLock, но, к сожалению, мои тестовые примеры (150 000 строк с 16 потоками) всегда вызывают 5-15 конфликтов идентификаторов из 150 000 выполнений.
Приложил мой код, вы хоть представляете, что я делаю не так, или вы можете предложить, может быть, лучший подход? Спасибо.
import java.io.Serializable;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.StampedLock;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.id.IntegralDataTypeHolder;
import org.hibernate.id.SequenceGenerator;
public class HighConcurrencySequenceGenerator extends SequenceGenerator
{
private StampedLock lock = new StampedLock();
private AtomicLong sequenceValue; // the current generator value
private IntegralDataTypeHolder lastSourceValue; // last value read from db
private IntegralDataTypeHolder upperLimitValue; // the value to query the db again
@Override
public Serializable generate( SessionImplementor session, Object object )
{
long stamp = this.lock.readLock();
try
{
while ( needsSequenceUpdate() )
{
long ws = this.lock.tryConvertToWriteLock( stamp );
if ( ws != 0L )
{
stamp = ws;
return fetchAndGetNextSequenceValue( session );
}
this.lock.unlockRead( stamp );
stamp = this.lock.writeLock();
}
return getNextSequenceValue();
}
finally
{
this.lock.unlock( stamp );
}
}
private long fetchAndGetNextSequenceValue( SessionImplementor session )
{
this.lastSourceValue = generateHolder( session );
long lastSourceValue = this.lastSourceValue.makeValue()
.longValue();
this.sequenceValue = new AtomicLong( lastSourceValue );
long nextVal = getNextSequenceValue();
this.upperLimitValue = this.lastSourceValue.copy()
.add( this.incrementSize );
return nextVal;
}
private long getNextSequenceValue()
{
long nextVal = this.sequenceValue.getAndIncrement();
return nextVal;
}
private boolean needsSequenceUpdate()
{
return ( this.sequenceValue == null ) || !this.upperLimitValue.gt( this.sequenceValue.get() );
}
}
этот фрагмент кода не является потокобезопасным
this.sequenceValue = new AtomicLong( lastSourceValue );
в худшем случае вы получите N экземпляров AtomicLong
с одним и тем же
значение, где N - количество запущенных потоков.
Дело не в этом - проблема в том, что в некоторых редких случаях последовательность может быть восстановлена до lastSourceValue, а этого не должно быть. Скорее всего, это то, что происходит на вашей стороне, что приводит к конфликтам. + поскольку он не изменчив, может произойти много магии.
Я заменил IntegralDataTypeHolder upperLimitValue на AtomicLong, решил проблему.
но этот код вызывается только тогда, когда была получена блокировка записи, следовательно, никакой другой поток не может его запустить?