HHH-17005 - Replace monitor with a Java lock to avoid pinning when using virtual threads

This commit replaces a `synchronized` with a ReentrantLock in:

- PooledOptimizer
- PooledLoOptimizer
- LegacyHiLoAlgorithmOptimizer
- HiLoOptimizer

The other implementations do not use a monitor lock.
This commit is contained in:
Clement Escoffier 2023-07-27 10:15:50 +02:00 committed by Sanne Grinovero
parent d825801f0d
commit 71cb3477ca
4 changed files with 147 additions and 68 deletions

View File

@ -9,6 +9,8 @@ package org.hibernate.id.enhanced;
import java.io.Serializable;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import org.hibernate.HibernateException;
import org.hibernate.id.IntegralDataTypeHolder;
@ -80,29 +82,39 @@ public class HiLoOptimizer extends AbstractOptimizer {
}
@Override
public synchronized Serializable generate(AccessCallback callback) {
final GenerationState generationState = locateGenerationState( callback.getTenantIdentifier() );
public Serializable generate(AccessCallback callback) {
lock.lock();
try {
final GenerationState generationState = locateGenerationState( callback.getTenantIdentifier() );
if ( generationState.lastSourceValue == null ) {
// first call, so initialize ourselves. we need to read the database
// value and set up the 'bucket' boundaries
generationState.lastSourceValue = callback.getNextValue();
while ( generationState.lastSourceValue.lt( 1 ) ) {
if ( generationState.lastSourceValue == null ) {
// first call, so initialize ourselves. we need to read the database
// value and set up the 'bucket' boundaries
generationState.lastSourceValue = callback.getNextValue();
while ( generationState.lastSourceValue.lt( 1 ) ) {
generationState.lastSourceValue = callback.getNextValue();
}
// upperLimit defines the upper end of the bucket values
generationState.upperLimit = generationState.lastSourceValue.copy().multiplyBy( incrementSize ).increment();
// initialize value to the lower end of the bucket
generationState.value = generationState.upperLimit.copy().subtract( incrementSize );
}
// upperLimit defines the upper end of the bucket values
generationState.upperLimit = generationState.lastSourceValue.copy().multiplyBy( incrementSize ).increment();
// initialize value to the lower end of the bucket
generationState.value = generationState.upperLimit.copy().subtract( incrementSize );
else if ( ! generationState.upperLimit.gt( generationState.value ) ) {
generationState.lastSourceValue = callback.getNextValue();
generationState.upperLimit = generationState.lastSourceValue.copy().multiplyBy( incrementSize ).increment();
generationState.value = generationState.upperLimit.copy().subtract( incrementSize );
}
return generationState.value.makeValueThenIncrement();
}
else if ( ! generationState.upperLimit.gt( generationState.value ) ) {
generationState.lastSourceValue = callback.getNextValue();
generationState.upperLimit = generationState.lastSourceValue.copy().multiplyBy( incrementSize ).increment();
generationState.value = generationState.upperLimit.copy().subtract( incrementSize );
finally {
lock.unlock();
}
return generationState.value.makeValueThenIncrement();
}
/**
* Use a lock instead of the monitor lock to avoid pinning when using virtual threads.
*/
private final Lock lock = new ReentrantLock();
private GenerationState noTenantState;
private Map<String,GenerationState> tenantSpecificState;
@ -139,8 +151,14 @@ public class HiLoOptimizer extends AbstractOptimizer {
}
@Override
public synchronized IntegralDataTypeHolder getLastSourceValue() {
return noTenantGenerationState().lastSourceValue;
public IntegralDataTypeHolder getLastSourceValue() {
lock.lock();
try {
return noTenantGenerationState().lastSourceValue;
}
finally {
lock.unlock();
}
}
@Override
@ -155,8 +173,14 @@ public class HiLoOptimizer extends AbstractOptimizer {
*
* @return Value for property 'lastValue'.
*/
public synchronized IntegralDataTypeHolder getLastValue() {
return noTenantGenerationState().value.copy().decrement();
public IntegralDataTypeHolder getLastValue() {
lock.lock();
try {
return noTenantGenerationState().value.copy().decrement();
}
finally {
lock.unlock();
}
}
/**
@ -166,7 +190,13 @@ public class HiLoOptimizer extends AbstractOptimizer {
*
* @return Value for property 'upperLimit'.
*/
public synchronized IntegralDataTypeHolder getHiValue() {
return noTenantGenerationState().upperLimit;
public IntegralDataTypeHolder getHiValue() {
lock.lock();
try {
return noTenantGenerationState().upperLimit;
}
finally {
lock.unlock();
}
}
}

View File

@ -9,6 +9,8 @@ package org.hibernate.id.enhanced;
import java.io.Serializable;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import org.hibernate.HibernateException;
import org.hibernate.id.IntegralDataTypeHolder;
@ -53,18 +55,29 @@ public class LegacyHiLoAlgorithmOptimizer extends AbstractOptimizer {
}
@Override
public synchronized Serializable generate(AccessCallback callback) {
final GenerationState generationState = locateGenerationState( callback.getTenantIdentifier() );
public Serializable generate(AccessCallback callback) {
lock.lock();
try {
final GenerationState generationState = locateGenerationState( callback.getTenantIdentifier() );
if ( generationState.lo > generationState.maxLo ) {
generationState.lastSourceValue = callback.getNextValue();
generationState.lo = generationState.lastSourceValue.eq( 0 ) ? 1 : 0;
generationState.hi = generationState.lastSourceValue.copy().multiplyBy( generationState.maxLo + 1 );
if ( generationState.lo > generationState.maxLo ) {
generationState.lastSourceValue = callback.getNextValue();
generationState.lo = generationState.lastSourceValue.eq( 0 ) ? 1 : 0;
generationState.hi = generationState.lastSourceValue.copy().multiplyBy( generationState.maxLo + 1 );
}
generationState.value = generationState.hi.copy().add( generationState.lo++ );
return generationState.value.makeValue();
}
generationState.value = generationState.hi.copy().add( generationState.lo++ );
return generationState.value.makeValue();
finally {
lock.unlock();
}
}
/**
* Use a lock instead of the monitor lock to avoid pinning when using virtual threads.
*/
private final Lock lock = new ReentrantLock();
private GenerationState noTenantState;
private Map<String,GenerationState> tenantSpecificState;
@ -108,8 +121,14 @@ public class LegacyHiLoAlgorithmOptimizer extends AbstractOptimizer {
}
@Override
public synchronized IntegralDataTypeHolder getLastSourceValue() {
return noTenantGenerationState().lastSourceValue.copy();
public IntegralDataTypeHolder getLastSourceValue() {
lock.lock();
try {
return noTenantGenerationState().lastSourceValue.copy();
}
finally {
lock.unlock();
}
}
@Override
@ -124,7 +143,13 @@ public class LegacyHiLoAlgorithmOptimizer extends AbstractOptimizer {
*
* @return Value for property 'lastValue'.
*/
public synchronized IntegralDataTypeHolder getLastValue() {
return noTenantGenerationState().value;
public IntegralDataTypeHolder getLastValue() {
lock.lock();
try {
return noTenantGenerationState().value;
}
finally {
lock.unlock();
}
}
}

View File

@ -9,6 +9,8 @@ package org.hibernate.id.enhanced;
import java.io.Serializable;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.Lock;
import org.hibernate.HibernateException;
import org.hibernate.id.IntegralDataTypeHolder;
@ -53,22 +55,32 @@ public class PooledLoOptimizer extends AbstractOptimizer {
}
@Override
public synchronized Serializable generate(AccessCallback callback) {
final GenerationState generationState = locateGenerationState( callback.getTenantIdentifier() );
public Serializable generate(AccessCallback callback) {
lock.lock();
try {
final GenerationState generationState = locateGenerationState( callback.getTenantIdentifier() );
if ( generationState.lastSourceValue == null
|| ! generationState.value.lt( generationState.upperLimitValue ) ) {
generationState.lastSourceValue = callback.getNextValue();
generationState.upperLimitValue = generationState.lastSourceValue.copy().add( incrementSize );
generationState.value = generationState.lastSourceValue.copy();
// handle cases where initial-value is less that one (hsqldb for instance).
while ( generationState.value.lt( 1 ) ) {
generationState.value.increment();
if ( generationState.lastSourceValue == null
|| ! generationState.value.lt( generationState.upperLimitValue ) ) {
generationState.lastSourceValue = callback.getNextValue();
generationState.upperLimitValue = generationState.lastSourceValue.copy().add( incrementSize );
generationState.value = generationState.lastSourceValue.copy();
// handle cases where initial-value is less that one (hsqldb for instance).
while ( generationState.value.lt( 1 ) ) {
generationState.value.increment();
}
}
return generationState.value.makeValueThenIncrement();
}
finally {
lock.unlock();
}
return generationState.value.makeValueThenIncrement();
}
/**
* Use a lock instead of the monitor lock to avoid pinning when using virtual threads.
*/
private final Lock lock = new ReentrantLock();
private GenerationState noTenantState;
private Map<String,GenerationState> tenantSpecificState;

View File

@ -9,6 +9,8 @@ package org.hibernate.id.enhanced;
import java.io.Serializable;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import org.hibernate.HibernateException;
import org.hibernate.id.IntegralDataTypeHolder;
@ -65,36 +67,46 @@ public class PooledOptimizer extends AbstractOptimizer implements InitialValueAw
@Override
public synchronized Serializable generate(AccessCallback callback) {
final GenerationState generationState = locateGenerationState( callback.getTenantIdentifier() );
public Serializable generate(AccessCallback callback) {
lock.lock();
try {
final GenerationState generationState = locateGenerationState( callback.getTenantIdentifier() );
if ( generationState.hiValue == null ) {
generationState.hiValue = callback.getNextValue();
// unfortunately not really safe to normalize this
// to 1 as an initial value like we do for the others
// because we would not be able to control this if
// we are using a sequence...
if ( generationState.hiValue.lt( 1 ) ) {
log.pooledOptimizerReportedInitialValue( generationState.hiValue );
if ( generationState.hiValue == null ) {
generationState.hiValue = callback.getNextValue();
// unfortunately not really safe to normalize this
// to 1 as an initial value like we do for the others
// because we would not be able to control this if
// we are using a sequence...
if ( generationState.hiValue.lt( 1 ) ) {
log.pooledOptimizerReportedInitialValue( generationState.hiValue );
}
// the call to obtain next-value just gave us the initialValue
if ( ( initialValue == -1
&& generationState.hiValue.lt( incrementSize ) )
|| generationState.hiValue.eq( initialValue ) ) {
generationState.value = generationState.hiValue.copy();
}
else {
generationState.value = generationState.hiValue.copy().subtract( incrementSize - 1 );
}
}
// the call to obtain next-value just gave us the initialValue
if ( ( initialValue == -1
&& generationState.hiValue.lt( incrementSize ) )
|| generationState.hiValue.eq( initialValue ) ) {
generationState.value = generationState.hiValue.copy();
}
else {
else if ( generationState.value.gt( generationState.hiValue ) ) {
generationState.hiValue = callback.getNextValue();
generationState.value = generationState.hiValue.copy().subtract( incrementSize - 1 );
}
}
else if ( generationState.value.gt( generationState.hiValue ) ) {
generationState.hiValue = callback.getNextValue();
generationState.value = generationState.hiValue.copy().subtract( incrementSize - 1 );
}
return generationState.value.makeValueThenIncrement();
return generationState.value.makeValueThenIncrement();
}
finally {
lock.unlock();
}
}
/**
* Use a lock instead of the monitor lock to avoid pinning when using virtual threads.
*/
private final Lock lock = new ReentrantLock();
private GenerationState noTenantState;
private Map<String,GenerationState> tenantSpecificState;