diff --git a/base/src/main/java/org/eclipse/serializer/concurrency/LockScope.java b/base/src/main/java/org/eclipse/serializer/concurrency/LockScope.java new file mode 100644 index 00000000..4905164c --- /dev/null +++ b/base/src/main/java/org/eclipse/serializer/concurrency/LockScope.java @@ -0,0 +1,78 @@ +package org.eclipse.serializer.concurrency; + +/*- + * #%L + * Eclipse Serializer Base + * %% + * Copyright (C) 2024 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import org.eclipse.serializer.functional.Action; +import org.eclipse.serializer.functional.Producer; + +/** + * Abstract base class for types, which want to utilize a {@link LockedExecutor}. + *

+ * All the executor's methods are exposed by abstract equivalents inside this type hierarchy. + */ +public abstract class LockScope +{ + private final transient LockedExecutor executor = LockedExecutor.New(); + + protected LockScope() + { + super(); + } + + /** + * Executes an operation protected by a read lock. + * + * @param action the action to execute + */ + protected void read(final Action action) + { + this.executor.read(action); + } + + /** + * Executes an operation protected by a read lock. + * + * @param the producer's return type + * @param producer the producer to execute + * @return the producer's result + */ + protected R read(final Producer producer) + { + return this.executor.read(producer); + } + + /** + * Executes an operation protected by a write lock. + * + * @param action the action to execute + */ + protected void write(final Action action) + { + this.executor.write(action); + } + + /** + * Executes an operation protected by a write lock. + * + * @param the producer's return type + * @param producer the producer to execute + * @return the producer's result + */ + protected R write(final Producer producer) + { + return this.executor.write(producer); + } + +} diff --git a/base/src/main/java/org/eclipse/serializer/concurrency/LockedExecutor.java b/base/src/main/java/org/eclipse/serializer/concurrency/LockedExecutor.java new file mode 100644 index 00000000..cc13ba48 --- /dev/null +++ b/base/src/main/java/org/eclipse/serializer/concurrency/LockedExecutor.java @@ -0,0 +1,224 @@ +package org.eclipse.serializer.concurrency; + +/*- + * #%L + * Eclipse Serializer Base + * %% + * Copyright (C) 2024 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock; +import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock; + +import org.eclipse.serializer.functional.Action; +import org.eclipse.serializer.functional.Producer; + + +/** + * Facility to execute operations with a reentrant mutual exclusion. + * + * @see ReentrantLock + * @see ReadWriteLock + */ +public interface LockedExecutor +{ + /** + * Executes an operation protected by a read lock. + * + * @param action the action to execute + */ + public void read(Action action); + + /** + * Executes an operation protected by a read lock. + * + * @param the producer's return type + * @param producer the producer to execute + * @return the producer's result + */ + public R read(Producer producer); + + /** + * Executes an operation protected by a write lock. + * + * @param action the action to execute + */ + public void write(Action action); + + /** + * Executes an operation protected by a write lock. + * + * @param the producer's return type + * @param producer the producer to execute + * @return the producer's result + */ + public R write(Producer producer); + + + + public static final class Static + { + private final static Object LOCK = new Object(); + private static volatile LockedExecutor sharedInstance; + + public static LockedExecutor sharedInstance() + { + /* + * Double-checked locking to reduce the overhead of acquiring a lock + * by testing the locking criterion. + * The field (Static.sharedInstance) has to be volatile. + */ + LockedExecutor sharedInstance = Static.sharedInstance; + if(sharedInstance == null) + { + synchronized(LOCK) + { + if((sharedInstance = Static.sharedInstance) == null) + { + sharedInstance = Static.sharedInstance = LockedExecutor.New(); + } + } + } + return sharedInstance; + } + + + private Static() + { + // static only + throw new UnsupportedOperationException(); + } + } + + + /** + * Provides a global {@link LockedExecutor} instance. + *

+ * Only a single one exists for the whole VM process, meaning it can be used to create VM-wide locks. + * + * @return a shared {@link LockedExecutor} instance + */ + public static LockedExecutor global() + { + return Static.sharedInstance(); + } + + + /** + * Pseudo-constructor method to create a new {@link LockedExecutor}. + * + * @return a newly created {@link LockedExecutor} + */ + public static LockedExecutor New() + { + return new LockedExecutor.Default(); + } + + + public static class Default implements LockedExecutor + { + private transient volatile ReentrantReadWriteLock reentrantLock; + + Default() + { + super(); + } + + private ReentrantReadWriteLock reentrantLock() + { + /* + * Double-checked locking to reduce the overhead of acquiring a lock + * by testing the locking criterion. + * The field (this.reentrantLock) has to be volatile. + */ + ReentrantReadWriteLock reentrantLock = this.reentrantLock; + if(reentrantLock == null) + { + synchronized(this) + { + if((reentrantLock = this.reentrantLock) == null) + { + reentrantLock = this.reentrantLock = new ReentrantReadWriteLock(); + } + } + } + return reentrantLock; + } + + @Override + public void read(final Action action) + { + final ReadLock readLock = this.reentrantLock().readLock(); + readLock.lock(); + + try + { + action.execute(); + } + finally + { + readLock.unlock(); + } + } + + @Override + public T read(final Producer producer) + { + final ReadLock readLock = this.reentrantLock().readLock(); + readLock.lock(); + + try + { + return producer.produce(); + } + finally + { + readLock.unlock(); + } + } + + @Override + public void write(final Action action) + { + final WriteLock writeLock = this.reentrantLock().writeLock(); + writeLock.lock(); + + try + { + action.execute(); + } + finally + { + writeLock.unlock(); + } + } + + @Override + public R write(final Producer producer) + { + final WriteLock writeLock = this.reentrantLock().writeLock(); + writeLock.lock(); + + try + { + return producer.produce(); + } + finally + { + writeLock.unlock(); + } + } + + } + +} diff --git a/base/src/main/java/org/eclipse/serializer/concurrency/StripeLockScope.java b/base/src/main/java/org/eclipse/serializer/concurrency/StripeLockScope.java new file mode 100644 index 00000000..cca58bb6 --- /dev/null +++ b/base/src/main/java/org/eclipse/serializer/concurrency/StripeLockScope.java @@ -0,0 +1,93 @@ +package org.eclipse.serializer.concurrency; + +/*- + * #%L + * Eclipse Serializer Base + * %% + * Copyright (C) 2024 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import org.eclipse.serializer.functional.Action; +import org.eclipse.serializer.functional.Producer; + + +/** + * Abstract base class for types, which want to utilize a {@link StripeLockedExecutor}. + *

+ * All the executor's methods are exposed by abstract equivalents inside this type hierarchy. + */ +public abstract class StripeLockScope +{ + private final transient StripeLockedExecutor executor = StripeLockedExecutor.New(this.stripeCount()); + + protected StripeLockScope() + { + super(); + } + + /** + * Gets the maximum number of stripes used for the {@link StripeLockedExecutor}. + * + * @return max number of stripes + */ + protected int stripeCount() + { + return Runtime.getRuntime().availableProcessors(); + } + + /** + * Executes an operation protected by a read lock. + * + * @param mutex the mutex to lock on, not null + * @param action the action to execute + */ + protected void read(final Object mutex, final Action action) + { + this.executor.read(mutex, action); + } + + /** + * Executes an operation protected by a read lock. + * + * @param the producer's return type + * @param mutex the mutex to lock on, not null + * @param producer the producer to execute + * @return the producer's result + */ + protected R read(final Object mutex, final Producer producer) + { + return this.executor.read(mutex, producer); + } + + /** + * Executes an operation protected by a write lock. + * + * @param mutex the mutex to lock on, not null + * @param action the action to execute + */ + protected void write(final Object mutex, final Action action) + { + this.executor.write(mutex, action); + } + + /** + * Executes an operation protected by a write lock. + * + * @param the producer's return type + * @param mutex the mutex to lock on, not null + * @param producer the producer to execute + * @return the producer's result + */ + protected R write(final Object mutex, final Producer producer) + { + return this.executor.write(mutex, producer); + } + +} diff --git a/base/src/main/java/org/eclipse/serializer/concurrency/StripeLockedExecutor.java b/base/src/main/java/org/eclipse/serializer/concurrency/StripeLockedExecutor.java new file mode 100644 index 00000000..940c7f0c --- /dev/null +++ b/base/src/main/java/org/eclipse/serializer/concurrency/StripeLockedExecutor.java @@ -0,0 +1,241 @@ +package org.eclipse.serializer.concurrency; + +/*- + * #%L + * Eclipse Serializer Base + * %% + * Copyright (C) 2024 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import static java.lang.Math.abs; +import static org.eclipse.serializer.math.XMath.positive; + +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock; +import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock; + +import org.eclipse.serializer.functional.Action; +import org.eclipse.serializer.functional.Producer; + + +/** + * Facility to execute operations with a reentrant mutual exclusion for defined mutexes. + * + * @see ReentrantLock + * @see ReadWriteLock + */ +public interface StripeLockedExecutor +{ + /** + * Executes an operation protected by a read lock. + * + * @param mutex the mutex to lock on, not null + * @param action the action to execute + */ + public void read(Object mutex, Action action); + + /** + * Executes an operation protected by a read lock. + * + * @param the producer's return type + * @param mutex the mutex to lock on, not null + * @param producer the producer to execute + * @return the producer's result + */ + public R read(Object mutex, Producer producer); + + /** + * Executes an operation protected by a write lock. + * + * @param mutex the mutex to lock on, not null + * @param action the action to execute + */ + public void write(Object mutex, Action action); + + /** + * Executes an operation protected by a write lock. + * + * @param the producer's return type + * @param mutex the mutex to lock on, not null + * @param producer the producer to execute + * @return the producer's result + */ + public R write(Object mutex, Producer producer); + + + + public static final class Static + { + private final static Object LOCK = new Object(); + private static volatile StripeLockedExecutor sharedInstance; + + public static StripeLockedExecutor sharedInstance() + { + /* + * Double-checked locking to reduce the overhead of acquiring a lock + * by testing the locking criterion. + * The field (Static.sharedInstance) has to be volatile. + */ + StripeLockedExecutor sharedInstance = Static.sharedInstance; + if(sharedInstance == null) + { + synchronized(LOCK) + { + if((sharedInstance = Static.sharedInstance) == null) + { + sharedInstance = Static.sharedInstance = StripeLockedExecutor.New( + Runtime.getRuntime().availableProcessors() + ); + } + } + } + return sharedInstance; + } + + + private Static() + { + // static only + throw new UnsupportedOperationException(); + } + } + + + /** + * Provides a global {@link StripeLockedExecutor} instance. + *

+ * Only a single one exists for the whole VM process, meaning it can be used to create VM-wide locks. + * + * @return a shared {@link StripeLockedExecutor} instance + */ + public static StripeLockedExecutor global() + { + return Static.sharedInstance(); + } + + + + /** + * Pseudo-constructor method to create a new {@link StripeLockedExecutor}. + * + * @param stripeCount maximum number of stripes + * @return a newly created {@link StripeLockedExecutor} + */ + public static StripeLockedExecutor New(final int stripeCount) + { + return new StripeLockedExecutor.Default( + positive(stripeCount) + ); + } + + + public static class Default implements StripeLockedExecutor + { + private transient volatile ReentrantReadWriteLock[] reentrantLocks; + + Default(final int stripeCount) + { + super(); + + this.reentrantLocks = new ReentrantReadWriteLock[stripeCount]; + } + + private ReentrantReadWriteLock reentrantLock(final Object mutex) + { + /* + * Double-checked locking to reduce the overhead of acquiring a lock + * by testing the locking criterion. + * The field (this.reentrantLocks) has to be volatile. + */ + + final int index = abs(mutex.hashCode()) % this.reentrantLocks.length; + ReentrantReadWriteLock reentrantLock = this.reentrantLocks[index]; + if(reentrantLock == null) + { + synchronized(this) + { + if((reentrantLock = this.reentrantLocks[index]) == null) + { + reentrantLock = this.reentrantLocks[index] = new ReentrantReadWriteLock(); + } + } + } + return reentrantLock; + } + + @Override + public void read(final Object mutex, final Action action) + { + final ReadLock readLock = this.reentrantLock(mutex).readLock(); + readLock.lock(); + + try + { + action.execute(); + } + finally + { + readLock.unlock(); + } + } + + @Override + public T read(final Object mutex, final Producer producer) + { + final ReadLock readLock = this.reentrantLock(mutex).readLock(); + readLock.lock(); + + try + { + return producer.produce(); + } + finally + { + readLock.unlock(); + } + } + + @Override + public void write(final Object mutex, final Action action) + { + final WriteLock writeLock = this.reentrantLock(mutex).writeLock(); + writeLock.lock(); + + try + { + action.execute(); + } + finally + { + writeLock.unlock(); + } + } + + @Override + public R write(final Object mutex, final Producer producer) + { + final WriteLock writeLock = this.reentrantLock(mutex).writeLock(); + writeLock.lock(); + + try + { + return producer.produce(); + } + finally + { + writeLock.unlock(); + } + } + + } + +} diff --git a/base/src/main/java/org/eclipse/serializer/functional/Producer.java b/base/src/main/java/org/eclipse/serializer/functional/Producer.java new file mode 100644 index 00000000..4bb19afa --- /dev/null +++ b/base/src/main/java/org/eclipse/serializer/functional/Producer.java @@ -0,0 +1,21 @@ +package org.eclipse.serializer.functional; + +/*- + * #%L + * Eclipse Serializer Base + * %% + * Copyright (C) 2024 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +@FunctionalInterface +public interface Producer +{ + public R produce(); +}